仅用TypeScript能否实现对象键路径自动补全及非法路径类型校验?
该需求完全可以仅通过 TypeScript 类型系统实现,不需要额外运行时依赖,核心用到 TypeScript 4.1 版本引入的模板字面量类型、递归条件类型、剩余参数类型推导特性即可完成。
完整实现代码
首先定义通用的路径校验工具类型,再基于工具类型定义目标函数的类型:
/** * 递归生成目标对象所有合法的点分隔路径联合类型 * 例如传入 {a:{b:1}} 会生成 'a' | 'a.b' */ type DotPath<T, Prefix extends string = ''> = T extends object ? { [K in keyof T & string]: Prefix extends '' ? K | DotPath<T[K], `${K}`> : `${Prefix}.${K}` | DotPath<T[K], `${Prefix}.${K}`> }[keyof T & string] : never /** * 递归生成目标对象所有合法的路径元组联合类型 * 例如传入 {a:{b:1}} 会生成 ['a'] | ['a', 'b'] */ type TuplePath<T, Tuple extends string[] = []> = T extends object ? { [K in keyof T & string]: [...Tuple, K] | TuplePath<T[K], [...Tuple, K]> }[keyof T & string] : never /** * 绑定指定对象结构的路径校验函数类型 * 支持两种传参格式:点分隔字符串、多参数拼接路径 */ type PathValidFn<T> = { (path: DotPath<T>): void (...path: TuplePath<T>): void }
使用示例
针对你给出的示例对象,使用效果如下:
const val = { a: { b: { c: 1 } } } // 生成适配 val 结构的校验函数 const myFn: PathValidFn<typeof val> = (pathOrFirst: string, ...rest: string[]) => { // 这里写你自己的业务运行时逻辑,比如路径拼接、取值、遍历等 const fullPath = rest.length ? [pathOrFirst, ...rest].join('.') : pathOrFirst // 示例:根据路径取值 const value = fullPath.split('.').reduce((prev, key) => prev[key as keyof typeof prev], val) console.log(value) } // 合法调用,TS无报错 myFn('a.b.c') myFn('a', 'b', 'c') myFn('a.b') myFn('a') // 非法调用,TS会直接标记类型错误 myFn('a.x.c') // 报错:类型"a.x.c"无法分配给类型"a" | "a.b" | "a.b.c" myFn('a', 'b', 'x') // 报错:类型"x"的参数不能分配给类型"c"的参数
扩展说明
- 如果需要支持数组下标的路径校验,只需将上述工具类型中的
keyof T & string修改为keyof T & (string | number)即可自动适配数字索引 - 如果需要让函数支持任意对象的路径校验(而非提前绑定固定对象),可以将函数改造为泛型函数,把目标对象作为第一个入参即可:
function myFn<T extends object>(obj: T, path: DotPath<T>): unknown function myFn<T extends object>(obj: T, ...path: TuplePath<T>): unknown function myFn<T extends object>(obj: T, pathOrFirst: string, ...rest: string[]) { const fullPath = rest.length ? [pathOrFirst, ...rest].join('.') : pathOrFirst return fullPath.split('.').reduce((prev, key) => prev[key as keyof typeof prev], obj) } // 调用示例 myFn(val, 'a.b.c') // 正常返回1 myFn(val, 'a', 'b', 'c') // 正常返回1 myFn(val, 'a.x') // TS报错
内容的提问来源于stack exchange,提问作者tom10271
相关产品推荐
相关产品推荐

