导读:本期聚焦于苹果创作的《如何在TypeScript类型体操中将对象类型的所有属性类型路径映射到异步生成器类型》,敬请观看详情。把对象里每一层属性的访问路径提取出来,并让每条路径对应一个异步生成器,是类型体操里少有人讲清的难点。直接手写联合类型不仅冗长,还无法应对嵌套结构。本文从索引递归遍历出发,利用模板字面量类型拼接键路径,再将其与异步生成器返回值做条件映射,给出可复用的工具类型。相比单纯用递归别名,该方案能在编译期校验路径合法性,并保留原属性值的类型信息,避免 any 泄露。

在复杂的 TypeScript 项目里,我们经常会遇到这样的需求:给定一个深层嵌套的对象类型,希望把它的每一个叶子属性的访问路径(例如 a.b.c)都抽取出来,并且让每条路径映射到一个返回对应属性类型的异步生成器。这个问题属于类型体操中偏进阶的部分,因为它同时涉及递归对象遍历、模板字面量类型拼接以及异步生成器类型的构造。

如何在TypeScript类型体操中将对象类型的所有属性类型路径映射到异步生成器类型

递归提取对象属性路径的基础实现

要完成映射,第一步必须是把对象类型的所有键路径以字符串联合的形式提取出来。我们不能用简单的 keyof,因为 keyof 只能拿到第一层键。对于嵌套对象,需要写一个新的工具类型,在遇到对象类型时继续向下递归,在遇到基本类型时停止并把当前累积的路径返回。

下面这段代码展示了最基础的路径提取逻辑。这里使用模板字面量类型来拼接父路径与子键,并用条件类型判断当前值是否还是对象。注意我们排除了函数类型,避免把方法也当成可遍历属性。

type Primitive = string | number | boolean | null | undefined | symbol | bigint;

type ExtractPaths<T, Prefix extends string = ''> =
  T extends Primitive
    ? Prefix
    : {
        [K in keyof T & string]:
          ExtractPaths<T[K], Prefix extends '' ? K : `${Prefix}.${K}`>
      }[keyof T & string];

interface Sample {
  id: number;
  user: {
    name: string;
    addr: {
      city: string;
      zip: number;
    };
  };
}

type Paths = ExtractPaths<Sample>;
// 结果: "id" | "user" | "user.name" | "user.addr" | "user.addr.city" | "user.addr.zip"

上面的实现有一个明显的特点:它把中间层级(例如 user 和 user.addr)也作为路径返回了。如果只想要叶子节点,可以再增加一个判断,当子类型仍是对象时不返回自身路径。但保留中间路径在后续映射生成器时反而更灵活,因为我们有时也需要对中间对象整体做异步读取。

这个基础工具类型的缺点是如果遇到循环引用类型会爆栈,但业务模型里极少出现。另外,它对数组支持不友好,若属性是数组,会把索引当成数字键,实际项目中可根据需要扩展。

将路径映射到异步生成器类型

拿到路径联合之后,下一步是构造一个映射类型,让每个路径对应一个函数,该函数返回 AsyncGenerator 并且 yield 或 return 的类型等于原对象中该路径的值类型。这里的核心是利用条件类型分发,对每一个路径字符串做模式匹配,拆出最后一段键,然后回查原类型。

为了简化回查,我们可以先写一个按路径取值的工具类型 GetByPath,再让生成器类型依赖于它。这样逻辑更清晰,也方便单独测试。下面代码演示了完整映射:

type GetByPath<T, P extends string> =
  P extends `${infer K}.${infer Rest}`
    ? K extends keyof T
      ? GetByPath<T[K], Rest>
      : never
    : P extends keyof T
      ? T[P]
      : never;

type PathToAsyncGen<T, P extends string> = {
  [K in P]: () => AsyncGenerator<GetByPath<T, K>, void, unknown>
};

type GenMap = PathToAsyncGen<Sample, Paths>;

// 使用示例
declare const map: GenMap;
async function test() {
  const gen = map['user.addr.city']();
  const res = await gen.next();
  // res.value 类型为 string
}

这里 AsyncGenerator 的第一个类型参数是 yield 的类型,我们直接填目标属性类型,使消费方在迭代时就能获得正确提示。第二个参数是 return 类型,设为 void 表示生成器正常结束不返回业务值。第三个参数是 next 方法接收的参数类型,用 unknown 保持最大兼容。

这种映射方式比手动声明每个异步函数要安全得多。如果原对象增加了字段,只要重新计算 Paths,编译期就会要求补上对应生成器,不会出现漏写。同时因为 GetByPath 在找不到路径时返回 never,若外部传入了错误路径字符串,整体类型直接报错,起到了约束作用。

处理嵌套与可选属性的边界情况

真实业务对象往往带可选属性(?)和深层嵌套,上面方案在可选属性下会推导出 string | undefined 这类联合,这是合理的。但如果某层本身可能是 undefined,递归就会断掉。我们需要在 ExtractPathsGetByPath 里用 NonNullable 做一层保护,确保路径提取不因为可选而丢失分支。

下面展示增强版工具,它去除了可选修饰带来的中断,同时忽略数组索引细节,仅把数组元素类型当作子对象处理。这样在 GraphQL 风格的数据模型里也能工作。

type StrictPaths<T, Prefix extends string = ''> =
  NonNullable<T> extends Primitive
    ? Prefix
    : {
        [K in keyof NonNullable<T> & string]:
          StrictPaths<NonNullable<T>[K], Prefix extends '' ? K : `${Prefix}.${K}`>
      }[keyof NonNullable<T> & string];

type SafeGet<T, P extends string> =
  P extends `${infer K}.${infer Rest}`
    ? K extends keyof NonNullable<T>
      ? SafeGet<NonNullable<T>[K], Rest>
      : never
    : P extends keyof NonNullable<T>
      ? NonNullable<T>[P]
      : never;

type SafeGenMap<T> = PathToAsyncGen<T, StrictPaths<T>>;

interface Account {
  profile?: {
    email: string;
    settings?: {
      theme: number;
    };
  };
}

type FinalMap = SafeGenMap<Account>;
// 即便 profile 可选,依然能映射出 "profile.email" 等路径

从示例可见,即使 profile 是可选的,我们依然能拿到 profile.email 路径并生成对应异步生成器,消费时拿到的是 string 而非 string | undefined,因为生成器内部可以负责处理空值逻辑,类型层面只关心成功产出时的形状。

总结来看,把对象所有属性路径映射到异步生成器,关键点在于先用递归加模板字面量抽路径,再用条件类型做路径回查并套上 AsyncGenerator。掌握这套模式后,类似的「路径到某类型」需求,比如映射到 Promise、映射到校验函数,都能依葫芦画瓢快速实现,大幅提升大型 TS 项目的类型鲁棒性。

TypeScript类型体操异步生成器修改时间:2026-08-18 13:42:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。