「TypeScript 奇淫巧技」never 的隐藏特性!
先来复习一下 never
的基本概念。
程序语言的设计确实应该存在一个底部类型的概念,当你在分析代码流的时候,这会是一个理所当然存在的类型。TypeScript 就是这样一种分析代码流的语言(😎),因此它需要一个可靠的,代表永远不会发生的类型。
never
类型是 TypeScript 中的底层类型。它自然被分配的一些例子:
- 一个从来不会有返回值的函数(如:如果函数内含有
while(true) {}
); - 一个总是会抛出错误的函数(如:
function foo() { throw new Error('Not Implemented') }
,foo
的返回类型是never
);
你也可以将它用做类型注解:
let foo: never; // ok
但是,never
类型仅能被赋值给另外一个 never
:
let foo: never = 123; // Error: number 类型不能赋值给 never 类型
// ok, 作为函数返回类型的 never
let bar: never = (() => {
throw new Error('Throw my hands in the air like I just dont care');
})();
细节一
never
是所有类型的子类型,也就是说never
类型可以赋给所有类型,但是反之任何类型都不能赋给 never
:
let a: never = (function () {
throw new Error('Not Implemented')
})();
let b: number = a;
上面的例子里,虽然 let b
这行的代码永远也不会执行到,但是这样确实不会触发类型错误,你可能会问这有任何意义么?
事实上是有的,never
是所有类型的子类型也就是说所有类型都是 never
的逆变,众所周知涉及函数兼容性的时候,参数是逆变的,所以使用 ...arg: never[]
作为参数可以做到对任何参数的兼容:
type MyReturnType<T> = T extends (...args: never[]) => infer Return
? Return
: never;
细节二
对 never
进行高级操作的时候产生的衍生类型可能还是 never
,这个我没找到具体的文档说明,在实践中发现了这几个:
type stillNever = [1, ...never];
// 对 never 进行展开;
type stillNever = never & 1;
// 对 never 进行并集;
type Never = never;
type stillNever = `${Never}`;
// 在模板字符串中使用 never;
在泛型编程中使用递归的时候一定要避免返回
never
,可能会和你使用的泛型操作符产生上面的副作用。
细节三
never
在碰上联合类型的时候会自动砍掉自己:
type One = never | 1;
// 1
这个特性有的时候还挺好用的,其实我感觉 never
本身也可以看做一种联合类型,就是应为这个特性才会产生下面的细节四。
细节四
使用 never
作为泛型参数的时候产生了条件类型的分发机制,生成的类型不论条件类型返回值如何都是 never
:
type IsNumber<T> = T extends number ? true : false;
type stillNever = IsNumber<never>;
这个特性只针对产生条件分发的情况,使用 never
作为泛型参数本身不会产生问题:
type OrOne<T> = T | 1;
type A = OrOne<never>;
// 1
所以我们想判断一个类型是不是 never
类型的时候要记得阻止条件分发机制:
export type IsNever<T> = [T] extends [never] ? true : false;
与void
的差异
一旦有人告诉你,never
表示一个从来不会优雅的返回的函数时,你可能马上就会想到与此类似的 void
,然而实际上,void
表示没有任何类型,never
表示永远不存在的值的类型。
当一个函数返回空值时,它的返回值为 void
类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never
类型。void
类型可以被赋值(在 strictNullChecking
为 false
时),但是除了 never
本身以外,其他任何类型不能赋值给 never
。
转载自:https://juejin.cn/post/7051508207822323725