先说说一个常见的问题,你有如下的一个数组:
type NullableNumber = number | null | undefined;const nullableNumbers: ReadonlyArray<NullableNumber> = [1, 2, 3, null, undefined];
该数据中既有 number
,又有 null
和 undefined
。
现在,你想把该数组中的 number
都过滤出来形成一个新的数组,所以你写下了如下代码:
const numbers: ReadonlyArray<number> = nullableNumbers .filter(n => n !== null && n !== undefined);
奇怪的是,编辑器报了一条错误,提示类型不匹配:
尽管它在运行时是正确的,但 TypeScript 没能理解你的代码。
这个问题,我们可以用 type guard 来解决,即给 filter
函数声明返回值的类型:
const numbers: ReadonlyArray<number> = nullableNumbers .filter((n): n is number => n !== null && n !== undefined);
缺点是,这里的 n is number
是写死的。其实,我们也可以写一个更加通用的 nonNullable filter
函数:
// 通用的 nonnullable filter 函数const isNonNullable = <T,>(t: T): t is NonNullable<T> => t !== null && t !== undefined;// 这样使用const nonNullableNumbers: ReadonlyArray<number> = nullableNumbers.filter(isNonNullable);
注意 <T,>
里的逗号,没有它,TypeScript 会误以为这是个 React Tag ,导致解析错误:
写到这里,你可能会好奇 NonNullable
是什么玩意。
理解 NonNullable
查看 TypeScript 文档,NonNullable
是 Utility Types 的一种。官方对它的解释是 NonNullable<Type>
会将 null
和 undefined
从 Type
中排除掉,由剩余类型组成一个新的类型。
所以,如果我们有:
type Foo = number | null | undefined;
那么 NonNullable<Foo>
就会和 number
就是同一类型:
你可能会好奇 NonNullable
是如何定义的。比如,我可能会把它定义为:
type MyNonNullable<T> = Exclude<T, null | undefined>
这样写没问题,但官方给的定义是这样的:
type NonNullable<T> = T extends null | undefined ? never : T
它把我搞蒙了,因为按这个定义,NonNullable<number | null | undefined>
应该返回 never
,因为 number | null | undefined
extends null | undefined
显然是 true
。你说是不是?
但实际上,这个定义不能这么理解:不能把 number | null | undefined
看成一个整体,而是一个由这3种类型构成的一个类型(可以是 number
,也可以是其它2个),而 NonNullable
的作用,就是从这些类型中排除 null
和 undefined
类型,用剩下的类型组成新的类型。
文章参考
官方 NonNullable 文档[1] Stackoverflow 问答[2] Medium 博客:Nullability in TypeScript[3]
参考资料
官方 NonNullable 文档: https://www.typescriptlang.org/docs/handbook/utility-types.html?#nonnullabletype
[2]Stackoverflow 问答: How to understand NonNullable in typescript?: https://stackoverflow.com/questions/65147309/how-to-understand-nonnullable-in-typescript
[3]Medium 博客:Nullability in TypeScript: https://gregoryppabian.medium.com/nullability-in-typescript-60be8c5a6d87