前几天,TypeScript 发布了一项 4.1 版本的新特性,字符串模板类型,还没有了解过的小伙伴可以先去这篇看一下:TypeScript 4.1 新特性:字符串模板类型,Vuex 终于有救了?[1]。
本文就利用这个特性,简单实现下 Vuex 在 modules
嵌套情况下的 dispatch
字符串类型推断,先看下效果,我们有这样结构的 store
:
const store = Vuex({
mutations: {
root() {},
},
modules: {
cart: {
mutations: {
add() {},
remove() {},
},
},
user: {
mutations: {
login() {},
},
modules: {
admin: {
mutations: {
login() {},
},
},
},
},
},
})
需要实现这样的效果,在 dispatch
的时候可选的 action
字符串类型要可以被提示出来:
store.dispatch('root')
store.dispatch('cart/add')
store.dispatch('user/login')
store.dispatch('user/admin/login')
首先先定义好 Vuex 这个函数,用两个泛型把 mutations
和 modules
通过反向推导给拿到:
type Store<Mutations, Modules> = {
// 下文会实现这个 Action 类型
dispatch(action: Action<Mutations, Modules>): void
}
type VuexOptions<Mutations, Modules> = {
mutations: Mutations
modules: Modules
}
declare function Vuex<Mutations, Modules>(
options: VuexOptions<Mutations, Modules>
): Store<Mutations, Modules>
那么接下来的重点就是实现 dispatch(action: Action<Mutations, Modules>): void
中的 Action
了,我们的目标是把他推断成一个 'root' | 'cart/add' | 'user/login' | 'user/admin/login'
这样的联合类型,这样用户在调用 dispatch
的时候,就可以智能提示了。
Action
里首先可以简单的先把 keyof Mutations
拿到,因为根 store
下的 mutations
不需要做任何的拼接,
重头戏在于,我们需要根据 Modules
这个泛型,也就是对应结构:
modules: {
cart: {
mutations: {
add() { },
remove() { }
}
},
user: {
mutations: {
login() { }
},
modules: {
admin: {
mutations: {
login() { }
},
}
}
}
}
来拿到 modules
中的所有拼接后的 key
。
先提前和大伙同步好,后续泛型里的:
Modules
代表 { cart: { modules: {} }, user: { modules: {} }
这种多个 Module
组合的对象结构。Module
代表单个子模块,比如 cart
。利用
type Values<Modules> = {
[K in keyof Modules]: Modules[K]
}[keyof Modules]
这种方式,可以轻松的把对象里的所有值 类型给展开,比如
type Obj = {
a: 'foo'
b: 'bar'
}
type T = Values<Obj> // 'foo' | 'bar'
由于我们要拿到的是 cart
、user
对应的值里提取出来的 key
,
所以利用上面的知识,我们编写 GetModulesMutationKeys
来获取 Modules
下的所有 key
:
type GetModulesMutationKeys<Modules> = {
[K in keyof Modules]: GetModuleMutationKeys<Modules[K], K>
}[keyof Modules]
首先利用 K in keyof Modules
来拿到所有的 key,这样我们就可以拿到 cart
、user
这种单个 Module
,并且传入给 GetModuleMutationKeys
这个类型,K
也要一并传入进去,因为我们需要利用 cart
、user
这些 key
来拼接在最终得到的类型前面。
接下来实现 GetModuleMutationKeys
,分解一下需求,首先单个 Module
是这样子的:
cart: {
mutations: {
add() { },
remove() { }
}
},
那么拿到它的 Mutations
后,我们只需要去拼接 cart/add
、cart/remove
即可,那么如何拿到一个对象类型中的 mutations
?
我们用 infer
来取:
type GetMutations<Module> = Module extends { mutations: infer M } ? M : never
然后通过 keyof GetMutations<Module>
,即可轻松拿到 'add' | 'remove'
这个类型,我们再实现一个拼接 Key
的类型,注意这里就用到了 TS 4.1 的字符串模板类型了
type AddPrefix<Prefix, Keys> = `${Prefix}/${Keys}`
这里会自动把联合类型展开并分配,${'cart'}/${'add' | 'remove'}
会被推断成 'cart/add' | 'cart/remove'
,不过由于我们传入的是 keyof GetMutations<Module>
它还有可能是 symbol | number
类型,所以用 Keys & string
来取其中的 string
类型,这个技巧也是老爷子在 Template string types MR[2] 中提到的:
Above, a keyof T & string intersection is required because keyof T could contain symbol types that cannot be transformed using template string types.
type AddPrefix<Prefix, Keys> = `${Prefix & string}/${Keys & string}`
那么,利用 AddPrefix<Key, keyof GetMutations<Module>>
就可以轻松的把 cart
模块下的 mutations
拼接出来了。
cart
模块下还可能有别的 Modules
,比如这样:
cart: {
mutations: {
add() { },
remove() { }
}
modules: {
subCart: {
mutations: {
add() { },
}
}
}
},
其实很简单,我们刚刚已经定义好了从 Modules
中提取 Keys
的工具类型,也就是 GetModulesMutationKeys
,只需要递归调用即可,不过这里我们需要做一层预处理,把 modules
不存在的情况给排除掉:
type GetModuleMutationKeys<Module, Key> =
// 这里直接拼接 key/mutation
| AddPrefix<Key, keyof GetMutations<Module>>
// 这里对子 modules 做 keys 的提取
| GetSubModuleKeys<Module, Key>
利用 extends 去判断类型结构,对不存在 modules
的结构直接返回 never,再用 infer 去提取出 Modules 的结构,并且把前一个模块的 key
拼接在刚刚写好的 GetModulesMutationKeys
返回的结果之前:
type GetSubModuleKeys<Module, Key> = Module extends { modules: infer SubModules }
? AddPrefix<Key, GetModulesMutationKeys<SubModules>>
: never
以这个 cart
模块为例,分解一下每个工具类型得到的结果:
cart: {
mutations: {
add() { },
remove() { }
}
modules: {
subCart: {
mutations: {
add() { },
}
}
}
},
type GetModuleMutationKeys<Module, Key> =
// 'cart/add' | 'cart | remove'
AddPrefix<Key, keyof GetMutations<Module>> |
// 'cart/subCart/add'
GetSubModuleKeys<Module, Key>
type GetSubModuleKeys<Module, Key> = Module extends { modules: infer SubModules }
? AddPrefix<
// 'cart'
Key,
// 'subCart/add'
GetModulesMutationKeys<SubModules>
>
: never
这样,就巧妙的利用递归把无限层级的 modules
拼接实现了。
type GetMutations<Module> = Module extends { mutations: infer M } ? M : never
type AddPrefix<Prefix, Keys> = `${Prefix & string}/${Keys & string}`
type GetSubModuleKeys<Module, Key> = Module extends { modules: infer SubModules }
? AddPrefix<Key, GetModulesMutationKeys<SubModules>>
: never
type GetModuleMutationKeys<Module, Key> = AddPrefix<Key, keyof GetMutations<Module>> | GetSubModuleKeys<Module, Key>
type GetModulesMutationKeys<Modules> = {
[K in keyof Modules]: GetModuleMutationKeys<Modules[K], K>
}[keyof Modules]
type Action<Mutations, Modules> = keyof Mutations | GetModulesMutationKeys<Modules>
type Store<Mutations, Modules> = {
dispatch(action: Action<Mutations, Modules>): void
}
type VuexOptions<Mutations, Modules> = {
mutations: Mutations,
modules: Modules
}
declare function Vuex<Mutations, Modules>(options: VuexOptions<Mutations, Modules>): Store<Mutations, Modules>
const store = Vuex({
mutations: {
root() { },
},
modules: {
cart: {
mutations: {
add() { },
remove() { }
}
},
user: {
mutations: {
login() { }
},
modules: {
admin: {
mutations: {
login() { }
},
}
}
}
}
})
store.dispatch("root")
store.dispatch("cart/add")
store.dispatch("user/login")
store.dispatch("user/admin/login")
这个新特性给 TS 库开发的作者带来了无限可能性,有人用它实现了 URL Parser 和 HTML parser[4],有人用它实现了 JSON parse
甚至有人用它实现了简单的正则[5],这个特性让类型体操的爱好者以及框架的库作者可以进一步的大展身手,期待他们写出更加强大的类型库来方便业务开发的童鞋吧~
本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/MeGj7bD8m3VvXDMrMXNUHw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。