You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

整体逻辑拆解

  1. 递归终止条件:T extends Record<PropertyKey, unknown> 判断当前类型T是否为可遍历的对象类型。如果不是(比如string/number这类基础类型),直接返回never,终止递归。
  2. 映射生成单键路径:通过[P in keyof T]遍历对象的每个键P,为每个键生成两种路径:
    • [P]:当前键自身作为一条完整路径(对应直接取当前节点或当前节点是叶子节点的场景)
    • [P, ...Path<T[P]>]:当前键拼接子节点的所有路径(递归调用Path处理子类型T[P],拿到子节点的路径后用展开运算符拼接到当前键后面)
  3. 联合所有路径:最后通过[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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:25:04