TypeScript映射类型分配性疑问:Tree Path Array解法解析
TypeScript Tree Path Array 类型解法原理解析
先明确核心解法代码:
type Path<T> = T extends Record<PropertyKey, unknown> ? { [P in keyof T]: [P, ...Path<T[P]>] | [P]; }[keyof T] : never;
整体逻辑拆解
- 递归终止条件:
T extends Record<PropertyKey, unknown>判断当前类型T是否为可遍历的对象类型。如果不是(比如string/number这类基础类型),直接返回never,终止递归。 - 映射生成单键路径:通过
[P in keyof T]遍历对象的每个键P,为每个键生成两种路径:[P]:当前键自身作为一条完整路径(对应直接取当前节点或当前节点是叶子节点的场景)[P, ...Path<T[P]>]:当前键拼接子节点的所有路径(递归调用Path处理子类型T[P],拿到子节点的路径后用展开运算符拼接到当前键后面)
- 联合所有路径:最后通过
[keyof T]索引访问映射生成的对象类型,将每个键对应的路径类型合并成一个大的联合类型。
针对你的疑问:映射类型后的展开逻辑
你提到的{ baz: ['baz', ...['b'] | ['c']] | ['baz'] }这一步,本质是映射类型为baz键生成了对应的路径联合类型['baz','b'] | ['baz','c'] | ['baz']。而[keyof T]的作用不是触发分配性条件类型,而是利用索引访问类型的特性:当用联合类型的键(keyof T在这里是'bar' | 'baz')去索引一个对象类型时,TypeScript会自动将每个键对应的取值类型合并成新的联合类型。
举个简化的例子理解这个特性:
type DemoObj = { foo: 'a' | 'b', bar: 'c' | 'd' }; // 用联合键索引后,得到所有值的联合类型 type DemoVal = DemoObj[keyof DemoObj]; // 'a' | 'b' | 'c' | 'd'
回到你的测试案例:
- 映射生成的对象类型是:
{ bar: ['bar'] | ['bar', 'a'], baz: ['baz'] | ['baz', 'b'] | ['baz', 'c'] } - 用
keyof T('bar' | 'baz')索引这个对象,就会把bar对应的联合类型和baz对应的联合类型合并,最终得到你看到的结果:['bar'] | ['baz'] | ['bar', 'a'] | ['baz', 'b'] | ['baz', 'c']
纠正分配性条件类型的误解
分配性条件类型仅发生在T extends U ? X : Y结构中,且T是联合类型的场景——此时TypeScript会将联合类型的每个成员单独代入条件判断,再将结果合并成联合类型。而这里的展开逻辑是索引访问类型对映射对象的联合展开,和分配性条件类型是完全不同的机制。
内容的提问来源于stack exchange,提问作者stratis
相关产品推荐
相关产品推荐

