You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionfetchData(): Promise<{price: number}>{returnnewPromise((resolve)=>{setTimeout(()=>{resolve({price: 100});},Math.random()*5000);});}asyncfunctionfetchPriceWithTimeout(tickerSymbol: string): Promise<number>{conststock=awaitPromise.race([fetchData(),// returns `Promise<{ price: number }>`asyncTimeout(3000),// returns `Promise<never>`]);// stock is now of type `{ price: number }`returnstock.price;}
/** * Exclude null and undefined from T */typeNonNullable<T>=Textendsnull|undefined ? never : T;
这里的条件类型对于传入泛型是联合类型的时候,会将判断类型分发到每一个到联合类型。
// if T = A | BTextendsU ? X : T==(AextendsU ? X : A)|(BextendsU ? X : B)
所以 NonNullable<string | null>的解析逐层如下:
NonNullable<string|null>// The conditional distributes over each branch in `string | null`.==(stringextendsnull|undefined ? never : string)|(nullextendsnull|undefined ? never : null)// The conditional in each union branch is resolved.==string|never// `never` factors out of the resulting union type.==string
functionfunc(value: unknown){// @ts-expect-error: Object is of type 'unknown'.value.test('abc');assertIsRegExp(value);// %inferred-type: RegExpvalue;value.test('abc');// OK}/** An assertion function */functionassertIsRegExp(arg: unknown): asserts arg is RegExp{if(!(arginstanceofRegExp)){thrownewTypeError('Not a RegExp: '+arg);}}
// 1)typeofNaN==='number'// true// 🤔 ("not a number" is a "number"...)// 2)isNaN('1')// false// 🤔 the string '1' is not-"not a number"... so it's a number??// 3)isNaN('one')// true// 🤔 'one' is NaN but `NaN === 'one'` is false...
description: 最近面试时被提问到 TypeScript 相关的知识,其中一个问题就是:什么时候应该在 TypeScript 中使用 never 和 unknown。
cover: https://de4965e.webp.li/blog-images/2024/09/1d13fb693096a714f5a96a44fc567a22.png
最近面试时被提问到 TypeScript 相关的知识,其中一个问题就是:什么时候应该在 TypeScript 中使用
never
和unknown
。笔者觉得自己没答好,于是再次学习了相关知识分享下来。本文不是 TypeScript types 的科普,所以别着急,只需一步一个脚印前进即可。
TypeScript 类型浅谈
首先,我们要明确什么是 TypeScript 的类型。
简单说,
string
类型即 TypeScript 中字符串变量的类型集合。同理,number
是数字变量的类型集合,当我们写下如下代码时:联合类型
string | number
应运而生,这个联合类型就是字符串类型集合和数字类型集合的并集。我们在 TypeScript 中可以把字符串类型或数字类型的变量赋值给
stringOrNumber
,除此之外还能把什么类型的变量赋值给stringOrNumber
呢?答案是:
never
any
never
类型是一个空集,它处于类型系统的最内层。当我们定一个变量的类型是
unknown
时,我们实际上需要的就是一个未知类型的值,举个例子:unknown
是所有类型集合的超集,never
则是所有类型的子集(bottom type)。never 类型浅析
来点代码,再谈其他:
上述代码是一个异步超时的函数,其返回值类型是
Promise<never>
,接着我们继续实现一个支持超时的请求函数:fetchData
是模拟的数据请求函数,实际开发中可以是某个接口请求,TypeScript 编译器将race
函数的返回值类型视为Promise<{price: number}> | Promise<never>
即stock
的类型为{price: number}
,这就是never
类型的使用场景实例。再来一个代码例子(来源于网络):
上述示例使用了
never
类型来缩窄条件类型的结果。在type Arguments<T>
中,如果传入的泛型T
是一个函数,则将从其参数的类型生成一个推断类型A
作为满足条件时的类型。也就是说,如果我们传入的不是函数,那么TypeScript 编译器最后就会得到never
类型兜底。于是乎在
time
函数定义的时候就可以使用这两个带泛型的条件类型定义,最终让time
函数能够通过传入的函数add
的类型来推导最终的返回值类型:sum
是number
类型。我们可以在官方源代码中看到类似的类型声明:
这里的条件类型对于传入泛型是联合类型的时候,会将判断类型分发到每一个到联合类型。
所以
NonNullable<string | null>
的解析逐层如下:最终编译器识别其为
string
类型。unknown 类型浅析
任何类型的值都可以赋值给
unknown
类型的变量,因此我们可以在考虑使用any
的时候优先考虑unknown
,一旦我们选择使用any
,也就意味着我们主动关闭了TypeScript
的类型安全保护。好了,下面来看一个代码示例:
这是一个美观的打印输出函数,在调用时可以传入任意类型的参数。但是在传入后我们需要手动去缩窄类型,才能在逻辑内部使用特定类型变量的方法。
此外,如果数据来源不明确时,也可以促使开发者进行必要的类型验证和类型检查,减少运行时错误。
另外,笔者还见到了如下所示的类型守卫示例,稍微再分享一下:
对于未知类型的参数,可以使用
assertIsRegExp
函数来断言类型,在其内部如果传入的参数符合类型的话,就不会抛出错误。在func
下半部分编译器则收到了断言信号,就可以直接使用test
方法了。这种写法跟
if/else
的区别在于开发者写if/else
时需要显式地书写类型不在预期时的代码,否则不满足类型时可能缺少运行信息,导致正确类型条件下的代码不执行。我们没法确保开发者一定会写类型不在预期时的处理逻辑,因此使用类型断言守卫也是一种可行之策,在运行时将会报错,如果前端项目运行在有效的监控体系下,也能收到错误信息以便于排查。
如果你喜欢这种类型守卫,可以使用elierotenberg/typed-assert: A typesafe TS assertion library这样的库来减少工作量,它帮我们写了
assertIsRegExp
这样的函数。如果你喜欢显式的
if/else
条件判断,也可以使用mesqueeb/is-what: JS type check (TypeScript supported) functions like `isPlainObject() isArray()` etc. A simple & small integration.这样的超轻量类型检查库。如果你不想引入其他库,那么只能自己写类型判断代码了,但是自己写的话可要注意了,来看看如下代码:
这只是一个
NaN
特例,但这就是 JavaScript。好了,下次见。
参考
The text was updated successfully, but these errors were encountered: