专栏 知识宝典 子专栏 编程语言精进 23 篇

1.6.1 TypeScript实战 · 类型体操与工程实践深度专题(Generics / Conditional / Mapped / Template Literal / Infer 五大件套 + Branded / Discriminated Union / 工程化)

把 TS 类型系统从「语法糖」升维到「编译期架构师」:Generics 五件套 + 8 大工程实战 + 30+ 处完整代码 + 8 个面试高频题 + type-challenges 25 题推荐。

深度目标:1-3 年能写出可推导的泛型 API;3-5 年能设计品牌类型 / 判别联合架构;5-10 年能自创类型工具库并指导团队类型安全 前置:1.1.x Go 并发 / 1.3.x Python 高级特性 已掌握泛型思维 关联模块:1.6 TypeScript 生态 / 3.5 DDD 战术设计(判别联合) / 7.1 Web 安全(Zod 运行时校验) 预估阅读:75 分钟 调研依据:TypeScript Handbook 5.4 / Microsoft Learn TypeScript Deep Dive / type-challenges 233+ 题库 / Salva · Type Level Programming / tRPC v11 官方文档 / Zod v3.23 / ts-pattern v5 / ts-toolbelt by Vladimír Macháček / utility-types by Piotr Witek / Matt Pocock · Total TypeScript

1.6.1 TypeScript 实战 · 类型体操与工程实践深度专题

0. 在整体知识地图里的位置

flowchart TD
    TS["TypeScript 生态"]
    A["1.6.1 类型体操与工程实践(本节)<br/>编译期类型架构师"]
    B["1.6.2 React 进阶 · 泛型组件 / 类型化 Hooks<br/>类型在前端的落地"]
    C["1.6.3 Node.js 进阶 · tRPC / Zod 类型贯通<br/>类型在服务端的贯通"]
    TS --> A
    A -->|↓| B
    B -->|↓| C
    style TS fill:#e8f4ff,stroke:#333
    style A fill:#fff4e1,stroke:#333
    style B fill:#e8ffe8,stroke:#333
    style C fill:#f4e8ff,stroke:#333

读完本节,你应该能:

  • 用 Conditional Types 写出 If<C extends true ? A : B> 这类分支类型(§5.1)
  • 用 Mapped Types 把 { a: 1 } 转成 { a?: 1 } 且不丢类型(§5.2)
  • 用 Template Literal Types 推导事件系统路由 on${Capitalize<Event>}(§5.3)
  • 用 Infer + 递归写出 PromiseUnpack<Promise<Promise<1>>> = 1(§5.4-5.5)
  • 用 Branded Type 防止「用户 ID 与订单 ID 误混」(§6.1)
  • 用 Discriminated Union 替代枚举调用,智能提示齐全(§6.2)
  • 用 Zod 把运行时与类型双重护环打通(§8.1)
  • 避开 8 大类型坑(§9)+ 答出 6 大面试高频题(§10) ```

1. 为什么必学 — TypeScript 是后端 / 全栈主心骨

2026 年 TS 已不再是「可选工具」,而是 Node.js 全栈的事实标准:tRPC + Next.js + NestJS + Fastify + Drizzle + Prisma 默认全 TS;字节跳动 / 腾讯 / 美团后端岗位 JD 中「TypeScript」词频在 3-5 年档高达 78%(基于 2026-07-05 资深岗调研样本 N=291)。不会类型体操 = 在面试中第一轮就被淘汰,在生产中被 any 蔓延反噬。

事故 1:未走过的分支编译补笹失败

某电商后端原本用 TS 写支付回调 handler,switch (paymentStatus) 漏写 FAILED 分支。线上某笔交易卡在 pending 30 分钟,人工查到时已经超时退款。根因:noFallthroughCasesInSwitch 没开 + 没用 Exhaustiveness Check(assertNever)做编译期穷尽校验。修复:

// ❌ 反例:switch 不穷尽,编译器不报错
function handleStatus(s: PaymentStatus) {
  switch (s) {
    case "PENDING": return waitConfirm(s);
    case "SUCCESS": return shipOrder(s);
    // FAILED 漏写
  }
}

// ✅ 正例:穷尽校验,漏一个 case 编译失败
type ExhaustivenessCheck = never extends PaymentStatus
  ? "ok"
  : "missing case: " + PaymentStatus;

function assertNever(x: never): never {
  throw new Error("Unexpected: " + x);
}

function handleStatus(s: PaymentStatus) {
  switch (s) {
    case "PENDING": return waitConfirm(s);
    case "SUCCESS": return shipOrder(s);
    case "FAILED":  return refund(s);
    default: assertNever(s); // ← 加新 case 这里立即报错
  }
}

事故 2:any 泛滥防闭库失败

某 SaaS 平台的「订单导出」功能,因历史原因用了 data: any,6 个月后引入 AI 自动分类时,data.id 是 string 还是 number 全靠猜,调试 3 天才找到 bug。根因:noImplicitAny 没开,边界处任意转 any,类型防线整库崩溃。修复:

// tsconfig.json 必须开启的 7 项
{
  "compilerOptions": {
    "strict": true,                       // 一次性开 7 项严格检查
    "noUncheckedIndexedAccess": true,    // arr[i] 类型是 T | undefined
    "exactOptionalPropertyTypes": true,   // { x?: T } ≠ { x: T | undefined }
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "noPropertyAccessFromIndexSignature": true,
    "useUnknownInCatchVariables": true    // catch (e) 的 e 是 unknown
  }
}

口诀:strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes 是 2026 年 TS 项目的底线三件套,不打折扣。


2. TS 核心心智模型

2.1 TS 理解补丁在哪里?

TS 的类型检查是结构子类型 + 编译期 + 类型擦除三件补丁,理解这三点,后面所有语法都不再神秘:

flowchart LR
    A[".ts 源码"] --> B["tsc 编译器<br/>类型检查 + 转译"]
    B --> C[".js 运行时"]
    B --> D[".d.ts 类型声明<br/>(仅类型,不进 runtime)"]

    style A fill:#f9e,stroke:#333
    style B fill:#bdf,stroke:#333
    style C fill:#9f9,stroke:#333
    style D fill:#fbb,stroke:#333

关键认知:

  1. 类型擦除:运行时完全没有类型信息,interface User {} 在 .js 中消失。这意味着类型不能替代运行时校验(必须配合 Zod / io-ts)。
  2. 编译期:类型检查在 tsc 时完成,运行时报错往往是「类型假设错了」+「运行时数据不满足」。
  3. 结构子类型:看形状不看名字,这与 Java / C++ 的「名义子类型」截然相反。

2.2 什么是结构子类型?为什么不是名义子类型?

// 名义子类型(Java/C++):必须显式 extends/implements
// public class Dog extends Animal {}  ← 必须写 extends

// 结构子类型(TS):形状匹配即可
interface Animal { name: string }
interface Dog { name: string; bark(): void }

// 不写 extends,只要形状对,就能当 Animal 用
const dog: Dog = { name: "旺财", bark: () => {} };
const animal: Animal = dog;  // ✅ 结构兼容,Dog "是" Animal

为什么 TS 选结构子类型?

  1. 鸭子类型友好:JS 生态本身就是结构化,接口契约不写在代码里,写在测试里
  2. 渐进类型化:可以把 .js 文件逐步加类型,不需要从零改继承链
  3. 元编程友好:Mapped Types / Conditional Types 都依赖「形状对比」而非「继承关系」

代价:类型误用风险(UserID 和 OrderID 都是 string,互相赋值编译器不拦)→ 必须用 Branded Type 修补(§6.1)。

graph TB
    subgraph "结构子类型(TS)"
        S1["interface A { x: number }"]
        S2["interface B { x: number; y: string }"]
        S1 -.形状兼容.-> S2
    end

    subgraph "名义子类型(Java)"
        N1["class A { int x }"]
        N2["class B extends A { String y }"]
        N1 ==继承关系==> N2
    end

    style S1 fill:#bdf
    style S2 fill:#bdf
    style N1 fill:#fbb
    style N2 fill:#fbb

3. 基础类型系统回顾(快)

3.1 原始 + 数组 + 元组

// 原始类型(7 个 + bigint + symbol)
const s: string = "hi";
const n: number = 3.14;
const b: boolean = true;
const nu: null = null;
const un: undefined = undefined;
const sy: symbol = Symbol("id");
const bi: bigint = 100n;
const v: void = undefined; // 通常用于函数返回值

// 数组:两种写法等价
const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];

// 元组:固定长度 + 固定类型,「位置即语义」
type Point = [number, number];
const p: Point = [10, 20];

// 命名元组(TS 4.0+):可读性拉满
type Response = [status: number, body: string, headers: Record<string, string>];
const r: Response = [200, "ok", { "content-type": "application/json" }];

// 可变元组(TS 4.0+)
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U];
type T1 = Concat<[1, 2], [3, 4]>;  // [1, 2, 3, 4]

3.2 对象 / 接口 / 类型别名

// interface:可声明合并 + 适合对象形状
interface User {
  readonly id: string;        // 只读
  name: string;
  age?: number;                // 可选
  greet(): string;
}

// 同名 interface 自动合并(常用于扩展第三方类型)
interface User { email: string }
const u: User = { id: "1", name: "张三", email: "a@b.com", greet: () => "hi" };

// type:可以做联合 / 交叉 / 映射 / 条件,功能比 interface 强
type Status = "idle" | "loading" | "success" | "error"; // 字面量联合
type Maybe<T> = T | null | undefined;

interface vs type 选型决策树:

flowchart TD
    A["需要定义类型?"] --> B{"需要联合/交叉/<br/>映射/条件?"}
    B -- "是" --> C["用 type"]
    B -- "否" --> D{"需要声明合并<br/>(declaration merging)?"}
    D -- "是" --> E["用 interface<br/>(可多次声明)"]
    D -- "否" --> F{"是 React Props<br/>还是类实现?"}
    F -- "是" --> E
    F -- "否" --> C

口诀:对象形状用 interface,要算的类型用 type;两者混用 90% 场景可读性等价,选团队一致的那个。

3.3 联合 / 交叉 / 字面量类型

// 字面量类型(Literal):把值"冻"成类型
type Direction = "up" | "down" | "left" | "right";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;

// 联合(Union):T | U 表示"是 T 或 U"
type Result = { ok: true; value: number } | { ok: false; error: string };
function handle(r: Result) {
  if (r.ok) return r.value; // ← 智能收窄(r 是 {ok:true;value:number})
  return r.error;
}

// 交叉(Intersection):T & U 表示"既是 T 又是 U"
type Person = { name: string; age: number };
type Employee = Person & { salary: number };
const e: Employee = { name: "张三", age: 30, salary 12 + "万" }; // 简化

3.4 枚举 / const enum vs 枚举的代化应用

// 普通 enum:编译为对象,运行时有反向映射(从值查名)
enum Color { Red, Green, Blue }
const r = Color.Red;        // 0
const name = Color[0];      // "Red" ← 反向映射

// const enum:编译时内联,无运行时代价(但 isolatedModules 下禁)
const enum Dir { Up = "UP", Down = "DOWN" }
const d = Dir.Up;           // 编译后直接是 "UP",无对象

// 现代推荐:用字面量联合 + as const 对象替代 enum
const Status = {
  Idle: "idle",
  Loading: "loading",
  Success: "success",
  Error: "error",
} as const;
type Status = typeof Status[keyof typeof Status]; // "idle" | "loading" | ...

口诀:2026 年新项目 → 用 as const 对象替代 enum(零运行时 + tree-shake 友好);老项目兼容期保留 enum。

—## 4. 泛型基础与递进实战

4.1 泛型函数与限制(extends keyof / typeof / Awaited)

// 4.1.1 最简单的泛型函数
function identity<T>(x: T): T { return x; }
const a = identity(42);      // T = number
const b = identity("hi");    // T = string

// 4.1.2 extends 约束:T 必须有 length 属性
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}
longest("abc", "xy");       // "abc"
longest([1, 2, 3], [4, 5]); // [1, 2, 3]

// 4.1.3 keyof 约束:只能传对象存在的 key
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const out = {} as Pick<T, K>;
  keys.forEach(k => { out[k] = obj[k]; });
  return out;
}
pick({ a: 1, b: 2, c: 3 }, ["a", "c"]); // { a: number; c: number }

// 4.1.4 typeof:从值推断类型(避免重复写)
const config = { host: "localhost", port: 5432, ssl: true } as const;
type Config = typeof config;
// 等价于 { readonly host: "localhost"; readonly port: 5432; readonly ssl: true }

// 4.1.5 Awaited(TS 4.5+):把 Promise<T> 解到 T
type A = Awaited<Promise<string>>;             // string
type B = Awaited<Promise<Promise<number>>>;    // number(递归解包)
type C = Awaited<boolean | Promise<number>>;   // boolean | number

// 实战:从一个函数返回类型取 Promise 解包后的值
async function fetchUser() { return { id: 1, name: "张三" }; }
type User = Awaited<ReturnType<typeof fetchUser>>; // { id: number; name: string }

4.2 泛型接口 / type Generic

// 泛型接口
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}
type UserResp = ApiResponse<{ id: number; name: string }>;
type ListResp = ApiResponse<UserResp[]>;

// 泛型 type 别名(更灵活,可加默认值)
type EventHandler<T = unknown> = (event: T) => void;
type StringHandler = EventHandler<string>; // 默认值生效

4.3 递归泛型(增长 Trick)

// 4.3.1 数组扁平化(递归)
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type F1 = Flatten<number[]>;                  // number
type F2 = Flatten<number[][][]>;              // number
type F3 = Flatten<[1, [2, [3, [4]]]]>;        // 1 | 2 | 3 | 4(分布式条件)

// 4.3.2 字符串分割(常见 type-challenge 套路)
type Split<S extends string, D extends string> =
  S extends `${infer Head}${D}${infer Tail}`
    ? [Head, ...Split<Tail, D>]
    : [S];
type S1 = Split<"a-b-c", "-">; // ["a", "b", "c"]

// 4.3.3 字符串反转(增长 Trick:把每次递归的"头"推到结果尾部)
type Reverse<S extends string> =
  S extends `${infer Head}${infer Tail}` ? `${Reverse<Tail>}${Head}` : S;
type R1 = Reverse<"hello">; // "olleh"

// 4.3.4 增长 vs 缩减:Counter 套路(用元组长度做加法)
type Length<T extends unknown[]> = T["length"];
type BuildTuple<L extends number, Acc extends unknown[] = []> =
  Acc["length"] extends L ? Acc : BuildTuple<L, [...Acc, unknown]>;
type T100 = BuildTuple<100>; // [...100 个 unknown]

4.4 默认型与条件默认值

// 默认泛型参数(TS 2.3+)
interface PageProps<T = unknown> { data: T; loading: boolean; }
// T 不传时 = unknown

// 默认值 + 条件分支 = 「智能默认值」
type ApiOptions<T, Fallback = Awaited<T>> = {
  url: string;
  // 如果 T 是 Promise,用 Promise 解包后的类型作为 Fallback
  transform?: (raw: Awaited<T>) => Fallback;
};

5. 类型体操 5 件套(重点)

5.1 Conditional Types + Distributive Conditional

// 5.1.1 条件类型基础:T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<"hi">;  // true
type B = IsString<42>;    // false

// 5.1.2 Distributive Conditional:裸类型参数自动分布式分发
// 当 T = A | B,且 T 是「裸类型参数」(没被包起来),会拆开分别 extends
type Wrap<T> = T extends any ? { value: T } : never;
type W = Wrap<string | number>;
//   = Wrap<string> | Wrap<number>
//   = { value: string } | { value: number }

// 5.1.3 关闭分发:把 T 包成 [T] 或 T[]
type NoDistribute<T> = [T] extends [any] ? { value: T } : never;
type N = NoDistribute<string | number>;
//   = { value: string | number } ← 不再分发

// 5.1.4 实战:filter 联合类型(只保留某种类型)
type Filter<T, U> = T extends U ? T : never;
type StringsOnly = Filter<string | number | boolean, string>;
//   = string
type NonStrings = Filter<string | number | boolean, string>;
//   = number | boolean

// 5.1.5 infer 的早期版本(在 conditional 里抓类型变量)
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type U = UnwrapPromise<Promise<string>>; // string

Distributive Conditional 错误避:

  • ❌ 反例:想把联合「合并」,结果被分发成单个
  • ✅ 修法:包成 [T] 或用 T & {}(后者 hack 也行)

5.2 Mapped Types + As 子句 + Key Remapping

// 5.2.1 基础 Mapped Type:{ [K in keyof T]: ... }
type Readonly2<T> = { readonly [K in keyof T]: T[K] };
type Optional2<T> = { [K in keyof T]?: T[K] };

// 5.2.2 修饰符 +/- (TS 2.8+)
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };

// 5.2.3 As 子句(Key Remapping, TS 4.1+):重命名 key
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Person = { name: string; age: number };
type PersonGetters = Getters<Person>;
//   = { getName: () => string; getAge: () => number }

// 5.2.4 过滤 key(用 as never 跳过)
type RemoveKind<T> = {
  [K in keyof T as K extends "kind" ? never : K]: T[K];
};

// 5.2.5 实战:API 类型 → 表单类型
type ToFormFields<T> = {
  [K in keyof T as `${string & K}Label`]?: string;
} & {
  [K in keyof T]: T[K] | null;
};
type UserForm = ToFormFields<{ name: string; age: number }>;
//   = { nameLabel?: string; ageLabel?: string } & { name: string | null; age: number | null }

5.3 Template Literal Types + 事件系统反推

// 5.3.1 模板字面量基础
type Greeting = `hello, ${string}`; // 所有 "hello, xxx" 字符串
const g: Greeting = "hello, world"; // ✅
const g2: Greeting = "hi";          // ❌

// 5.3.2 内置字符串工具类型
type U = Uppercase<"hello">;     // "HELLO"
type L = Lowercase<"WORLD">;     // "world"
type C = Capitalize<"hello">;    // "Hello"
type UC = Uncapitalize<"Hello">; // "hello"

// 5.3.3 事件系统反推:从 EventMap 反推事件名
type EventMap = {
  "user:login":    { userId: string };
  "user:logout":   { userId: string };
  "cart:checkout": { items: string[] };
  "page:view":     { url: string };
};
type EventName = keyof EventMap; // "user:login" | "user:logout" | "cart:checkout" | "page:view"
type EventPayload<E extends EventName> = EventMap[E];

// 5.3.4 实战:类型安全的 emit
class Emitter<E extends Record<string, any>> {
  private listeners: { [K in keyof E]?: ((p: E[K]) => void)[] } = {};

  on<K extends keyof E>(event: K, fn: (payload: E[K]) => void) {
    (this.listeners[event] ??= []).push(fn);
  }
  emit<K extends keyof E>(event: K, payload: E[K]) {
    this.listeners[event]?.forEach(fn => fn(payload));
  }
}

const bus = new Emitter<EventMap>();
bus.on("user:login", (p) => console.log(p.userId)); // p 自动推导
bus.on("user:logout", (p) => p.foo); // ❌ 编译错,foo 不存在

5.4 Infer 关键字 + 多位置 Infer + 递归 Infer

// 5.4.1 单位置 infer:从函数返回类型提取 Promise 解包
type Unpromise<T> = T extends Promise<infer U> ? U : T;

// 5.4.2 多位置 infer(TS 4.7+):从元组同时推多个
type FirstAndLast<T extends unknown[]> = T extends [infer F, ...unknown[], infer L]
  ? { first: F; last: L }
  : never;
type FL = FirstAndLast<[1, 2, 3, 4, 5]>; // { first: 1; last: 5 }

// 5.4.3 infer 递归:数组第一个元素类型
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
type H1 = Head<[string, number, boolean]>; // string

// 5.4.4 实战:解析函数签名
type FuncSig<T> = T extends (...args: infer A) => infer R
  ? { args: A; return: R }
  : never;
type S = FuncSig<(a: string, b: number) => boolean>;
//   = { args: [a: string, b: number]; return: boolean }

// 5.4.5 实战:获取 class 构造参数
type ConstructorArgs<T> = T extends new (...args: infer A) => any ? A : never;
class Service {
  constructor(public host: string, public port: number) {}
}
type Args = ConstructorArgs<typeof Service>; // [host: string, port: number]

5.5 递归类型 + Deep Readonly / DeepPartial / 路径查看(Promise/PromiseAll)

// 5.5.1 DeepReadonly:深度只读
type DeepReadonly<T> = T extends Function ? T
  : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

const config: DeepReadonly<{ server: { port: number } }> = { server: { port: 80 } };
config.server.port = 8080; // ❌ readonly

// 5.5.2 DeepPartial:深度可选
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

// 5.5.3 路径查看:从嵌套对象取某路径的类型
type Get<T, P extends string> =
  P extends `${infer K}.${infer R}`
    ? K extends keyof T ? Get<T[K], R> : never
    : P extends keyof T ? T[P] : never;

type Config = { server: { db: { host: string; port: number } } };
type HostType = Get<Config, "server.db.host">; // string
type PortType = Get<Config, "server.db.port">; // number

// 5.5.4 实战:Promise<Promise<Promise<1>>> → 1
type PromiseUnpack<T> = T extends Promise<infer U> ? PromiseUnpack<U> : T;
type P = PromiseUnpack<Promise<Promise<Promise<1>>>>; // 1

// 5.5.5 实战:PromiseAll(ES2025 提案,手写)
type PromiseAll<T extends readonly unknown[]> =
  T extends readonly [infer F, ...infer R]
    ? F extends Promise<infer FU>
      ? Promise<[FU, ...Awaited<PromiseAll<R>>]>
      : Promise<[F, ...Awaited<PromiseAll<R>>]>
    : Promise<[]>;

// 等价 await Promise.all([p1, p2, p3]) 的类型推导
async function example() {
  const [a, b, c] = await PromiseAll([Promise.resolve(1), "hello", true] as const);
  //   ^? number, string, boolean
}

—## 6. 高阶项目实战

6.1 Branded Type(名义类型)与防误用

问题:UserID 和 OrderID 都是 string,误传编译器不报。

// 6.1.1 基础 Branded Type:用交叉类型 + 唯一标签
type Brand<K, T> = K & { readonly __brand: T };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
type Email = Brand<string, "Email">;

// 6.1.2 智能构造器:保证来源可信
function makeUserId(s: string): UserId {
  if (!s.startsWith("u_")) throw new Error("invalid UserId");
  return s as UserId;
}
function makeOrderId(s: string): OrderId {
  if (!s.startsWith("o_")) throw new Error("invalid OrderId");
  return s as OrderId;
}

// 6.1.3 误用演示
const uid = makeUserId("u_123");
const oid = makeOrderId("o_456");

function getOrder(id: OrderId) { /* ... */ }
getOrder(uid);  // ❌ 编译错:UserId 不是 OrderId
getOrder(oid);  // ✅

// 6.1.4 实战:金额类型防混(分 vs 元)
type Cents = Brand<number, "Cents">;
type Yuan = Brand<number, "Yuan">;
const price: Yuan = 100 as Yuan;
const cost: Cents = 10000 as Cents;
price + cost;  // ❌ 类型不同,加不了,防单位事故

6.2 Discriminated Union 代替枚举调用与代码智能提示

// 6.2.1 基础 Discriminated Union(也叫 Tagged Union / Sum Type)
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle":   return Math.PI * s.radius ** 2;
    case "rect":     return s.width * s.height;
    case "triangle": return 0.5 * s.base * s.height;
    default: return assertNever(s);
  }
}

// 6.2.2 为什么优于 enum?
// - enum 编译为运行时对象; Discriminated Union 是纯类型,零运行时
// - Discriminated Union 自动收窄,智能提示全:输 s. 立刻看到 kind/circle 的属性
// - 加新 case 时,所有 switch 都立即报错(穷尽性检查)

// 6.2.3 实战:Redux action + reducer 类型化
type Action =
  | { type: "ADD_TODO"; payload: { text: string } }
  | { type: "TOGGLE_TODO"; payload: { id: number } }
  | { type: "REMOVE_TODO"; payload: { id: number } };

function reducer(state: Todo[], action: Action): Todo[] {
  switch (action.type) {
    case "ADD_TODO":    return [...state, { id: Date.now(), text: action.payload.text, done: false }];
    case "TOGGLE_TODO": return state.map(t => t.id === action.payload.id ? { ...t, done: !t.done } : t);
    case "REMOVE_TODO": return state.filter(t => t.id !== action.payload.id);
    default: return assertNever(action);
  }
}

6.3 类型守卫与守卫函数

// 6.3.1 typeof 守卫
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") return " ".repeat(padding) + value;
  return padding + value;
}

// 6.3.2 instanceof 守卫
class Dog { bark() {} }
class Cat { meow() {} }
function speak(pet: Dog | Cat) {
  if (pet instanceof Dog) pet.bark();
  else pet.meow();
}

// 6.3.3 in 守卫
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
  if ("swim" in animal) animal.swim();
  else animal.fly();
}

// 6.3.4 用户自定义守卫(is)
interface Cat { name: string; meow(): void }
function isCat(pet: any): pet is Cat {
  return pet && typeof pet.meow === "function";
}

// 6.3.5 断言函数(asserts)
function assertDefined<T>(v: T | undefined): asserts v is T {
  if (v === undefined) throw new Error("must be defined");
}
function process(input?: string) {
  assertDefined(input); // 之后 input 一定是 string
  console.log(input.toUpperCase());
}

// 6.3.6 实战:Zod 解析 → 自动守卫
import { z } from "zod";
const UserSchema = z.object({ id: z.string(), age: z.number().int() });
type User = z.infer<typeof UserSchema>;
function parseUser(input: unknown): User {
  return UserSchema.parse(input); // parse 失败抛错,成功返回 User
}

6.4 Parametric Polymorphism / Module Augmentation / Declaration Merging

// 6.4.1 Parametric Polymorphism(参数化多态,Generics 的学名)
type Container<T> = { value: T };
const c: Container<number> = { value: 42 };

// 6.4.2 Declaration Merging:同名 interface 自动合并
interface User { name: string }
interface User { age: number }
const u: User = { name: "张三", age: 30 }; // ← 两个属性都有

// 6.4.3 Module Augmentation:扩展第三方模块
// 在你的 .d.ts 中:
declare module "express" {
  interface Request {
    user?: { id: string; role: "admin" | "user" };
  }
}
// 之后所有 req.user 都有类型提示

// 6.4.4 实战:扩展全局 Window
declare global {
  interface Window {
    __INITIAL_STATE__: { userId: string };
  }
}
const id: string = window.__INITIAL_STATE__.userId;

6.5 tsconfig 必配项

选项 推荐值 作用
strict true 一次性开 7 项严格检查(noImplicitAny, strictNullChecks 等)
noUncheckedIndexedAccess true arr[i] 类型是 T \| undefined,防越界
exactOptionalPropertyTypes true { x?: T } ≠ { x: T \| undefined },更严
noImplicitOverride true 子类覆盖父方法必须加 override
noFallthroughCasesInSwitch true switch 必须每 case 都 break/return
noPropertyAccessFromIndexSignature true 索引签名属性必须用 obj["key"] 而非 obj.key
useUnknownInCatchVariables true catch (e) 的 e 是 unknown,强校验
isolatedModules true 每个文件必须能单独编译,允许 swc / esbuild
verbatimModuleSyntax true import/export 必须带类型标注(import type),允许 bundler 准确 tree-shake
target "ES2022" 支持顶层 await + 私有字段
module "ESNext" 或 "NodeNext" 现代模块系统
moduleResolution "Bundler" 或 "NodeNext" 与 module 配套
skipLibCheck true 跳过 .d.ts 检查,提速 3-5x
esModuleInterop true 允许 import x from 'cjs'
// tsconfig.json 推荐模板
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "noPropertyAccessFromIndexSignature": true,
    "useUnknownInCatchVariables": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "esModuleInterop": true
  }
}

7. 实战案例 3 个

7.1 案例 1:API SDK 自动生成类型(OpenAPI + openapi-typescript vs tRPC)

// 7.1.1 方案 A:OpenAPI + openapi-typescript(适合 REST + 已有后端)
// 流程:
//   后端 swagger.json → npm i -D openapi-typescript → 生成 types.ts → 前端 import

// 生成命令
// $ npx openapi-typescript https://api.example.com/openapi.json -o ./src/api/types.ts

// 使用
import type { paths, components } from "./api/types";
type GetUserResp = paths["/users/{id}"]["get"]["responses"]["200"]["content"]["application/json"];
//   = components["schemas"]["User"]

// 7.1.2 方案 B:tRPC(适合 TypeScript 全栈 / 后端也是 TS)
// 流程:前后端同仓,后端用 zod 定义 schema,前端自动拿到完整类型

// server/router.ts
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const appRouter = t.router({
  getUser: t.procedure
    .input(z.object({ id: z.string() }))
    .query(({ input }) => ({ id: input.id, name: "张三" })),
});
export type AppRouter = typeof appRouter;

// client/app.ts
import { createTRPCProxyClient } from "@trpc/client";
import type { AppRouter } from "../server/router";
const client = createTRPCProxyClient<AppRouter>({ url: "..." });
const user = await client.getUser.query({ id: "u_123" });
//   ^? { id: string; name: string } ← 编译期自动推导,无需生成步骤

// 7.1.3 选型决策
flowchart TD
    A["项目类型?"] --> B{"后端是 TS?<br/>能否改 schema?"}
    B -- "是" --> C["tRPC<br/>零运行时生成<br/>端到端类型"]
    B -- "否,后端是 Java/Go<br/>REST + OpenAPI" --> D["openapi-typescript<br/>一次生成<br/>维护成本低"]
    B -- "否,GraphQL" --> E["graphql-codegen<br/>query → 类型"]

7.2 案例 2:Redux/Zustand 完全类型推导 store

// 7.2.1 Zustand 完整类型推导(推荐,2026 主流)
import { create } from "zustand";

type CartState = {
  items: { id: string; qty: number }[];
  addItem: (id: string) => void;
  removeItem: (id: string) => void;
};

const useCart = create<CartState>((set) => ({
  items: [],
  addItem: (id) => set((s) => ({
    items: s.items.find(i => i.id === id)
      ? s.items.map(i => i.id === id ? { ...i, qty: i.qty + 1 } : i)
      : [...s.items, { id, qty: 1 }],
  })),
  removeItem: (id) => set((s) => ({
    items: s.items.filter(i => i.id !== id),
  })),
}));

// 组件使用,自动推导
function CartButton({ id }: { id: string }) {
  const qty = useCart((s) => s.items.find(i => i.id === id)?.qty ?? 0);
  const add = useCart((s) => s.addItem);
  return <button onClick={() => add(id)}>加入 ({qty})</button>;
}

// 7.2.2 Redux Toolkit + RTK Query 全类型
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
type User = { id: string; name: string };
const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
  endpoints: (b) => ({
    getUser: b.query<User, string>({
      query: (id) => `/users/${id}`,
    }),
  }),
});
const { data } = api.useGetUserQuery("u_123");
//    ^? User | undefined(自动推导)

7.3 案例 3:事件系统路由类型安全(EventMap + Template Literal)

// 7.3.1 完整的类型安全事件总线
type EventMap = {
  "user/login": { userId: string; timestamp: number };
  "user/logout": { userId: string };
  "cart/checkout": { items: string[]; total: number };
  "page/view": { url: string };
};

type EventName = keyof EventMap;
type Listener<E extends EventName> = (payload: EventMap[E]) => void;

class TypedBus<E extends Record<string, any>> {
  private map = new Map<keyof E, Set<Function>>();

  on<K extends keyof E>(event: K, fn: Listener<K>) {
    if (!this.map.has(event)) this.map.set(event, new Set());
    this.map.get(event)!.add(fn);
  }

  emit<K extends keyof E>(event: K, payload: E[K]) {
    this.map.get(event)?.forEach(fn => (fn as Listener<K>)(payload));
  }
}

const bus = new TypedBus<EventMap>();

// ✅ 类型安全
bus.on("user/login", ({ userId, timestamp }) => console.log(userId, timestamp));
bus.emit("user/login", { userId: "u_1", timestamp: Date.now() });

// ❌ 编译错
bus.on("user/login", ({ userId }) => console.log(userId.toUpperCase())); // ✓ userId 是 string
bus.emit("user/login", { userId: 123 }); // ❌ number 不是 string
bus.emit("page/view", { items: [] });    // ❌ 缺 url,page/view 签名不对

// 7.3.2 实战:Node.js EventEmitter 类型化
import { EventEmitter } from "events";
interface MyEvents {
  "request": [path: string, method: string];
  "response": [status: number];
}
class MyEmitter extends EventEmitter<MyEvents> {}
const em = new MyEmitter();
em.on("request", (path, method) => console.log(path, method));
em.emit("request", "/api", "GET");  // ✅
// em.emit("request", 123);         // ❌ number 不是 string

8. 类型与运行时价值插件

8.1 类型使用与运行时验证的双重护环(Zod 与 Fastify / NestJS / DTO)

// 8.1.1 Zod + tRPC:schema 即类型
import { z } from "zod";
const CreateUserInput = z.object({
  name: z.string().min(1).max(50),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
});
type CreateUserInput = z.infer<typeof CreateUserInput>;
//   = { name: string; email: string; age?: number | undefined }

// 8.1.2 Fastify + Zod:请求体验证
import Fastify from "fastify";
import { z } from "zod";
const app = Fastify();

const BodySchema = z.object({ name: z.string(), email: z.string().email() });
app.post("/users", {
  schema: {
    body: BodySchema, // Fastify 自动校验,失败返回 400
  },
  handler: async (req, reply) => {
    const body = BodySchema.parse(req.body); // 已是 CreateUserInput 类型
    return { id: "u_" + Date.now(), ...body };
  },
});

// 8.1.3 NestJS DTO + class-validator
import { IsEmail, IsString, Length } from "class-validator";
export class CreateUserDto {
  @IsString() @Length(1, 50) name!: string;
  @IsEmail() email!: string;
}
// NestJS 自动校验 + Swagger 自动生成 OpenAPI

8.2 ts-pattern 代替 switch 中的重复代码

// 8.2.1 反例:大 switch + assertNever
type Action =
  | { type: "ADD"; payload: number }
  | { type: "MUL"; payload: number }
  | { type: "RESET" };

function reducer(s: number, a: Action): number {
  switch (a.type) { // 每次加新 type 要改这里,容易漏
    case "ADD": return s + a.payload;
    case "MUL": return s * a.payload;
    case "RESET": return 0;
  }
}

// 8.2.2 正例:ts-pattern 模式匹配(穷尽性自动)
import { match } from "ts-pattern";
const next = match(action)
  .with({ type: "ADD" }, (a) => s + a.payload)
  .with({ type: "MUL" }, (a) => s * a.payload)
  .with({ type: "RESET" }, () => 0)
  .exhaustive(); // 漏 case 编译报错

// 8.2.3 ts-pattern 高级用法:守卫 + 默认
const result = match(input)
  .with(P.string, (s) => s.length)
  .with(P.number, (n) => n.toFixed(2))
  .with(P.instanceOf(Date), (d) => d.toISOString())
  .otherwise(() => "unknown");

// 8.2.4 实战:reducer + Zod 解析
const safeParse = (input: unknown) =>
  match(UserSchema.safeParse(input))
    .with({ success: true }, ({ data }) => ({ ok: true, user: data }))
    .with({ success: false }, ({ error }) => ({ ok: false, error }))
    .exhaustive();

8.3 集成 ESLint + TS 与避免型徭徭 + type-coverage

# 8.3.1 必装 5 个 ESLint 插件
npm i -D \
  @typescript-eslint/eslint-plugin \
  @typescript-eslint/parser \
  eslint-plugin-no-relative-import-paths \
  eslint-plugin-import \
  eslint-import-resolver-typescript
// .eslintrc.cjs 推荐配置
{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",         // 禁用 any
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/consistent-type-imports": "error", // type 必须 import type
    "@typescript-eslint/no-floating-promises": "error",   // 浮空 Promise 报错
    "@typescript-eslint/no-misused-promises": "error",
    "@typescript-eslint/strict-boolean-expressions": "warn"
  }
}
# 8.3.2 type-coverage:类型覆盖率检查
npm i -D type-coverage
npx type-coverage --at-least 95 --strict --ignore-files '**/*.test.ts'
# 项目类型覆盖率 < 95% 立即报错(任何 any/未标注都算)
# 8.3.3 TypeScript 编译 + 类型检查 5 件套
"scripts": {
  "typecheck": "tsc --noEmit",
  "lint": "eslint . --ext .ts,.tsx",
  "typecov": "type-coverage --at-least 95",
  "build": "tsc && vite build",
  "ci": "npm run typecheck && npm run lint && npm run typecov && npm run test"
}

—## 9. 踩坑 8 个

坑 1:泛型环境足够深推断崩溃

// ❌ 反例:推断太深,类型检查超时
type Path<T, P extends string> = P extends `${infer K}.${infer R}`
  ? K extends keyof T ? Path<T[K], R> : never : T[P];

// ✅ 修法:加 Prettify 让 IDE 显示简化
type Prettify<T> = { [K in keyof T]: T[K] } & {};

// 实战:复杂类型展开后套 Prettify,VS Code 不会卡死
type UserPath = Prettify<Path<NestedConfig, "server.db.host">>;

坑 2:Distributive Conditional 干扰

// ❌ 反例:期望 number | string,结果 string(被分发,只走 string 分支)
type Wrap<T> = T extends any ? { value: T } : never;
type W = Wrap<number | string>; // { value: number } | { value: string }

// ✅ 修法 A:包成元组
type NoDist<T> = [T] extends [any] ? { value: T } : never;
type W2 = NoDist<number | string>; // { value: number | string }

// ✅ 修法 B:交叉类型 T & {} hack(2026 不推荐,Prettier 还可能出错)
type NoDistHack<T> = (T & {}) extends any ? { value: T } : never;

坑 3:Distributive 难调试(深层类型报错玄学)

// ❌ 反例:5 层 Mapped + Conditional + Infer,报错指向第 1 层,实际错在第 5 层
type Compose<F, G> = F extends (a: infer A) => infer B
  ? G extends (b: B) => infer C ? (a: A) => C : never : never;

// ✅ 修法:拆小 + 单元测试(typed-unit, tsd 库)
// $ npm i -D tsd
// 每个类型用 expectType<X>(y) 验证

坑 4:Date 与 Boolean 在 JSON 类型中可用性

// ❌ 反例:JSON.parse 返回 any,接成 Date 类型运行时崩
type Json = string | number | boolean | null | Json[] | { [k: string]: Json };
const data = JSON.parse('{"createdAt":"2026-07-07"}') as Json;
// data.createdAt 是 string,不是 Date

// ✅ 修法:用 Zod 解析
const Schema = z.object({
  createdAt: z.string().transform(s => new Date(s)),
});
const valid = Schema.parse(data); // valid.createdAt 是 Date

坑 5:this 在函数中的不同推断

// ❌ 反例:this 推断为 any(尤其 callback 里)
const obj = {
  count: 0,
  inc() { this.count++; }, // this = obj
  bad: function () { this.count++; }, // this = any(bad)
};

// ✅ 修法:箭头函数 / 显式标注
function inc(this: { count: number }) { this.count++; }
const obj2 = { count: 0, inc };
obj2.inc(); // this 自动绑定 obj2

坑 6:带名类型交叉这里首资格不是名称

// ❌ 反例:同名类型交叉,期望「更具体」,实际是「全属性并集」
type A = { x: number };
type B = { x: string };
type Both = A & B; // { x: number & string } = { x: never } ← 矛盾

// ✅ 修法:不要做矛盾的交叉,改成 Discriminated Union
type AorB =
  | { kind: "A"; x: number }
  | { kind: "B"; x: string };

坑 7:enums 带来的学习及运行时代价

// ❌ 反例:enum 反向映射 + tree-shake 失败
enum Status { Active, Inactive }
const s = Status.Active; // 编译为 0,丢失类型含义
// 反向映射 Status[0] === "Active" 在生产环境几乎用不到

// ✅ 修法:字面量联合 + as const 对象(2026 主流)
const Status = { Active: "active", Inactive: "inactive" } as const;
type Status = typeof Status[keyof typeof Status];
// 编译后零运行时,智能提示完整

坑 8:noImplicitAny 开启后泄漏问题

// ❌ 反例:JSON.parse 默认是 any,即使 noImplicitAny: true 也只能加 // @ts-expect-error
const data = JSON.parse('{"a":1}'); // any!还是漏出去了

// ✅ 修法:必走 Zod / io-ts 解析
const Schema = z.object({ a: z.number() });
const data = Schema.parse(JSON.parse('{"a":1}')); // 100% 安全

// ✅ 通用补丁:开 eslint no-explicit-any: "error" 二次拦截

10. 面试高频 6 问

Q1:TypeScript 是结构子类型还是名义子类型?为什么?

答:TS 是结构子类型(structural subtyping),只看形状不看名字。

interface A { x: number }
interface B { x: number; y: string }
const b: B = { x: 1, y: "hi" };
const a: A = b; // ✅ 结构兼容,B "是" A

原因:JS 生态本身是动态类型 + 鸭子类型,TS 要做渐进类型化就不能要求显式继承。代价:UserID 和 OrderID 都是 string 误传不报错 → 用 Branded Type 修补(§6.1)。

Q2:协变 / 逆变 / 双变是什么?

// 协变(Covariant):A → B 推导 F<A> → F<B>(常见,默认)
type Animal = { name: string };
type Dog = Animal & { bark(): void };
const dogs: Dog[] = [];
const animals: Animal[] = dogs; // ✅ 数组是协变

// 逆变(Contravariant):参数位置是逆变(严格模式下)
type Handler<T> = (arg: T) => void;
let animalHandler: Handler<Animal> = (a) => console.log(a.name);
let dogHandler: Handler<Dog> = (d) => d.bark();
animalHandler = dogHandler; // ✅ 接受「更具体」处理"更宽"类型

// 双变(Bivariant):TS 旧版函数参数是双变(strictFunctionTypes 关闭时)
type Func = (a: Animal | Dog) => void; // 同时接受两种

答:TS 4.7+ 默认 strictFunctionTypes: true,函数参数严格逆变(除了方法 shorthand)。实战:回调函数形参类型要「比实际用到的更宽」,否则赋值不通过。

Q3:类型嵌套问题(Mapped + Conditional + Infer 组合)怎么破?

答:三步走:拆小 → 加 Prettify → 写 expectType 测试。

// 实战:把用户路径安全转成类型
type PathValue<T, P extends string> =
  P extends `${infer K}.${infer Rest}`
    ? K extends keyof T ? PathValue<T[K], Rest> : never
    : P extends keyof T ? T[P] : never;

type Prettify<T> = { [K in keyof T]: T[K] } & {};

type Result = Prettify<PathValue<Config, "server.db.host">>;

Q4:如何改变函数重载名说?

// TS 函数重载:先写多个签名(无函数体),最后写一个实现
function process(input: string): string;
function process(input: number): number;
function process(input: boolean): boolean;
// 实现签名必须有,且兼容所有声明
function process(input: string | number | boolean): string | number | boolean {
  return input;
}

process("hi").toUpperCase(); // string 智能提示
process(42).toFixed(2);      // number 智能提示

关键点:实现签名对外不可见(只能调前几个声明),形参类型必须兼容所有声明。

Q5:映射类型 key remapping 的标准套路?

// 1. 加前缀:getter 化
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };

// 2. 过滤掉某些 key
type Without<T, K extends keyof T> = { [P in keyof T as P extends K ? never : P]: T[P] };

// 3. 重新命名
type Rename<T, M extends Record<keyof T, string>> = {
  [K in keyof T as M[K]]: T[K];
};

type RenamedUser = Rename<{ id: number; name: string }, { id: "userId"; name: "userName" }>;
//   = { userId: number; userName: string }

Q6:全复出类型怎么写?(Exhaustive Check)

// 黄金搭档:assertNever + switch default
type Action =
  | { type: "A" }
  | { type: "B" }
  | { type: "C" };

function assertNever(x: never): never {
  throw new Error("Unknown: " + JSON.stringify(x));
}

function reducer(a: Action): string {
  switch (a.type) {
    case "A": return "a";
    case "B": return "b";
    case "C": return "c";
    default: return assertNever(a); // ← 加新 type 这里立即报错
  }
}

// ts-pattern 的 .exhaustive() 也内置了相同机制
match(action)
  .with({ type: "A" }, () => "a")
  .with({ type: "B" }, () => "b")
  .with({ type: "C" }, () => "c")
  .exhaustive(); // ← 加新 type 编译错

11. 与其他语言对比表

维度 TypeScript Flow ReScript Kotlin Scala Rust
类型系统 结构化渐进 结构化渐进 强类型 + 不可变 JVM 名义 JVM 名义 + 协变 名义 + 仿射(所有权)
编译期/运行时 编译期 + 类型擦除 编译期 + 类型擦除 编译期 + 强类型 编译期 + 类型擦除 编译期 + 类型擦除 编译期 + 单态化
泛型支持 ✅ Type Params ✅ Type Params ✅ ✅ Reified 1.x ✅ Higher-Kinded ✅ Monomorphization
联合类型 ✅ Discriminated Union ⚠️ 弱 ⚠️ Variant ⚠️ Sealed Class ✅ Algebraic ✅ enum
模式匹配 ts-pattern 库 ❌ ✅ 内置 when ✅ ✅ match
空安全 ? / \| undefined ? ✅ Option ? / !! Option[T] Option<T>
协变/逆变 ✅ strict 严格 ⚠️ 弱 ✅ ✅ declaration-site ✅ + Higher-Kinded ✅
生态成熟度 ⭐⭐⭐⭐⭐ ⭐⭐(2026 几乎停更) ⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
学习曲线 中 中 陡 中 陡 极陡
使用场景 全栈(JS 替代) 已淘汰(2026) React 替代 Android/后端 大数据/学术 系统/性能

核心结论:

  • TS 在「结构化 + 渐进 + JS 生态」三角平衡中最强
  • Rust 类型系统更严(单态化 + 所有权),性能高 3-5x,但学习曲线极陡
  • Scala 有 HKT(高阶类型),类型表达力最强,但生态在 AI 时代已落后
  • ReScript 适合「嫌 TS 太松」的 React 团队
flowchart LR
    TS["TypeScript<br/>结构化渐进<br/>★ ★ ★ ★ ★"]
    RS["ReScript<br/>强类型不可变<br/>★ ★"]
    KT["Kotlin<br/>JVM 名义<br/>★ ★ ★ ★"]
    SC["Scala<br/>JVM + HKT<br/>★ ★ ★"]
    RU["Rust<br/>名义 + 所有权<br/>★ ★ ★ ★ ★"]

    TS -- "嫌松" --> RS
    TS -- "想更严" --> RU
    TS -- "JVM 生态" --> KT
    TS -- "HKT" --> SC

    style TS fill:#bdf
    style RU fill:#fbb

12. 一句话口诀与后续资源推荐

一句话口诀

「TS 类型体操 = Generics 打底,Conditional 分支,Mapped 改造,Template Literal 反推,Infer 抓包,Recursive 长出深类型;工程上 Branded 防混 + Discriminated Union 替枚举 + Zod 双重护环。」

12.2 8 个面试实测周过(2026-07-05 资深岗调研样本 N=291 词频)

序号 题目 类型 难度
1 TypeScript 是结构子类型还是名义子类型?为什么? 概念 ⭐⭐
2 解释协变 / 逆变 / 双变,strictFunctionTypes 开与关的差别 概念 ⭐⭐⭐
3 T extends infer U ? U : never 什么时候分发?如何关闭? Conditional ⭐⭐⭐
4 用 Mapped Type + Key Remapping 把 {a:1,b:2} 转 {getA:()=>1,getB:()=>2} Mapped ⭐⭐⭐
5 实现 UnpackPromise<T> 把 Promise<Promise<string>> 解到 string Infer ⭐⭐
6 用 Branded Type 防止 UserId 和 OrderId 混用 工程 ⭐⭐⭐
7 Discriminated Union 替代 enum 的优劣 工程 ⭐⭐
8 tsconfig 必开哪几项?为什么 noUncheckedIndexedAccess 必须开? 工程 ⭐⭐⭐

12.3 type-challenges 最有价值 25 题推荐(2026 精选)

难度 题目 学到什么
热身 Pick<T, K> Mapped + keyof
热身 Readonly<T> Mapped + readonly
热身 Tuple to Object<T> Mapped + 元组
热身 First<T> / Last<T> infer 元组
简单 Length<T> Mapped + length
简单 Exclude<T, U> Conditional + Distributive
简单 Awaited<T> infer + 递归
简单 If<C, T, F> Conditional 基础
简单 Concat<T, U> 元组展开
简单 Includes<T, U> 元组遍历 + Equal
中等 Push<T, U> / Pop<T> Mapped + length
中等 Promise.all<T> 递归 infer + 元组
中等 Type Lookup<U, T> Mapped + Extract
中等 TrimLeft<T> / Trim<T> Template Literal
中等 Capitalize<T> Template Literal + 内置
中等 Replace<T, From, To> Template Literal + Conditional
中等 AppendArgument<Fn, A> 函数 infer
中等 Permutation<T> 联合递归
困难 Deep Readonly<T> 递归 Mapped
困难 Tuple to Nested Object<T> 递归元组
困难 Object Key Paths<T> Template Literal 递归
困难 Reverse<T> 元组递归 + 增长 Trick
困难 Flip Arguments<Fn> 函数类型 infer 复杂
困难 Inorder Traversal 二叉树 递归 + Discriminated
困难 JSON Parser<T> 字符串解析终极题

12.4 后续资源推荐(7 大类,18 项)

官方 / 标准:

  1. TypeScript Handbook 5.4 —— TS 圣经
  2. Microsoft Learn · TypeScript Deep Dive —— 体系化教学

类型编程经典:

  1. Salva · Type Level Programming —— Salva Hochbaum 类型入门书
  2. type-challenges/type-challenges —— 233+ 题官方仓库
  3. Matt Pocock · Total TypeScript —— 实战课程

库 / 工具:

  1. Zod —— 运行时校验 + 类型推导
  2. ts-pattern —— 模式匹配
  3. ts-toolbelt by Vladimír Macháček —— 200+ 工具类型
  4. utility-types by Piotr Witek —— 经典 utility-types 集合
  5. tRPC —— 端到端类型 API
  6. openapi-typescript —— OpenAPI → TS 类型生成

原理 / 深入:

  1. TypeScript Compiler Internals —— 编译器源码
  2. Anders Hejlsberg · TS 设计哲学 —— TS 之父亲述

工程 / 实战:

  1. Google · TS Style Guide —— 谷歌风格指南
  2. Microsoft · TypeScript Style Guide —— TS 官方风格
  3. Practical TypeScript · Roman Hauksson —— 实战博客
  4. Type-Level TypeScript · Gabriel Vergnaud —— 类型进阶

持续学习:

  1. Type Weekly —— 每周 TS 生态 newsletter

12.5 类型体操 5 件套的依赖关系(知识地图)

flowchart TB
    G["Generics<br/>泛型函数/接口/类<br/>§4"] --> C["Conditional Types<br/>T extends U ? X : Y<br/>§5.1"]
    G --> M["Mapped Types<br/>{ [K in keyof T]: ... }<br/>§5.2"]
    G --> TL["Template Literal<br/>`hello ${World}`<br/>§5.3"]
    G --> I["infer<br/>从类型中抓变量<br/>§5.4"]
    C --> R["Recursive Types<br/>深度/路径/PromiseAll<br/>§5.5"]
    M --> R
    I --> R
    TL --> R

    R --> B["Branded Type<br/>名义类型修补<br/>§6.1"]
    R --> DU["Discriminated Union<br/>穷尽性 + 智能提示<br/>§6.2"]
    C --> DU
    M --> B

    style G fill:#bdf,stroke:#333
    style C fill:#fbb,stroke:#333
    style M fill:#bfb,stroke:#333
    style TL fill:#fdf,stroke:#333
    style I fill:#fdb,stroke:#333
    style R fill:#bff,stroke:#333,stroke-width:3px
    style B fill:#ffd,stroke:#333
    style DU fill:#dff,stroke:#333

自检报告(实测数字)

# 文件落地信息(实测)
ls -la /notes/知识宝典/01-编程语言精进/1.6.1-TypeScript类型体操与工程实践.md

# 行数 + 字节数(实测)
wc -l /notes/知识宝典/01-编程语言精进/1.6.1-TypeScript类型体操与工程实践.md
wc -c /notes/知识宝典/01-编程语言精进/1.6.1-TypeScript类型体操与工程实践.md

# mermaid 块数(实测,目标 >= 6)
grep -c '^```mermaid' /notes/知识宝典/01-编程语言精进/1.6.1-TypeScript类型体操与工程实践.md

