这是我参与「第四届青训营 」笔记创作活动的第15天
「前言」
在之前的文章中,我们讨论过 ts 基础类型 和 ts 高级类型,似乎类型的玩法就这么多
typescript 的类型远不止于此
「前置知识」
字面量类型
type T = 'a';、type T = 'a' | 'b'; 这种类型,有一种特殊的名称叫做 字面量类型,顾名思义,类型具有固定的值,可以为 字符串、布尔值、数字、对象、函数、甚至泛型、接口,只要是一个具有确切的固定值的类型都可以作为 字面量类型
type IBookItem
= { author: string }
& ({
type: 'computer';
range: string;
}
| {
type: 'history';
theme: string;
})
在上一篇的文章中,我们用来他来实现类型保护,其中这是运用了字面量类型可穷尽的特点: typescript(7)- 高级类型 | 青训营笔记 - 掘金 (juejin.cn),这里我们不过多的探讨类型保护
这种类型与 枚举类型十分类似,他们都有一个显著地特点:可遍历,可穷尽
type IBookItem
= { author: string }
& ({
type: 'computer';
range: string;
}
| {
type: 'history';
theme: string;
})
function test(book: IBookItem) {
if (book.type! == 'computer' && book.type !== 'history') { } // error
}
对于可穷尽的类型,在分支判断的类型校验十分严格
错误原因:
- 如果
book.type不是computer,满足条件,则不会进入分支内部 - 如果是
computer,那一定不是可穷尽类型中的其他值,所以后续的判断是多于的
「ts 标准库预定义的类型」
TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:
Exclude<T, U>-- 从T中剔除可以赋值给U的类型。Extract<T, U>-- 提取T中可以赋值给U的类型。NonNullable<T>-- 从T中剔除null和undefined。ReturnType<T>-- 获取函数返回值类型。InstanceType<T>-- 获取构造函数类型的实例类型。
我们来讨论他们的用法和原理实现
Exclude
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'd'>; // 'b' | 'c'
type T2 = Exclude<'a' | 'd', 'a' | 'b' | 'c'>; // 'd'
类似于数学中的 差集
原理
type Exclude<T, U> = T extends U ? never : T;
看到这里你是不是也想到了 JavaScript 中的三元表达式。ts 对这种类型判断叫做 条件类型
type IsNumber<T> = T extends number ? true : false;
type IsNumber<T> = T extends number ? true : false;
type T1 = IsNumber<number> // true
type T2 = IsNumber<1> // true
type T3 = IsNumber<string> // false
type T4 = IsNumber<any> // boolean
type T5 = IsNumber<unknown> // false
type T6 = IsNumber<never> // never
type T7 = IsNumber<number | string> // boolean
type T8 = IsNumber<number & string> // never
对于联合类型,交叉类型这些复合类型使用类型判断等效于
// 等效 T7
type T9 = IsNumber<number> | IsNumber<string>
在运算过程中会被拆解分支
再看下面的例子
type Naked<T> = T extends boolean ? true : false;
type WrappedTuple<T> = [T] extends [boolean] ? true : false;
type WrappedArray<T> = T[] extends boolean[] ? true : false;
type WrappedPromise<T> = Promise<T> extends Promise<boolean> ? true : false;
type T0 = Naked<number | boolean>; // boolean
type T1 = WrappedTuple<number | boolean>; // false
type T2 = WrappedArray<number | boolean>; // false
type T3 = WrappedPromise<number | boolean>; // false
由以上结果可知,如果条件类型中的类型参数 T 被包装过,所以在运算过程中就不会被分解成多个分支。
了解以上特点,现在我们再来看这段代码
type Exclude<T, U> = T extends U ? never : T;
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'd'>; // 'b' | 'c'
type T2 = Exclude<'a' | 'd', 'a' | 'b' | 'c'>; // 'd'
extends 右边的泛型不会拆分运算步骤,也就是相当于三次 Exclude 结果的联合类型
Extract
type Extract<T, U> = T extends U ? T : never;
与 Exclude 十分类似,只是交换了结果
NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;