TypeScript 中的 Infer,强大又强大
让我们来谈谈infer
TypeScript 中的关键字。我相信很多开发者都对它感到困惑infer
。我什么时候应该使用它?它是如何工作的?它的作用是什么?
我们先从简单的事情开始。接下来的案例将帮助我们理解 的概念infer
。好的,请看下面的代码:
let a = 10;
TypeScript 知道一种类型a
——数字。这使得开发人员不必到处都写这种类型。TypeScript 会处理好这个问题。顺便说一下,如果要用到的话,会用到另一种类型const
。自己尝试一下吧。我在评论区等着你的解释。这是一个与读者的小小互动。
好的,我们刚刚发现,TypeScript 非常智能。如果一个值符合特定条件,TypeScript 就能判断它的类型。就像这样:
if (typeof a === 'number') {
return number;
} else {
return ???;
}
(什么???
意思?)遇到这种never
类型了。看起来没什么用,谁会用呢?在 TypeScript 中,never
它被视为无值类型。我们有没有不返回值的函数(undefined 是正确的值)?顺便说一下,我们可以创建这样的函数。例如:
function foo() {
throw new Error('error');
}
检查的返回类型foo
。
其实never
类型并非毫无用处。我们回到那个数字的例子。我们可以never
在 else 条件中使用:
if (typeof a === 'number') {
return number;
} else {
return never;
}
// or
typeof a === 'number' ? number : never;
当然,TypeScript 的代码库里没有这样的代码。这只是 TypeScript 工作方式的一个模型。
TypeScript 中有一个与 if/else 等效的语句。我说的是这个extends
关键字:
type NumberFromType<T> = T extends number
? number
: never;
嗯,有新东西了—— <T>
。我们把它想象成一个类型的盒子。T
可以是任何类型。只要它在语句中的任何地方被定义,我们就会把它放进那个盒子里。经典的例子:
function test<T>(x: T): T {
return x;
}
test(10);
如果你这样调用它test(10);
,TypeScript 就会定义T
为number
,因为x
是number
。我知道,这个解释很简单。但现在就够了。好的,我们可以继续了。
让我们回到NumberFromType
. ,T extends number
这意味着可以安全地假设 类型的值T
也是 类型number
。例如, 10 扩展了 ,number
因为let a: number = 10
它是类型安全的。嗯,如果我们将一个字符串传递给NumberFromType<T>
? ,会发生什么?
type A = NumberFromType<'10'>;
// It will be treated like this:
type NumberFromType<'10'> = '10' extends number ? number : never;
因此,never
类型中会有A
,因为字符串不是从数字扩展而来的。但是如果我们也需要处理字符串呢?我们可以将一个条件放在另一个条件中。像这样:
type StringOrNumberFromType<T> = T extends string
? string
: T extends number
? number
: never
瞧,它适用于字符串和数字)因此,您可以更深入地了解每种类型并添加更多条件。
infer 关键字
终于准备好了infer
!首先,该infer
关键字只能在条件类型中使用。原因很简单——你不能在其他地方使用它 =) 该关键字在条件类型之外没有任何含义。
好的,我们尝试将一个数字数组传递给NumberFromType
。结果never
显然是 。数字数组并非从 number 扩展而来。但如果我们需要获取数组项的类型怎么办?可以像这样:
type ArrayItemType<T> = T extends ...
这条语句接下来应该做什么?我们需要检查一下,它T
是一个数组:
type ArrayItemType<T> = T extends [] ? ... : never;
我刚刚完成了条件,就像在 中一样NumberFromType
。但主要问题还没有解决,因为我们需要数组项的类型。顺便说一下,我们可以为数字数组写一个类型,例如number[]
或Array<number>
。任何其他类型都可以放在<>
括号内。因此,我们的条件可以这样写:
type ArrayItemType<T> = T extends Array<ITEM_TYPE>
? ITEM_TYPE
: never;
好的,好多了!但是ITEM_TYPE
没有定义。它还没有被推断出来。是的,我们需要 TypeScript 来推断类型。我们可以让 TypeScript 来做这件事:
type ArrayItemType<T> = T extends Array<infer ITEM_TYPE>
? ITEM_TYPE
: never;
这意味着,如果T
从某种类型扩展而来,那么,TypeScript,如果您能推断出项目的类型并将其作为结果返回,Array
那对您来说真的太好了。T's
一般来说,我们可以说,infer
TypeScript 中的关键字和条件类型允许我们采用一种类型并隔离它的任何部分以供以后使用。
有一些现实生活中的例子。
不承诺
export type Unpromisify<T> = T extends Promise<infer Result>
? Result
: never;
顾名思义,它返回一个 Promise的Unpromisify<T>
结果。另外,如果您使用 TypeScript 4.5 或更高版本,则可以使用内置类型。typescriptlang.org上Awaited
有一些示例。Awaited
组件属性
在 React 中,我们经常需要访问组件的 prop 类型。为此,React 提供了一种实用程序类型,用于访问 prop 类型,该类型由infer
名为 的关键字提供支持。您可以在DefinitelyTyped 仓库ComponentProps
中找到其完整定义。
type ComponentProps<
T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> =
T extends JSXElementConstructor<infer P>
? P
: T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T]
: {};
在检查到它T
是一个 React 组件 ( T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
) 之后,它会推断出它的 props 并返回它们 ( T extends JSXElementConstructor<infer P> ? P
)。尾部是一个简单的 React 元素。
结论
该infer
关键字是一个强大的工具,它允许我们从任何复杂类型中解包并存储类型。它就像一个类型拆箱操作。所以,这个关键字背后并没有什么神秘之处。
如果你想挑战一下你的 TypeScript 技能,我推荐你参加type-challenges。我确信这篇文章对你来说非常有用。尽情享受吧!
鏂囩珷鏉ユ簮锛�https://dev.to/artemmalko/infer-in-typescript-the-great-and-powerful-5cch