We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TypeScript中有一项相当重要的进阶特性:conditional types,这个功能出现以后,很多积压已久的TypeScript功能都可以轻而易举的实现了。
conditional types
那么本篇文章就会通过一个简单的功能:把
distribute({ type: 'LOGIN', email: string })
这样的函数调用方式给简化为:
distribute('LOGIN', { email: string })
没错,它只是节省了几个字符串,但是却是一个非常适合我们深入学习条件类型的实战。
先简单的看一个条件类型的示例:
function process<T extends string | null>( text: T ): T extends string ? string : null { ... }
A extends B ? C : D
这样的语法就叫做条件类型,A, B, C和D可以是任何类型表达式。
A
B
C
D
这个extends关键字是条件类型的核心。 A extends B恰好意味着可以将类型A的任何值安全地分配给类型B的变量。在类型系统术语中,我们可以说“ A可分配给B”。
extends
A extends B
从结构上来讲,我们可以说A extends B,就像“ A是B的超集”,或者更确切地说,“ A具有B的所有特性,也许更多”。
举个例子来说 { foo: number, bar: string } extends { foo: number }是成立的,因为前者显然是后者的超集,比后者拥有更具体的类型。
{ foo: number, bar: string } extends { foo: number }
官方文档中,介绍了一种操作,叫 Distributive conditional types
Distributive conditional types
简单来说,传入给T extends U中的T如果是一个联合类型A | B | C,则这个表达式会被展开成
T extends U
T
A | B | C
(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
条件类型让你可以过滤联合类型的特定成员。 为了说明这一点,假设我们有一个称为Animal的联合类型:
type Animal = Lion | Zebra | Tiger | Shark
再假设我们要编写一个类型,来过滤出Animal中属于“猫”的那些类型
type ExtractCat<A> = A extends { meow(): void } ? A : never type Cat = ExtractCat<Animal> // => Lion | Tiger
接下来,Cat的计算过程会是这样子的:
type Cat = | ExtractCat<Lion> | ExtractCat<Zebra> | ExtractCat<Tiger> | ExtractCat<Shark>
然后,它被计算成联合类型
type Cat = Lion | never | Tiger | never
然后,联合类型中的never没什么意义,所以最后的结果的出来了:
type Cat = Lion | Tiger
记住这样的计算过程,记住ts这个把联合类型如何分配给条件类型,接下来的实战中会很有用。
举一个类似redux中的dispatch的例子。
redux
dispatch
首先,我们有一个联合类型Action,用来表示所有可以被dispatch接受的参数类型:
Action
type Action = | { type: "INIT" } | { type: "SYNC" } | { type: "LOG_IN" emailAddress: string } | { type: "LOG_IN_SUCCESS" accessToken: string }
然后我们定义这个dispatch方法:
declare function dispatch(action: Action): void // ok dispatch({ type: "INIT" }) // ok dispatch({ type: "LOG_IN", emailAddress: "[email protected]" }) // ok dispatch({ type: "LOG_IN_SUCCESS", accessToken: "038fh239h923908h" })
这个API是类型安全的,当TS识别到type为LOG_IN的时候,它会要求你在参数中传入emailAddress这个参数,这样才能完全满足联合类型中的其中一项。
LOG_IN
emailAddress
到此为止,我们可以去和女朋友约会了,此文完结。
等等,我们好像可以让这个api变得更简单一点:
dispatch("LOG_IN_SUCCESS", { accessToken: "038fh239h923908h" })
好,推掉我们的约会,打电话给我们的女朋友!取消!
首先,利用方括号选择出Action中的所有type,这个技巧很有用。
type
type ActionType = Action["type"] // => "INIT" | "SYNC" | "LOG_IN" | "LOG_IN_SUCCESS"
但是第二个参数的类型取决于第一个参数。 我们可以使用类型变量来对该依赖关系建模。
declare function dispatch<T extends ActionType>( type: T, args: ExtractActionParameters<Action, T> ): void
注意,这里就用到了extends语法,规定了我们的入参type必须是ActionType中一部分。
ActionType
注意这里的第二个参数args,用ExtractActionParameters<Action, T>这个类型来把type和args做了关联,
ExtractActionParameters<Action, T>
来看看ExtractActionParameters是如何实现的:
ExtractActionParameters
type ExtractActionParameters<A, T> = A extends { type: T } ? A : never
在这次实战中,我们第一次运用到了条件类型,ExtractActionParameters<Action, T>会按照我们上文提到的分布条件类型,把Action中的4项依次去和{ type: T }进行比对,找出符合的那一项。
分布条件类型
{ type: T }
来看看如何使用它:
type Test = ExtractActionParameters<Action, "LOG_IN"> // => { type: "LOG_IN", emailAddress: string }
这样就筛选出了type匹配的一项。
接下来我们要把type去掉,第一个参数已经是type了,因此我们不想再额外声明type了。
// 把类型中key为"type"去掉 type ExcludeTypeField<A> = { [K in Exclude<keyof A, "type">]: A[K] }
这里利用了keyof语法,并且利用内置类型Exclude把type这个key去掉,因此只会留下额外的参数。
keyof
Exclude
type Test = ExcludeTypeField<{ type: "LOG_IN", emailAddress: string }> // { emailAddress: string }
到此为止,我们就可以实现上文中提到的参数简化功能:
// ok dispatch({ type: "LOG_IN", emailAddress: "[email protected]" })
到了这一步为止,虽然带参数的Action可以完美支持了,但是对于"INIT"这种不需要传参的Action,我们依然要写下面这样代码:
dispatch("INIT", {})
这肯定是不能接受的!所以我们要利用TypeScript的函数重载功能。
// 简单参数类型 function dispatch<T extends SimpleActionType>(type: T): void // 复杂参数类型 function dispatch<T extends ComplexActionType>( type: T, args: ExtractActionParameters<Action, T>, ): void // 实现 function dispatch(arg: any, payload?: any) {}
那么关键点就在于SimpleActionType和ComplexActionType要如何实现了,
SimpleActionType
ComplexActionType
SimpleActionType顾名思义就是除了type以外不需要额外参数的Action类型,
type SimpleAction = ExtractSimpleAction<Action>
我们如何定义这个ExtractSimpleAction条件类型?
ExtractSimpleAction
如果我们从这个Action中删除type字段,并且结果是一个空的接口,
那么这就是一个SimpleAction。 所以我们可能会凭直觉写出这样的代码:
SimpleAction
type ExtractSimpleAction<A> = ExcludeTypeField<A> extends {} ? A : never
但这样是行不通的,几乎所有的类型都可以extends {},因为{}太宽泛了。
我们应该反过来写:
type ExtractSimpleAction<A> = {} extends ExcludeTypeField<A> ? A : never
现在,如果ExcludeTypeField <A>为空,则extends表达式为true,否则为false。
ExcludeTypeField <A>
但这仍然行不通! 因为分布条件类型仅在extends关键字左侧是类型变量时发生。
分布条件件类型仅发生在如下场景:
type Blah<Var> = Var extends Whatever ? A : B
而不是:
type Blah<Var> = Foo<Var> extends Whatever ? A : B type Blah<Var> = Whatever extends Var ? A : B
但是我们可以通过一些小技巧绕过这个限制:
type ExtractSimpleAction<A> = A extends any ? {} extends ExcludeTypeField<A> ? A : never : never
A extends any是一定成立的,这只是用来绕过ts对于分布条件类型的限制,而我们真正想要做的条件判断被放在了中间,因此Action联合类型中的每一项又能够分布的去匹配了。
A extends any
那么我们就可以简单的筛选出所有不需要额外参数的type
type SimpleAction = ExtractSimpleAction<Action> type SimpleActionType = SimpleAction['type']
再利用Exclude取反,找到复杂类型:
type ComplexActionType = Exclude<ActionType, SimpleActionType>
到此为止,我们所需要的功能就完美实现了:
// 简单参数类型 function dispatch<T extends SimpleActionType>(type: T): void // 复杂参数类型 function dispatch<T extends ComplexActionType>( type: T, args: ExtractActionParameters<Action, T>, ): void // 实现 function dispatch(arg: any, payload?: any) {} // ok dispatch("SYNC") // ok dispatch({ type: "LOG_IN", emailAddress: "[email protected]" })
本文的实战示例来自国外大佬的博客,我结合个人的理解整理成了这篇文章。
中间涉及到的一些进阶的知识点,如果小伙伴们不太熟悉的话,可以参考各类文档中的定义去反复研究,相信你会对TypeScript有更深一步的了解。
https://artsy.github.io/blog/2018/11/21/conditional-types-in-typescript/
https://github.com/sl1673495/typescript-codes/blob/master/src/dispatch-conditional-types-with-builtin-types.ts
The text was updated successfully, but these errors were encountered:
No branches or pull requests
TypeScript中有一项相当重要的进阶特性:
conditional types
,这个功能出现以后,很多积压已久的TypeScript功能都可以轻而易举的实现了。那么本篇文章就会通过一个简单的功能:把
这样的函数调用方式给简化为:
没错,它只是节省了几个字符串,但是却是一个非常适合我们深入学习条件类型的实战。
通过这篇文章,你可以学到以下特性在实战中是如何使用的:
conditional types的第一次使用
先简单的看一个条件类型的示例:
这样的语法就叫做条件类型,
A
,B
,C
和D
可以是任何类型表达式。可分配性
这个
extends
关键字是条件类型的核心。A extends B
恰好意味着可以将类型A的任何值安全地分配给类型B的变量。在类型系统术语中,我们可以说“ A可分配给B”。从结构上来讲,我们可以说
A extends B
,就像“ A是B的超集”,或者更确切地说,“ A具有B的所有特性,也许更多”。举个例子来说
{ foo: number, bar: string } extends { foo: number }
是成立的,因为前者显然是后者的超集,比后者拥有更具体的类型。分布条件类型
官方文档中,介绍了一种操作,叫
Distributive conditional types
简单来说,传入给
T extends U
中的T
如果是一个联合类型A | B | C
,则这个表达式会被展开成条件类型让你可以过滤联合类型的特定成员。 为了说明这一点,假设我们有一个称为Animal的联合类型:
再假设我们要编写一个类型,来过滤出Animal中属于“猫”的那些类型
接下来,Cat的计算过程会是这样子的:
然后,它被计算成联合类型
然后,联合类型中的never没什么意义,所以最后的结果的出来了:
记住这样的计算过程,记住ts这个把联合类型如何分配给条件类型,接下来的实战中会很有用。
分布条件类型的真实用例
举一个类似
redux
中的dispatch
的例子。首先,我们有一个联合类型
Action
,用来表示所有可以被dispatch接受的参数类型:然后我们定义这个dispatch方法:
这个API是类型安全的,当TS识别到type为
LOG_IN
的时候,它会要求你在参数中传入emailAddress
这个参数,这样才能完全满足联合类型中的其中一项。到此为止,我们可以去和女朋友约会了,此文完结。
等等,我们好像可以让这个api变得更简单一点:
好,推掉我们的约会,打电话给我们的女朋友!取消!
参数简化实现
首先,利用方括号选择出
Action
中的所有type
,这个技巧很有用。但是第二个参数的类型取决于第一个参数。 我们可以使用类型变量来对该依赖关系建模。
注意,这里就用到了
extends
语法,规定了我们的入参type
必须是ActionType
中一部分。注意这里的第二个参数args,用
ExtractActionParameters<Action, T>
这个类型来把type和args做了关联,来看看
ExtractActionParameters
是如何实现的:在这次实战中,我们第一次运用到了条件类型,
ExtractActionParameters<Action, T>
会按照我们上文提到的分布条件类型
,把Action中的4项依次去和{ type: T }
进行比对,找出符合的那一项。来看看如何使用它:
这样就筛选出了type匹配的一项。
接下来我们要把type去掉,第一个参数已经是type了,因此我们不想再额外声明type了。
这里利用了
keyof
语法,并且利用内置类型Exclude
把type
这个key去掉,因此只会留下额外的参数。到此为止,我们就可以实现上文中提到的参数简化功能:
利用重载进一步优化
到了这一步为止,虽然带参数的Action可以完美支持了,但是对于"INIT"这种不需要传参的Action,我们依然要写下面这样代码:
这肯定是不能接受的!所以我们要利用TypeScript的函数重载功能。
那么关键点就在于
SimpleActionType
和ComplexActionType
要如何实现了,SimpleActionType
顾名思义就是除了type以外不需要额外参数的Action类型,我们如何定义这个
ExtractSimpleAction
条件类型?如果我们从这个Action中删除
type
字段,并且结果是一个空的接口,那么这就是一个
SimpleAction
。 所以我们可能会凭直觉写出这样的代码:但这样是行不通的,几乎所有的类型都可以extends {},因为{}太宽泛了。
我们应该反过来写:
现在,如果
ExcludeTypeField <A>
为空,则extends表达式为true,否则为false。但这仍然行不通! 因为
分布条件类型
仅在extends关键字左侧是类型变量时发生。分布条件件类型仅发生在如下场景:
而不是:
但是我们可以通过一些小技巧绕过这个限制:
A extends any
是一定成立的,这只是用来绕过ts对于分布条件类型的限制,而我们真正想要做的条件判断被放在了中间,因此Action联合类型中的每一项又能够分布的去匹配了。那么我们就可以简单的筛选出所有不需要额外参数的type
再利用Exclude取反,找到复杂类型:
到此为止,我们所需要的功能就完美实现了:
总结
本文的实战示例来自国外大佬的博客,我结合个人的理解整理成了这篇文章。
中间涉及到的一些进阶的知识点,如果小伙伴们不太熟悉的话,可以参考各类文档中的定义去反复研究,相信你会对TypeScript有更深一步的了解。
参考资料
https://artsy.github.io/blog/2018/11/21/conditional-types-in-typescript/
源码
https://github.com/sl1673495/typescript-codes/blob/master/src/dispatch-conditional-types-with-builtin-types.ts
The text was updated successfully, but these errors were encountered: