什么意思呢?对于T extends U ? X : Y,当T是联合类型时,例如A | B | C,T extends U ? X : Y会被自动展开为:(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y),利用这个特性,配合infer关键字可以实现联合类型转交叉类型:
type UnionToIntersection<T> = (T extends any ? (k: T) => void : never) extends (k: infer I) => void ? I : never;
上面的UnionToIntersection可以理解为:
当T为A | B | C时:
(T extends any ? (k: T) => void : never)会被展开为(A extends any ? (k: A) => void : never) | (B extends any ? (k: B) => void : never) | (C extends any ? (k: C) => void : never),即(k: A) => void | (k: B) => void | (k: C) => void,显然(k: A) => void | (k: B) => void | (k: C) => void会被(k: A & B & C) => void类型约束,那么使用infer就可以解出A & B & C了。
通过 actionFunction 的函数签名拿到 payload 和 返回值
1、拿到payload:定义一个GetParam泛型,通过infer可推断出函数的第一个参数:
type GetParam<T extends (...args: any) => any> = T extends () => any ? undefined : T extends (arg: infer R) => any ? R : any;
本文是在掘金的这篇文章(TS 4.1 新特性实现 Vuex 无限层级命名空间的 dispatch 类型推断。)的基础上进一步的实现,在阅读本文之前,可以先到掘金看看这篇文章。
TypeScript 4.1 类型模板字符串
先来看看TypeScript 4.1 beta 版本
首先在项目中安装 TypeScript 4.1 Beta 版本:
可以在任何地方使用类型模板字符串:
1、直接使用
上面的例子等价于:
2、在类型映射中使用
上面的例子等价于:
3、用于联合类型
上面的例子等价于:
4、常见的例子:用于事件定义
假设有一个函数
makeWatchedObject
,用于遍历一个对象,生成一个格式相同的对象,但是每次改变此对象属性时会跑出对应的事件,使用一个新的on
方法来检测属性的更改:对于需要监听的事件名,可以用类型模板字符串实现:
5、字符串转换工具函数
上面的例子等价于:
除
Uppercase
外,还有Lowercase
、Capitalize
、Uncapitalize
等工具函数6、类型映射中使用 as 子句映射类型模板字符串生成的新key
等价于:
问题描述
看下面这个例子,这是最常见的一个 Vuex 初始化方法:
我们希望实现
store.dispatch
时,可以出现智能提示和类型提示,如下图所示:那么问题描述变为通过
vuexOptions
,拿到一个action
名称到action
的payload
类型和dispatch
返回值的映射,等价于拿到如下类型:通过上面的
Actions
和Mutations
,我们可以很轻松地通过infer
拿到action
名称、payload
类型与dispatch
返回值类型,即推断actionFunction
的函数签名。推断 actionFunction 的函数签名
我们知道在Vuex初始化时,
action
一般是如下定义的:async homeAction(actionsContext: ActionContext<{}, {}>, homeContext: string) {}
,它的类型会被推断为:rootAction(actionsContext: ActionContext<{}, {}>, payload: string): Promise<void>
因此我们这里需要过滤掉第一个参数
ActionContext
,通过infer
就可以实现:获取root模块的Actions和Mutations映射
1、首先通过
infer
关键字拿到单个模块的所有actions
的类型 2、再通过in
关键字对actions
的keys做类型映射 3、通过我们上面定义好的GetRestFuncType
拿到actionFunction
的函数签名获取任一模块的Actions和Mutations映射
但是,上述的实现并不完善,对于非root模块,例如
detail
模块,例如想要dispatch
detail模块的detailAction
时,就要用到模板字符串拼接为detail/detailAction
:利用联合类型的自动展开,即可将
GetActionsTypes
通用化到非root模块:可以测试下如下代码:
拿到所有模块的Actions和Mutations映射
1、对于 root 模块而言,所有带命名空间的子模块都是放到
modules
属性下面,同样可以通过infer
关键字拿到modules
下所有模块的类型 2、首先定义GetModulesActionTypes
,通过in
关键字对keyof Modules
做类型映射,这样将拿到所有定义到modules
的Actions
和Mutations
映射 3、然后定义GetSubModuleActionsTypes
,通过infer
拿到SubModules
类型,并通过GetModulesActionTypes
拿到Actions
和Mutations
映射测试以下代码:
此时,
AllActionsUnion
的类型为:显然我们希望得到的是这样的类型:
这里我们需要将联合类型转化为交叉类型,我们可以利用Conditional Types in TypeScript
什么意思呢?对于
T extends U ? X : Y
,当T
是联合类型时,例如A | B | C
,T extends U ? X : Y
会被自动展开为:(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
,利用这个特性,配合infer
关键字可以实现联合类型转交叉类型:上面的
UnionToIntersection
可以理解为:当
T
为A | B | C
时:(T extends any ? (k: T) => void : never)
会被展开为(A extends any ? (k: A) => void : never) | (B extends any ? (k: B) => void : never) | (C extends any ? (k: C) => void : never)
,即(k: A) => void | (k: B) => void | (k: C) => void
,显然(k: A) => void | (k: B) => void | (k: C) => void
会被(k: A & B & C) => void
类型约束,那么使用infer
就可以解出A & B & C
了。通过 actionFunction 的函数签名拿到 payload 和 返回值
1、拿到
payload
:定义一个GetParam
泛型,通过infer
可推断出函数的第一个参数:2、拿到返回值:通过 TypeScript 内置的
ReturnType
可实现这里可以直接通过TypeScript的工具函数和
ReturnType
拿到,其原理也不复杂,就是通过infer
关键字:完整的实现
通过上面的方法,就可以实现
Vuex
的store.commit
和store.dispatch
类型判断,完整代码如下:小结
上面是笔者在Vuex中实践TypeScript模板字符串的总结,笔者也是顺手发布了一个npm 包,感兴趣的小伙伴看看vuex-typescript-commit-dispatch-prompt
使用方法
1、首先安装依赖,需要安装TypeScript 4.1 以上版本
2、在初始化store处引入
vuex-typescript-commit-dispatch-prompt
,然后拓展vuex module 的类型定义文章最后,笔者近期维护了一个公众号,用于分享前端新技术,欢迎大家到Counter的前端小站逛逛,一起关注前端的新技术发展。