1. any
2. unknown
3. void
4. never
never表示永不存在值的类型,什么情况下变量会永远不存在值呢?因为通常情况下,变量一旦申明了,就会被分配值,即使没有特别指定,也会被初始化为undefined,同样一个函数即使有个写返回值,也会默认返回undefined,也不是真正的不存在返回值:
let foo;console.log(typeof foo); // undefinedfunction bar() {};console.log(typeof bar()); // undefined
其实确实有一些情形,值会永不存在,比如,从程序运行的维度讲,如果一个函数执行时抛出了异常,那么这个函数便永远不会有值了(因为抛出异常会直接中断程序运行,这样程序就运行不到返回值那一步了,即具有不可达的终点,也就永不存在返回了):
function err(msg: string): never {throw new Error(msg);};// 有机会到达终点的函数也算存在返回值,编译会报错function err1(): never {if (Math.randow > 0.5) {throw new Error('message');};};
还有一种极端情况也比较类似,就是函数中执行无限循环的代码(死循环),这样也同样使得程序永远无法运行到函数返回值那一步,永不存在返回:
function loopForever(): never {while (true) {};}
所以never一般用来定义:
- 一个总是会抛出错误的函数
- 一个从来不会有返回值的函数
- 一个在条件类型中永远不会进入的分支
- 一个在
promise中reject分支中返回值的类型const p = Promise.reject('foo') // const p: Promise<never>
4.1 特性
**never**是所有类型的子类型,意思就是它可以赋值给任何类型(前提是配置了"strictNullChecks": false,否则检查不通过): ```typescript function fn(input: string) {}
declare let myNever: never fn(myNever) // ✅ 允许 never 类型参数
2. **没有任何类型是**`**never**`**的子类型,除了**`**never**`**自身,即除了**`**never**`**任何类型都不能赋值给**`**never**`**类型的变量**(如果配置了`"strictNullChecks": true`,`never`也不能赋值给`never`):```typescriptfunction fn(input: never) {}declare let myNever: neverfn(myNever) // ✅ 只允许 never 类型参数// 传其他类型的参数(或者不传)都会引起类型错误:fn() // ❌ An argument for 'input' was not provided.fn(1) // ❌ Argument of type 'number' is not assignable to parameter of type 'never'.fn('foo') // ❌ Argument of type 'string' is not assignable to parameter of type 'never'.// 哪怕参数是 `any` 类型也不可以declare let myAny: anyfn(myAny) // ❌ Argument of type 'any' is not assignable to parameter of type 'never'.
**never**在联合类型中不起作用,类似于0在加法运算中没有意义一样:type Res = never | string // string
**never**在交叉类型中会覆盖其他类型,类似于0在乘法中会使结果为0一样:type Res = never & string // never
4.2 用法
确保对
**switch**和**if-else**语句中的所有条件都做处理 ```typescript type Color = ‘red’ | ‘green’
function getColorName(color: Color): string { switch(color) { case ‘red’: return ‘is red’; // 这里 color 被收窄为 red case ‘green’: return ‘is green’; // 这里 color 被收窄为 green default: const exhaustiveCheck: never = val // ✅ color 在这里是 never return ‘no match color’ } }
在`switch`当中判断`color`,`TS`是可以收窄类型的。上面例子中的`default`里面我们把被收窄为`never`的`color`赋值给一个显式声明为`never`的变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了`Color`的类型:```typescripttype Color = 'red' | 'green' | 'blue'
然而他忘记了在getColorName里面加上针对blue的处理逻辑,这个时候在default里面color会被收窄为blue,导致无法赋值给never,产生一个编译错误。所以通过这个办法,你可以确保getColorName总是穷尽(exhaust)了所有Color的可能类型。
如果你只想在运行时来做安全或者详细的检查,可以在default里面执行一个返回never的函数:
function unknownColor(x: never): never {throw new Error("unknown color");}type Color = 'red' | 'green'function getColorName(color: Color): string {switch(color) {case 'red':return 'is red';case 'green':return 'is green';default:// ✅ unknownColor 返回的是 never 类型,never 类型是所有类型的子类型,所以 ts 会校验通过return unknownColor(color);}}
- 禁用结构化类型中的一部分
假设我们有一个函数,它接受一个VariantA类型或VariantB类型的参数。但是,不能接受一个同时包含两种类型所有属性的类型,即两种类型的一个子类型。
我们可以利用一个联合类型VariantA | VariantB来作为参数。然而,由于TypeScript中的类型兼容性是基于结构子类型的,所以允许向函数传递一个属性多于参数类型的对象类型(除非你传递对象字面量)。
type VariantA = {a: string,}type VariantB = {b: number,}declare function fn(arg: VariantA | VariantB): voidconst input = {a: 'foo', b: 123 }fn(input) // 这违背了我们的设计,但是 TypeScript 不会报警
以上的代码片段中,TypeScript不会给出类型错误。但使用never后,我们就可以将类型结构中的部分给禁用掉,从而阻止用户向其传递包含两种类型属性的对象:
type VariantA = {a: string;b?: never;}type VariantB = {b: number;a?: never;}declare function fn(arg: VariantA | VariantB): void;const input = {a: 'foo', b: 123 };fn(input) // ❌ Types of property 'a' are incompatible
- 在联合类型中做过滤
当用于联合类型时,never类型会自动删除。换句话说,在联合类型中,never类型没有用处。
当我们编写工具类用于根据某些标准选择来自联合类型的某些成员时,never类型的 “无用” 性恰恰成为最适合放在else分支的类型。
假设我们有一个工具类ExtractTypeByName,用于在联合类型中找出name属性为foo的类型成员,并将其他的成员过滤掉:
type Foo = {name: 'foo';id: number;}type Bar = {name: 'bar';id: number;}type All = Foo | Bar;type ExtractTypeByName<T, G> = T extends {name: G} ? T : never;type ExtractedType = ExtractTypeByName<All, 'foo'>;
让我们看看它具体是如何工作的,以下是TypeScript如何一步一步得到类型结果的:
- 条件类型首先分发成联合类型
```typescript
type ExtractedType = ExtractTypeByName
type ExtractedType = ExtractTypeByName
type ExtractedType = ExtractTypeByName
- 将类型实现和赋值拆分```typescripttype ExtractedType = Foo extends {name: 'foo'} ? Foo : never| Bar extends {name: 'foo'} ? Bar : nevertype ExtractedType = Foo | never
- 将
never从联合类型中移除 ```typescript type ExtractedType = Foo | never
type ExtractedType = Foo
4. **表示不兼容类型的交叉类型**这一点感觉上更像是`TypeScript`语言的行为特征,而不是一个never类型的用例。然而,这对于理解一些神秘的错误消息是至关重要的。<br />任何不兼容的交叉类型都是`never`类型:```typescripttype Res = number & string // never
同时,任何类型与never类型的交叉类型也是never类型:
type Res = number & never // never
对于对象类型,情况会有些复杂…
在交叉对象类型时,根据属性的类型是否为可辨别属性(字面量类型或字面量类型的联合类型),可能会也可能不会将整个类型简化为never类型。
此例中,只有name属性会推导为never类型,因为string和number不是可辨别属性:
type Foo = {name: string;age: number;}type Bar = {name: number;age: number;}type Baz = Foo & Bar // { name: never, age: number }
而在下面这个例子中,整个Baz类型会推导为never类型,因为boolean类型是可辨别属性(类型boolean就是true|false的联合类型):
type Foo = {name: boolean;age: number;}type Bar = {name: number,age: number}type Baz = Foo & Bar // never
- 用于表示理论上无法到达的条件分支
当我们在条件类型中使用infer创建一个类型变量时,我们必须为每个infer关键字创建else分支:
type A = 'foo';type B = A extends infer C ? (C extends 'foo'? true : false // 在此表达式中,C 等同于 A) : never // 这个分支永远不会执行,但是我们也不能不写它
