在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
TypeScript中有一项相当重要的进阶特性: 那么本篇文章就会通过一个简单的功能:把 distribute({ type: 'LOGIN', email: string }) 这样的函数调用方式给简化为: distribute('LOGIN', { email: string })
没错,它只是节省了几个字符串,但是却是一个非常适合我们深入学习条件类型的实战。 通过这篇文章,你可以学到以下特性在实战中是如何使用的:
conditional types的第一次使用先简单的看一个条件类型的示例: function process<T extends string | null>( text: T ): T extends string ? string : null { ... }
这样的语法就叫做条件类型, 可分配性这个 从结构上来讲,我们可以说 举个例子来说 分布条件类型官方文档中,介绍了一种操作,叫 简单来说,传入给 (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这个把联合类型如何分配给条件类型,接下来的实战中会很有用。 分布条件类型的真实用例举一个类似 首先,我们有一个联合类型 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为 到此为止,我们可以去和女朋友约会了,此文完结。 等等,我们好像可以让这个api变得更简单一点: dispatch("LOG_IN_SUCCESS", { accessToken: "038fh239h923908h" }) 好,推掉我们的约会,打电话给我们的女朋友!取消! 参数简化实现首先,利用方括号选择出 type ActionType = Action["type"] // => "INIT" | "SYNC" | "LOG_IN" | "LOG_IN_SUCCESS" 但是第二个参数的类型取决于第一个参数。 我们可以使用类型变量来对该依赖关系建模。 declare function dispatch<T extends ActionType>( type: T, args: ExtractActionParameters<Action, T> ): void 注意,这里就用到了 注意这里的第二个参数args,用 来看看 type ExtractActionParameters<A, T> = A extends { type: T } ? A : never 在这次实战中,我们第一次运用到了条件类型, 来看看如何使用它: 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] } 这里利用了 type Test = ExcludeTypeField<{ type: "LOG_IN", emailAddress: string }> // { emailAddress: string } 然后用它来剔除参数中的 // 把参数对象中的type去掉 type ExtractActionParametersWithoutType<A, T> = ExcludeTypeField<ExtractActionParameters<A, T>>; declare function dispatch<T extends ActionType>( type: T, args: ExtractActionParametersWithoutType<Action, T> ): void
到此为止,我们就可以实现上文中提到的参数简化功能: // ok dispatch({ type: "LOG_IN", emailAddress: "[email protected]" }) 利用重载进一步优化到了这一步为止,虽然带参数的Action可以完美支持了,但是对于"INIT"这种不需要传参的Action,我们依然要写下面这样代码:
// 简单参数类型 function dispatch<T extends SimpleActionType>(type: T): void // 复杂参数类型 function dispatch<T extends ComplexActionType>( type: T, args: ExtractActionParametersWithoutType<Action, T>, ): void // 实现 function dispatch(arg: any, payload?: any) {}
那么关键点就在于
type SimpleAction = ExtractSimpleAction<Action> 我们如何定义这个 如果我们从这个Action中删除 那么这就是一个 type ExtractSimpleAction<A> = ExcludeTypeField<A> extends {} ? A : never 但这样是行不通的,几乎所有的类型都可以extends {},因为{}太宽泛了。 我们应该反过来写: type ExtractSimpleAction<A> = {} extends ExcludeTypeField<A> ? A : never 现在,如果 但这仍然行不通! 因为 分布条件件类型仅发生在如下场景: 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
而我们真正想要做的条件判断被放在了中间,因此Action联合类型中的每一项又能够分布的去匹配了。 那么我们就可以简单的筛选出所有不需要额外参数的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]" }) 完整代码type Action = | { type: "INIT"; } | { type: "SYNC"; } | { type: "LOG_IN"; emailAddress: string; } | { type: "LOG_IN_SUCCESS"; accessToken: string; }; // 用类型查询查出Action中所有type的联合类型 type ActionType = Action["type"]; // 把类型中key为"type"去掉 type ExcludeTypeField<A> = { [K in Exclude<keyof A, "type">]: A[K] }; type ExtractActionParameters<A, T> = A extends { type: T } ? A : never // 把参数对象中的type去掉 // Extract<A, { type: T }会挑选出能extend { type: T }这个结构的Action中的类型 type ExtractActionParametersWithoutType<A, T> = ExcludeTypeField<ExtractActionParameters<A, T>>; type ExtractSimpleAction<A> = A extends any ? {} extends ExcludeTypeField<A> ? A : never : never; type SimpleActionType = ExtractSimpleAction<Action>["type"]; type ComplexActionType = Exclude<ActionType, SimpleActionType>; // 简单参数类型 function dispatch<T extends SimpleActionType>(type: T): void; // 复杂参数类型 function dispatch<T extends ComplexActionType>( type: T, args: ExtractActionParametersWithoutType<Action, T> ): void; // 实现 function dispatch(arg: any, payload?: any) {} dispatch("SYNC"); dispatch('LOG_IN', { emailAddress: '[email protected]' }) 总结本文的实战示例来自国外大佬的博客,我结合个人的理解整理成了这篇文章。 中间涉及到的一些进阶的知识点,如果小伙伴们不太熟悉的话,可以参考各类文档中的定义去反复研究,相信你会对TypeScript有更深一步的了解。 参考资料源码这里是用TS内置工具类型改造过后的源码,更加简洁优雅的完成了本文中的需求,可以扩展学习。 |
请发表评论