NonNullable是一个比较简单的工具类型,它接受一个范型 T 作为参数,如果 T 是null或undefined,则返回never,否则返回 T 本身。NonNullable 早些年的实现如下所示:
ts
typeNonNullable = Textendsnull|undefined?never: T;
不记得 extends 关键字的可以回顾一下《ts 类型体操之内置工具类型(上)》的内容。extends 实际执行时是对联合类型T里的每一个元素分别进行条件判断。所以 NonNullable 通常也是用于联合类型操作,剔除联合类型中的 null 和 undefined:
ts
typeT0=NonNullable;// string | numbertypeT1=NonNullable;// string[]
不过在typescript 4.8后,NonNullable 被重写了,现在它的实现如下:
ts
typeNonNullable = T & {};
这个实现其实更简单,它利用了类型系统中的交叉(&)操作符,将 T 和一个空对象类型{}进行合并,从而剔除了 T 中的null和undefined。这里提几个八股小知识点:{}是除了 undefined 和 null 之外,所有类型的父类型。 所以{}和 undefined 或 null 的交叉类型是 never,而且其余的类型和{}交叉的结果是其本身。
以NonNullable<number | undefined>为例:
NonNullable<number | undefined>=>(number | undefined) & {} => number=>(number & {} ) | (undefined & {})=>number | never=>number
再补充一个八股 unknown 事实上等价于{} | undefined | null, 所以NonNullable<unknown>等于{},但是NonNullable<any>等于any。
Awaited<T>
Awaited 类型用于获取 Promise 的返回值类型。例如:
ts
typeT0=Awaited>;// stringtypeT1=Awaited>>;// numbertypeT2=Awaited>;// number | boolean
Awaited “方法”还是有点难度的:
该类型需要支持递归:它需要将嵌套的 Promise 的类型展开,直至得到 Promise 的最终返回值类型。
递归的结束条件是:对非 PromiseLike 的类型(没有 then 方法的对象类型)返回 never。
我们逐行解释上面的实现:
T extends null | undefined:如果 T 是 null 或者 undefined,则直接返回 T。这个判断是为了处理非严格模式下,null 和 undefined 的情况。在严格模式下,null 和 undefined 不能作为合法的 Promise。
T extends object & { then(onfulfilled: infer F, ...args: infer _): any }:这行很长,中心思想是:如果 T 是一个对象,并且该对象具有 then 方法,那么我们就可以认为它是一个 PromiseLike 类型。这里我们用到了infer关键字,它表示在类型推导过程中,将 then 方法的第一个参数类型提取出来,赋值给 F。若 T 不是 PromiseLike 类型,则直接返回 T。
F extends (value: infer V, ...args: infer _) => any:F由上一步推断得到,如果 then 方法的第一个参数是函数类型,那么我们就可以认为它是一个 Promise。我们再次用到了infer关键字,将 then 方法的第一个参数的类型提取出来,赋值给 V。若 F 不是函数类型,则不是一个合法的 Promise,直接返回 never。
Awaited<V>:递归地展开 V,直到 V 不再是 PromiseLike 类型为止。
原始版本虽然能看得懂,但是太麻烦了。我们自实现type challenge这道MyAwaited的时候可以用下面一个简化版代替:
ts
typeAwaited = TextendsPromiseLike ?Awaited : T;
PromiseLike<T>也是一个内置接口,表示一个具有 then 方法的对象类型——Promise 的鸭子类型。大家可以直接用。
PromiseLike<infer R>:表示将 PromiseLike 类型中的泛型参数 R 提取出来,然后递归调用 Awaited,直到递归到非 PromiseLike 的类型。这里有个知识点:infer 甚至可以推断出接口中的范型参数。比如Promise<string>,可以直接推断出 string
extends ? (...) : T: 我们之前提到过:extends 会遍历联合类型。对于boolean | Promise<number>这样的 case,extends 会分别对boolean和Promise<number>进行判断,最终返回 boolean | number。
chopard-shs.fdcpx.net
chopard-bjs.fdcpx.net
chopard-shenzhen.biaoshouhou.cn
chopard-gzs.biaoshouhou.cn
chopard-shs.audemarsweixiu.com
chopard-bjs.audemarsweixiu.com
chopard-shenzhen.hidcwatch.com
chopard-gzs.hidcwatch.com
chopard-shs.fjfsx.com
chopard-bjs.fjfsx.com
chopard-shenzhen.hntwx.cn
chopard-gzs.hntwx.cn
chopard-shs.hx626.com
chopard-bjs.hx626.com
chopard-shenzhen.watchjwf.cn
chopard-gzs.watchjwf.cn
chopard-shs.shjshdzb.com
chopard-bjs.shjshdzb.com
chopard-shenzhen.shmwatch.cn
chopard-gzs.shmwatch.cn
chopard-shs.gyjshd.com
chopard-bjs.gyjshd.com
chopard-shenzhen.zhcxb.cn
chopard-gzs.zhcxb.cn
chopard-shenzhen.jshdvip.com
chopard-gzs.jshdvip.com
chopard-shs.gyjshdzb.com
chopard-bjs.gyjshdzb.com
chopard-sys.jhpwd.cn
chopard-zzs.jhpwd.cn
chopard-shenzhen.wzjshd.com
chopard-gzs.wzjshd.com
chopard-shs.jsfltime.com
chopard-bjs.jsfltime.com
chopard-sys.watchwb.cn
chopard-css.watchwb.cn
chopard-shenzhen.watch-hdl.com
chopard-gzs.watch-hdl.com
chopard-shs.watchhdlb.cn
chopard-bjs.watchhdlb.cn
chopard-whs.watchhdli.cn
chopard-nbs.watchhdli.cn
chopard-shenzhen.watchrhf.cn
chopard-gzs.watchrhf.cn
chopard-shs.watchec.cn
chopard-bjs.watchec.cn
chopard-whs.watchda.cn
chopard-xms.watchda.cn
chopard-hzs.csjshd.com
chopard-njs.csjshd.com
NoInfer<Type>
NoInfer<Type>:用于防止 TypeScript 从泛型函数内部推断类型。它是一个固有类型,没有更底层的实现:
ts
// lib.es5.d.tstypeNoInfer = intrinsic;
它是 TypeScript 5.4 刚推出的一个内置类型,所以我们正好看看如何在某些情况下使用它来改进 TypeScript 的推理行为。如下例所示:通常的情况下编译器是可以从函数入惨里推断出 result 类型是'hello'
ts
constreturnWhatIPassedIn = (value: T) =>value;constresult =returnWhatIPassedIn('hello');//const result: 'hello'
但如果我们用NoInfer<T>来包装 value, NoInfer 使 value 无法成为有效推断来源 T。因此如下 result 被推断为 unknown。
ts
constreturnWhatIPassedIn = (value: NoInfer<T>) =>value;constresult =returnWhatIPassedIn('hello');//const result: unknown
我们需要明确提供范型才能获得 returnWhatIPassedIn 的返回类型:
ts
constresult = returnWhatIPassedIn<'hello'>('hello');// const result: "hello"
NoInfer 要解决什么问题呢?一个很好的例子是创建有限状态机 (FSM) 的函数。FSM 有一个 initial 状态和一个列表 states。initial 状态必须是 states 之一。
ts
declarefunctioncreateFSM(config: {initial:TState;states:TState[];}):TState;
请注意,TypeScript 可以从两个可能的地方推断类型:initial 和 states。如下所示:example 的类型推断为"not-allowed" | "open" | "closed"。显然,正确的类型推断应该是状态机只有"open" | "closed"这两种类型,而initial = "not-allowed"要抛错。