Mpx框架中利用TypeScript模板字符串类型实现链式Key类型推导
mpx Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 项目地址: https://gitcode.com/gh_mirrors/mp/mpx
引言
在现代前端开发中,类型系统的重要性日益凸显。TypeScript作为JavaScript的超集,为开发者提供了强大的类型支持。在滴滴开源的Mpx小程序框架中,我们面临一个具有挑战性的类型推导问题:如何为类似Vuex的数据仓库设计中的链式Key提供完善的类型推导?
本文将深入探讨如何利用TypeScript 4.1引入的模板字符串类型(Template Literal Types)特性,解决Mpx框架中链式Key的类型推导难题。
问题背景
在Mpx框架的数据仓库设计中,我们经常需要处理类似这样的场景:
createStoreWithThis({
actions: {
someAction() {
let result = this.dispatch('deeperActions.anotherAction', 1)
return result
}
},
deps: {
deeperActions: createStoreWithThis({
actions: {
anotherAction(payload: number) {
return 'result' + payload
}
}
})
}
})
我们需要解决的问题是:
- 如何让
dispatch('deeperActions.anotherAction', 1)
获得正确的返回值类型推导 - 如何对dispatch的参数类型进行限制
TypeScript 4.1新特性解析
TypeScript 4.1引入了模板字符串类型,这一特性为我们的问题提供了解决方案。让我们先了解它的基本用法:
type Color = "red" | "blue"
type Quantity = "one" | "two"
type SeussFish = `${Quantity | Color} fish`
// 结果为:"one fish" | "two fish" | "red fish" | "blue fish"
这种类型拼接的能力正是我们解决链式Key类型推导的基础。
链式Key类型推导实现
基础工具类型
首先,我们需要定义几个基础工具类型:
// 过滤掉symbol类型的key
type StringKeyof<T> = Exclude<keyof T, symbol>
// 拼接两个key
type CombineStringKey<H extends string | number, L extends string | number> =
H extends '' ? `${L}` : `${H}.${L}`
核心推导类型
接下来,我们实现核心的链式Key推导类型:
type ChainKeys<T, P extends string | number = ''> = UnionToIntersection<{
[K in StringKeyof<T>]: Record<CombineStringKey<P, K>, T[K]> &
(T[K] extends Record<any, any> ? ChainKeys<T[K], CombineStringKey<P, K>> : {})
}[StringKeyof<T>]>
这个类型递归地遍历对象的所有属性,生成所有可能的链式Key路径及其对应的类型。
类型转换工具
为了实现完整的推导,我们需要一个将联合类型转换为交叉类型的工具:
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
((k: infer I) => void) ? I : never
这个工具利用了TypeScript的条件类型和逆变位置的类型推断特性。
完整实现
结合上述工具,我们可以实现完整的getValueByPath
类型推导:
declare function getValueByPath<T extends Record<any, any>, K extends keyof ChainKeys<T>>(
object: T,
prop: K
): ChainKeys<T>[K]
// 使用示例
let value = getValueByPath({ a: { b: { c: 1 } } }, 'a.b.c') // 类型为number
在Mpx数据仓库中的应用
将这一技术应用到Mpx框架的数据仓库中,我们可以实现完善的dispatch方法类型推导:
interface DeeperActions {
[k: string]: ((...args: any[]) => any) | DeeperActions
}
type ActionChainKeys<T, P extends string | number = ''> = UnionToIntersection<{
[K in StringKeyof<T>]: T[K] extends DeeperActions
? ActionChainKeys<T[K], CombineStringKey<P, K>>
: Record<CombineStringKey<P, K>, T[K]>
}[StringKeyof<T>]>
type Promisify<T> = T extends Promise<any> ? T : Promise<T>
declare function dispatch<K extends keyof ActionChainKeys<Actions>>(
key: K,
...args: Parameters<ActionChainKeys<Actions>[K]>
): Promisify<ReturnType<ActionChainKeys<Actions>[K]>>
// 使用示例
let result = dispatch('deeperActions.anotherAction') // Promise<string>
性能优化与注意事项
在实际应用中,我们需要注意以下几点:
- 递归深度限制:TypeScript对递归深度有限制,过深的嵌套会导致类型推导失败
- 性能优化:可以通过减少不必要的递归和类型计算来优化性能
- 备选方案:对于特别复杂的场景,可以考虑使用反向解析的方案
总结
通过利用TypeScript 4.1的模板字符串类型,我们成功地为Mpx框架实现了链式Key的完善类型推导。这一技术不仅解决了数据仓库设计中的类型推导难题,也为其他类似场景提供了参考方案。
TypeScript的类型系统正在变得越来越强大,掌握这些高级特性可以帮助我们构建更健壮、更易维护的应用程序。在Mpx框架的持续演进中,我们将继续探索和应用这些先进的技术,为开发者提供更好的开发体验。
mpx Mpx,一款具有优秀开发体验和深度性能优化的增强型跨端小程序框架 项目地址: https://gitcode.com/gh_mirrors/mp/mpx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考