《Effective JavaScript》最后一篇

84 阅读4分钟

第四章:类型定义

条款28:倾向选择代表有效状态的类型

什么是有效状态?(个人理解)
状态是一个变量对应一种状态的。多个变量组合构成一种状态不能说成一种有效状态。

  • request请求中我们常常设置loading和error状态,显示不同的ui
// 如果loading,error有遗漏的控制逻辑,那么就会产生bug
// loading 和error代码逻辑阅读有一点点成本
async function getInfo() {
    let loading = true
    let error = false
    try {
        await APIs.getInfo()
    } catch(e) {
        error = true
    }
    loading = false
}

function handleState() {
 if (error) { throw Error('request error')}
 if (loading) { // render loading }
 // render data
}
// 取而代之使用一个有效状态控制是更加清晰的
async function getInfo() {
    request.state = 'loading'
    try {
        await APIs.getInfo()
        request.state = 'success'
    } catch(e) {
        request.state = 'error'
    }
}

举了一个血腥飞机空难的例子,原因是控制杆的角度,由左右杆子共同控制。但是左右杆子控制逻辑在一定场景下产生了漏洞。

条款29:(函数入参和结果)宽进严出

条款30:不要在文档中重复信息(注释和ts类型推导)

条款31:空值处理

  • 避免一个值是否为null和另一个值是否为null有隐式关系。
  • 如果有同步null的关系,可以声明一个大对象(完全null、完全非null更好判断)
const getMinMax = (arr: number[]) => {
    let min: number, max: number
    arr.forEach(item => {
        // wrong
        if (!min) {
            min = item
            max = item
        } else {
            min = Math.min(min, item)
            min = Math.min(max, item)
        }
    })
}

条款32:优选接口的联合,而不是联合的接口(组合类型制定更精准的类型

// 标签类型也有类似的思想
interface A { person: Student, address: StudentAddress}
interface B { person: Teacher, address: TeacherAddress}
// good
type C = A | B

// bad: 使用的时候可能产生了Student + TeacherAddress这类组合
type C = {
    person: Student | Teacher,
    address: StudentAddress | TeacherAddress
}
  • 当多个属性是一起存在或者不存在时,包装一个大对象,大对象设置为可选。 这也像把null值推到边界(佩服作者的联想能力

条款33: 收窄类型
比如string,有时可以定义为枚举值,有时是object的key 使用泛型收窄 也是一种手段

条款34:不正确的类型比没有类型更加可怕

条款35:从接口定义中生成类型

条款36:准确的命名类型

条款37:限制结构类型的参数

第五章:和any一起工作

  • 收缩any的作用范围
// good 
const res = returningFoo() // res is Foo
receivePoo(res as any) param is expected Poo
res is Foo

// bad: res is any
const res: any = returningFoo() // res is Foo
receivePoo(res)
res is any
  • any的演变
    有时候变量没有被初始化(推断为any),会因为使用的过程,能演变为更加精确的类型。 如果变量被显式定义为any,则不会有演变的过程。 但是演变规律,书也没给出很具体的规则,所以看过就算了吧

  • any会不断向下渗透传递,使用unknown就能避免这个问题

  • 改变全局变量的类型(Document, Window)

第六章: 类型声明和@types

  • 警惕使用泛型来函数重载时的陷阱
  1. T的类型是什么,返回结果类型就是什么。对于const a = 2,a的类型就是2(let声明就是number)

image.png

  1. 使用条件类型优于重载 但是extends多种类型的话,就比较难办了

image.png

  • 自定义类型以切断包类型依赖(只适用于依赖类型比较小的情况下)

  • 类型测试

  1. 构造helper方法,判断参数类型和自定义类型是否“相等”
function assertType<T>(fn: T){}
assertType<number[]>(map(['john', 'mary'], name => name.length))
  1. 简单对象检查可分配性, const 2可分配给number
  2. 复杂对象的可分配性,子类型可以分配给父类型
// {name: string}[] = {name: string, name: number}[]
assertType<{name: string}[]>(() => ([{name: 'mary', age: 12}])) // ok
  1. 函数方法可分配性,带有更少输入参数可以分配给更多参数的函数类型 解决办法:利用内置方法ReturnType等,分开比较函数入参和函数出参
const g: (x: string) => any = () => 12 // ok
  • 有一些我们习以为常的语法,不是javascript而是ts带来的
  1. class的private,protected, public标识。js private语法应该是#privateField。如下图,ts编译前&编译后

image.png

总结:

本书的目的是指导读者怎么写出合适高效的ts(过于精确和模糊的ts都不好,啰嗦的ts,不可拓展的ts也不好)。这些技巧其实很多都已经融入到了我日常开发的使用中。

本书还穿插着一些讲解让你对ts的概念机制更加了解,因为大多数ts的教学更集中于用法上,能看到这些也是挺宝贵的。是一本挺好的ts入门书。  

对于我来说,有点遗憾的是,高级复杂的ts用法比较少,ts编译,推断原理也没有提及。但是某些章节从作者的讲解思路也能窥探到一点点这部分。  

虽然遗憾,但是作为一个前端开发来说,ts内部的机制确实不要求掌握。看了这本书之后,希望在以后使用ts遇到报错时,能有更加正确的解决思路。