当前位置: 首页 > Web前端 > HTML5

陪尤雨溪实现Vuex无限层级类型推断,(TS4.1新特性)

时间:2023-04-05 20:26:58 HTML5

前言日前,TypeScript发布了一个4.1版本的新特性,字符串模板类型。没听说过的可以先看这篇文章:TypeScript4.1新特性:String模板类型,Vuex终于有救了?.本文利用该特性简单实现了Vuex在模块嵌套情况下的dispatchstring类型推断。先来看看效果吧。我们有一个具有这种结构的商店:conststore=Vuex({mutations:{root(){},},modules:{car??t:{mutations:{add(){},remove(){},},},user:{mutations:{login(){},},modules:{admin:{mutations:{login(){},},},},},},})需要实现这个效果,可选action派单时可以提示字符串类型:store.dispatch('root')store.dispatch('cart/add')store.dispatch('user/login')store.dispatch('user/admin/login')实现定义functionskeleton首先先定义Vuex函数,使用两个泛型把mutations和modules反向推导:typeStore={//这个Actiontypedispatch(action:Action)会被实现下面:void}typeVuexOptions={mutations:Mutationsmodules:Modules}declarefunctionVuex(options:VuexOptions):StoreimplementsAction,那么接下来的重点就是实现dispatch(action:Action):void中的Action,我们的目标是将其推断为'root'|'购物车/添加'|'用户/登录'|像'user/admin/login'这样的union类型,这样当用户调用dispatch的时候,可以智能提示。在Action中,他可以先简单的获取到Mutations的key,因为rootstore下的mutations不需要做任何拼接,最重要的是我们需要使用Modules的泛型类型,也就是对应的结构体:模块:{购物车:{突变:{添加(){},删除(){}}},用户:{突变:{登录(){}},模块:{管理:{突变:{登录(){}},}}}}得到模块中所有拼接的key。推断ModulesKeys应该是提前同步给大家的,在后面的泛型中:Modules表示{cart:{modules:{}},user:{modules:{}}这是一个由多个Modules组成的对象结构.Module表示单个子模块,例如cart。使用typeValues={[KinkeyofModules]:Modules[K]}[keyofModules],可以方便的扩展对象中的所有值类型,比如typeObj={a:'foo'b:'bar'}typeT=Values//'foo'|'bar'由于我们要获取的是从cart和user对应的value中提取的key,所以利用上面的知识,我们写GetModulesMutationKeys获取Modules下的所有key:typeGetModulesMutationKeys={[KinkeyofModules]:GetModuleMutationKeys}[keyofModules]首先使用keyofModules中的K获取所有的key,这样我们就可以获取单个模块比如cart和user,传给GetModuleMutationKeystype,并且K也必须传入,因为我们需要在最终的type前面使用cart和user的key进行拼接。推断单个模块键,然后实现GetModuleMutationKeys以分解需求。首先,单个Module看起来是这样的:cart:{mutations:{add(){},remove(){}}},那么在得到它的Mutations之后,我们只需要拼接cart/add,cart/remove,所以如何获得对象类型的突变?我们使用infer来获取:typeGetMutations=Moduleextends{mutations:inferM}?M:never然后通过keyofGetMutations,可以很容易的得到类型'add'|'remove',我们再实现一种拼接Key的类型,注意这里使用的是TS4.1的string模板类型typeAddPrefix=`${Prefix}/${Keys}`会自动展开并在此处分配关节类型,${'cart'}/${'add'|'remove'}将被推断为'cart/add'|'cart/remove',但由于我们传入了keyofGetMutations它也可能是符号|number类型,所以使用Keys&string获取字符串类型。老哥在TemplatestringtypesMR中也提到了这个技巧:上面,需要一个keyofT&stringintersection因为keyofT可能包含无法使用模板字符串类型转换的符号类型。typeAddPrefix=`${Prefix&string}/${Keys&string}`然后,使用AddPrefix>可以方便的拼接cart模块下的mutations推断嵌套的ModuleKeyscart模块下可能还有其他Modules,例如:cart:{mutations:{add(){},remove(){}}},}}}},其实很简单,我们刚刚定义了从Modules中提取Keys的工具类型,即GetModulesMutationKeys,只需要递归调用即可,不过这里需要做一层预处理,去掉modulesthatdonotexist排除这种情况:typeGetModuleMutationKeys=//这里直接拼接key/mutation|AddPrefix>//在此处从子模块中提取密钥|GetSubModuleKeys使用extends判断类型结构,直接返回never不存在modules的结构,然后使用infer提取Modules的结构,在GetModulesMutationKeys返回的结果之前拼接上一个module的key刚刚写的:typeGetSubModuleKeys=Moduleextends{modules:inferSubModules}?AddPrefix>:never以这个cart模块为例,我们来分解一下每种工具类型的结果:cart:{mutations:{add(){},remove(){}}modules:{subCart:{mutations:{add(){},}}}},输入GetModuleMutationKeys=//'cart/add'|'购物车|移除'AddPrefix>|//'cart/subCart/add'GetSubModuleKeystypeGetSubModuleKeys=Moduleextends{modules:inferSubModules}?AddPrefix>:never这样巧妙地使用递归把无限层级的模块拼接实现完整代码typeGetMutations=Moduleextends{mutations:inferM}?M:nevertypeAddPrefix=`${Prefix&string}/${Keys&string}`typeGetSubModuleKeys=Moduleextends{modules:inferSubModules}?AddPrefix>:nevertypeGetModuleMutationKeys=AddPrefix>|GetSubModuleKeystypeGetModulesMutationKeys={[KinkeyofModules]:GetModuleMutationKeys}[keyofModules]typeAction=keyof突变|GetModulesMutationKeystypeStore={dispatch(action:Action):void}typeVuexOptions={mutations:Mutations,modules:Modules}declarefunctionVuex(选项:VuexOptions):Storeconststore=Vuex({mutations:{root(){},},modules:{car??t:{mutations:{add(){},remove(){}}},用户:{突变:{login(){}},模块:{admin:{突变:{login(){}},}}}}})store.dispatch("root")store.dispatch("cart/add")store.dispatch("user/login")store.dispatch("user/admin/login")前往TypeScriptPlayground体验尾声这个新特性为TS库开发的作者们带来了无限可能。有人用它实现URLParser和HTML解析器,有人用它实现JSON解析,甚至有人用它实现简单的正则化。这个功能可以让类型体操爱好者和框架库作者进一步大显身手。期待他们写出更强大的类型库,方便业务发展~感谢大家关注公众号《前端从进阶到入学》,后台回复TS,发了几张《TypeScript项目实战》实战》,很好的实用书。