由于 typescript 越来越复杂. 所以特意开多一个篇幅来记入一些比较难的, 和一些到了一定程度需要知道的基础.
主要参考
https://basarat.gitbook.io/typescript/ 高级书
https://jkchao.github.io/typescript-book-chinese/ 高级书中文版
1. 名词术语
Basic Annotation 是基本注解 let a : string
Inline Type Annotation 是内联类型注解 let a : { name: string }
Union 是联合类型 string | number
intersection 是交叉类型 string & number
Tuple 是 元组类型[string, number]
Nullish Coalescing 是 ??
Optional Chaining 是可选链 obj?.name
Rest parameter 是 call(...args : string[])
Spread operator 是 call(...['a', 'b'])
Conditional 是 T extends string ? number : never;
Type Guard 是 类型保护 arg is Foo
Destructuring 是解构 const { name } = { name: 'Derrick' }
Naked type 是 type Naked<T> = T extends ... (没有被任何东西包装)
NotNaked type 是 type NotNaked<T> = { o: T } extends ... // 在对象里面, 算被抱着了
Utility Types 是 Partial, Required, 等这些东西
Mapped types 是 { [P in TKeys] : any }
object literals 是 { name: string }
2. Number Enums as flags
flags 这个概念 c# 也有 (点这里和这里), 经常看到 enum 可以配合 | 来用, 比如 :
PropertyInfo[] attrs = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); //获取attrs
简单理解就是把一个 enum 变成 类似 enum list
typescript 也允许我们做到这点. 点这里
首先定义 enum 的时候需要多写一些号码和符号进去进去 (这里用到的是二进制的移位方法, 我不太懂)
enum Abc { A = 1, B = 1 << 0, C = 1 << 1, D = 1 << 2, E = 1 << 3 }
然后呢,就可以这样子去使用了.
let x: Abc = Abc.A | Abc.B; if(x & Abc.B) { console.log('has B'); } const hasB = !!(x & Abc.B); // true const hasC = !!(x & Abc.C); // false
书里有写它的原理,和如果添加移除,但我目前没有使用到,所以就不研究了.
2.1 扩展特定 enum 方法
通过 namespace
enum Abc { A } namespace Abc { export function print(): string { return 'dada'; } } Abc.print()
3. 理解 target 和 lib 点这里
target es6 的话, lib 就会有 promise 这些东西.
有些时候我们想分开来管理.
target 指的是 output 出来的 js 等级
比如 es5, 那么它就不会有 Promise, Set, Map 之类的.
lib 是我们开发环境用的. 我们当然希望什么都有咯.
所以一个比较合理的做法就是,
target es5 出来的 js 是 es5
lib dom, es6, 开发的时候我们用的 es6 的 js
然后通过 Polyfill 来补上就好了.
4. declare overload method 点这里
没有 overload 的情况下可以这样 declare, 或者用 interface 也可以
type LongHand = {
(a: number): number;
};
type ShortHand = (a: number) => number;
overload 情况下只可以用第一种
type LongHandAllowsOverloadDeclarations = {
(a: number): number;
(a: string): string;
};
5. 兼容性和变体 点这里
兼容性是指一个类型是否可以当另一个类型来使用 (有时候过多的限制会让代码很难写,所以哪怕是强类型我们也经常会要强转之类的, 所以不可以档死了)
对象结构
type IsExtends<T, U> = T extends B ? true : false; type A = { age: number }; type B = { age: number, name: string }; // 属性可以多, 不可以少 type result1 = IsExtends<B, A>; // true
函数参数
type Method = (age: number) => void; type Method2 = () => void; // 参数可以少, 不可以多 type result2 = IsExtends<Method2, Method>; // true
上面都是比较简单的例子,只是数量上的问题
复杂的情况是参数类型,返回类型这些.
比如 2 个方法对比, 第 1 个参数类型是子类, 第 2 个是父类, 这个能兼容吗 ?
说到这里可能就需要引入变体的概念了
首先讲一下定义
A ≼ B
意味着 A
是 B
的子类型。
A → B
指的是以 A
为参数类型,以 B
为返回值类型的函数类型。
协变(Covariant):只在同一个方向;
逆变(Contravariant):只在相反的方向;
双向协变(Bivariant):包括同一个方向和不同方向 (通常这个是不安全的,但有时候没有办法就比如强转一样)
不变(Invariant):如果类型不完全相同,则它们是不兼容的。
来一个函数类型的例子, 2个函数是否可兼容依据参数类型和返回类型
灰狗 ≼ 狗 ≼ 动物
有一个方法是 狗 → 狗
那么怎样的方法是和它兼容的 ?
1. 灰狗 → 灰狗
2. 灰狗 → 动物
3. 动物 → 动物
4. 动物 → 灰狗 (正解)
参数类型默认情况下是双向的 (开启 strictFunctionTypes 之后就换成逆变),也就是说,只要是父类或者子类都 ok,返回类型是协变的,所以 a extends b 的话, a 的返回子类是 ok 的.
没开启 strictFunctionTypes 的时候
type methodParent = (p: Parent) => Parent; type methodChild = (c: Child) => Child; type result = IsExtends<methodChild, methodParent>; // true
开启之后就不行了, 改成父类就 ok
type methodParent = (p: Parent) => Parent; type methodChild = (c: Parent) => Child; type result = IsExtends<methodChild, methodParent>; // true
为什么参数得是逆变呢 ?
想想看,A 函数, B 函数,B 要能取代 A, 凡是 A 能用的地方 B 要能用.
参数是一种限制, B 的参数如果是协变的话,那就是参数是灰狗了, 一个限制是灰狗参数的方法, 怎么可能替代的了, 只限制在狗参数的函数呢.
所以要记得,
协变就是说要求 A 类的地方可以用 A 或 A 的子类来替换 (不可以用 A 的父类),
逆变就是要求 A 类的地方可以用 A 或 A 的父类来替换 (不可以用 A 的子类).
双向就是上面 2 个都可以
不变就是 A 只能用 A 不可以是其它的
6. Conditional, Infer
refer : https://fettblog.eu/typescript-union-to-intersection/ (联合类型转交叉类型)
当 contional 遇到 union
如果是 naked 的话会被拆成多个来解析, not naked 的话就是把 T 传过去用而已
type Naked<T> = T extends any ? { name: T } : never; type result = Naked<'a' | 'b'>; // { name: 'a' } | { name: 'b' } type NotNaked<T> = T[] extends any ? { name: T } : never; type result2 = NotNaked<'a' | 'b'>; // { name: 'a' | 'b' }
Infer 有个特色就是如果 infer 是用在参数类型上, 由于是逆变 position, 最终出来的结果会变成交叉类型.
如果不是逆变的话,出来的结果会是联合类型
// 这是转为联合类型 type Foo<T> = T extends { a: infer U, b: infer U } ? U : never; type T10 = Foo<{ a: string, b: string }>; // string type T11 = Foo<{ a: string, b: number }>; // string | number // 这是转为交叉类型 type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never; type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number