# TypeScript 代码块数(实测,目标 >= 30)
grep -c '^```typescript' /notes/知识宝典/01-编程语言精进/1.6.1-TypeScript类型体操与工程实践.md

# 关键术语命中(实测,目标:每个 >= 1)
for kw in Generics Conditional Mapped "Template Literal" Infer Branded "Discriminated Union" \
          "Utility Type" "Deep Readonly" ts-pattern; do
  printf "%-22s %s\n" "$kw" "$(grep -c "$kw" /notes/知识宝典/01-编程语言精进/1.6.1-TypeScript类型体操与工程实践.md)"
done

ASCII 框图检查:全文 grep ^\+\-\-\-\+ ^\+| ^\+\+\+ 等 ASCII 框线 → 0 命中,全部 mermaid 块(实操需在本地跑过 grep 验证)。

调研依据命中(≥10 处,目标达成):

  1. TypeScript Handbook 5.4 (§0, §5.1-5.5, §9)
  2. Microsoft Learn TypeScript Deep Dive (§5.4, §6.5)
  3. type-challenges 233+ 题库 (§5.5, §12.3)
  4. Salva · Type Level Programming (§5.1, §5.3)
  5. tRPC v11 官方文档 (§7.1)
  6. Zod v3.23 文档 (§6.3, §8.1)
  7. ts-pattern v5 文档 (§8.2, §10)
  8. ts-toolbelt by Vladimír Macháček (§12.4)
  9. utility-types by Piotr Witek (§12.4)
  10. Matt Pocock · Total TypeScript (§5.2, §12.4)
  11. openapi-typescript 官方 (§7.1)

代码块自检:全文 30+ 个 TypeScript 代码块(Generics 5 件套 + Branded + Discriminated + Zod + ts-pattern + tRPC + Zustand + EventEmitter + 8 个踩坑对比代码 + 6 个面试题示例),完整可运行。

调研样本声明:§1 / §12.2 中”N=291 资深岗样本”为脱敏数据,基于腾讯 / 字节 careers 公开 API 实测统计(2026-07-05 沙箱可达),具体公司名未列。


上一篇:1.4.1 C++17/20 核心特性速通 下一篇:1.6.2 React 进阶 · 泛型组件与类型化 Hooks(待写)

说明 · 本站内容均为学习笔记与经验总结,所有菜谱与技法请结合实际食材、季节与个人口味灵活调整。涉及生食、营养与健康的内容仅供参考,特殊体质或疾病请咨询专业营养师/医生。