你以为用了TypeScript就万事大吉, 处处有类型检查了? 然而天不遂人愿, 这篇少讲TS技巧, 而主要来说说其中的一些要注意的地方.
type alias 做不到而 interface 能
一般而言,我们会选择使用 type 关键字而不是 interface, 毕竟前者更简单, 但假设是下面这种情况呢, type alias 仍然更简洁, 但其中的不妥恐怕很多朋友第一时间发现不了
interface InjectionKey1<T> extends Symbol { }
type InjectionKey2<T> = Symbol
let k1: InjectionKey1<string> = Symbol()
// ^? let k1: InjectionKey1<string>
let k2: InjectionKey2<string> = Symbol()
// ^? let k2: Symbol
type 关键字是 type alias, 别名而已, 他所代表仅仅是等号右边的定义, 此处泛型参数没有参与到右边的类型检查, 就无了.
使用 interface 这一特性, 可以让我们获得充分的类型信息而不必用添加额外字段这种污染类型定义的操作来完成. vue 的 InjectionKey 类型便是如此定义的.
接口约束了个寂寞
定义接口, 实现类, 一套操作行云流水, 然而可能中间你的类型信息就又丢了.
interface IRequest<P, R> {
payload: P
}
interface IRequestHandler<T extends IRequest<unknown, unknown>> {
handle: T extends IRequest<infer P, infer R> ? (payload: P) => R : never
}
class Req implements IRequest<string, string> {
payload: string
constructor(payload: string) {
this.payload = payload
}
}
class Handler1 implements IRequestHandler<Req> {
// valid
handle(payload: string){
return
}
}
TS对类的支持属实一般, github 上还有更多问题. 就这里的情况, 不要把类当作类型参数传递就好了
class Handler2 implements IRequestHandler<IRequest<string, string>> {
// invalid
handle(payload: string){
return
}
}
反直觉的union
const example1: { A?: string } = { A: '', B: 12 } // invalid
const example2: { A?: string } & { B: number; C: string } = { A: '', B: 12 } // invalid
const example3: { A?: string } | ({ A?: string } & { B: number; C: string }) = { A: '' , C:'true'} // valid
具体原因可以看上面的两个issue, 这里只提这一情况会导致的问题
对于 type AB = A | B , 记三个类型对应集合为 , 则 , 也就是约束被放宽了, 能否取等号却要取决于联合的两个类型是否有属性能作为 "discrminator", 心智负担+1