前置知识: TypeScript

交叉类型与类型合并

31 minIntermediate2026/7/21

交叉类型、接口合并与类型覆盖

学习目标

完成本章学习后,读者应能够在以下认知层级(Bloom 分类法)达到相应能力:

  1. 记忆(Remember):复述交叉类型(Intersection Type)的语法 A & B,列举接口声明合并(Declaration Merging)的三种成员类型(属性、方法、索引签名),写出 OverrideMergeDeepPartial 等工具类型的定义。
  2. 理解(Understand):解释交叉类型在集合论上的交集语义,说明同名属性类型冲突时产生 never 的根本原因,对比接口声明合并与交叉类型在组合机制上的差异。
  3. 应用(Apply):使用交叉类型实现 Mixin 模式,使用接口声明合并扩展第三方库类型,使用 Override 工具类型修改已有类型的部分字段。
  4. 分析(Analyze):解构复杂交叉类型,识别哪些属性会产生 never,推断交叉类型的最终结构,分析深度合并工具类型的递归行为。
  5. 评价(Evaluate):评估在特定场景下应使用交叉类型还是接口声明合并,权衡类型组合的可读性、可维护性与编译性能,判断递归类型的终止性。
  6. 创造(Create):设计并实现一个支持条件合并、路径覆盖、类型安全配置嵌套的通用类型组合工具库,覆盖企业级应用的复杂类型组合需求。

历史动机与背景

类型组合(Type Composition)是类型系统的核心能力之一。在没有类型组合的语言中,每种数据结构都必须从零定义,导致大量重复。TypeScript 提供了多种类型组合机制,其中交叉类型与接口声明合并是最基础也最强大的两种。理解它们的演化背景,有助于在实际工程中做出正确选型。

阶段一:结构化类型的早期探索(2012-2014)

TypeScript 1.0(2012)引入了结构化类型系统,但最初只支持 interface 的 extends 单继承。开发者面对”组合多个类型”的需求时,只能通过手动复制字段或使用 extends 继承单一接口来解决:

// 早期 TypeScript:只能单继承
interface Person { name: string; age: number }
interface Employee extends Person { employeeId: number }
// 想同时组合 Timestamped 与 SoftDeletable?做不到

这种限制在 Mixin、配置合并、Props 组合等场景下显得力不从心。

阶段二:交叉类型的引入(TypeScript 1.6, 2015)

TypeScript 1.6 引入了交叉类型 A & B,允许将多个类型组合为一个同时满足所有成员类型的新类型。这是 TypeScript 类型系统的重要里程碑:

type Timestamped = { createdAt: Date; updatedAt: Date };
type SoftDeletable = { deletedAt: Date | null; isDeleted: boolean };
type Entity = Timestamped & SoftDeletable;
// Entity 同时拥有 createdAt、updatedAt、deletedAt、isDeleted

交叉类型的引入解决了多类型组合问题,但也带来了新的挑战:属性冲突如何处理。TypeScript 的设计决策是:同名属性类型不同时,该属性类型变为 never,表示不可能存在满足两种类型的值。这一决策符合集合论的交集语义,但在实践中常常令初学者困惑。

阶段三:接口声明合并的成熟(TypeScript 2.0+, 2016)

接口声明合并是 TypeScript 的独有特性:同名接口的成员会自动合并。这一特性在扩展第三方库类型时尤为有用:

// 扩展 Window 接口
interface Window {
  myCustomAPI: { doSomething: () => void };
}
// 此后 window.myCustomAPI 在类型检查中合法

接口声明合并不产生 never,因为同名属性必须类型一致,否则编译报错。这一更严格的语义在某些场景下比交叉类型更安全。

阶段四:工具类型与递归组合(TypeScript 4.0+, 2020)

随着条件类型、映射类型、模板字面量类型的成熟,开发者可以构建复杂的类型组合工具,如 DeepMergeDeepPartialPathOverride 等。这些工具类型在配置管理、状态合并、GraphQL 类型增强等场景中广泛应用,使 TypeScript 的类型系统具备了接近图灵完备的表达能力。

与其他语言的对比

  • Java/C#:通过 implements 多接口实现组合,但接口只能声明行为不能声明状态,无法像交叉类型那样组合属性。
  • Rust:通过 Trait 组合行为,但 Trait 同样不包含字段,组合语义弱于交叉类型。
  • Scala:通过 Trait with 字段组合,语义最接近交叉类型,但运行时线性化(Linearization)顺序影响字段初始化。
  • Go:通过 Struct Embedding 实现字段组合,但不支持冲突解决(同名字段以最外层为准)。

TypeScript 的交叉类型在表达力上接近 Scala 的 Trait 组合,但在编译时完成且运行时无开销,是 JavaScript 生态的独特优势。

形式化定义

交叉类型的形式化语义

给定两个类型 T1T_1T2T_2,其交叉类型 T1&T2T_1 \mathbin{\&} T_2 的语义解释定义为:

Sem(T1&T2)=Sem(T1)Sem(T2)\text{Sem}(T_1 \mathbin{\&} T_2) = \text{Sem}(T_1) \cap \text{Sem}(T_2)

即交叉类型的值集合是各成员类型值集合的交集。这一语义与集合论的交集操作完全一致,因此交叉类型在英文中称为 “Intersection Type”。

属性合并的形式化规则

对于对象类型 T1={p1:τ1,p2:τ2,}T_1 = \{ p_1: \tau_1, p_2: \tau_2, \dots \}T2={p2:τ2,p3:τ3,}T_2 = \{ p_2: \tau_2', p_3: \tau_3, \dots \},交叉类型的属性合并遵循以下规则:

T1&T2={p1:τ1,p2:τ2τ2,p3:τ3,}T_1 \mathbin{\&} T_2 = \{ p_1: \tau_1, p_2: \tau_2 \cap \tau_2', p_3: \tau_3, \dots \}

即:

  • 非冲突属性(仅出现在一个类型中)保留原类型。
  • 冲突属性(同时出现在两个类型中)的类型取交集。
  • τ2τ2=\tau_2 \cap \tau_2' = \emptyset(即两个类型无公共值)时,该属性类型为 never

never 类型的形式化定义

never 类型是类型系统的底类型(Bottom Type),其语义解释为空集:

Sem(never)=\text{Sem}(\text{never}) = \emptyset

底类型是所有类型的子类型:

never<:TT\text{never} <: T \quad \forall T

在交叉类型中,never 具有吸收性:

T&never=neverT \mathbin{\&} \text{never} = \text{never}

这一定理保证了:一旦某个属性冲突产生 never,整个对象类型在实际使用时会变得不可构造。

接口声明合并的形式化规则

接口声明合并遵循以下形式化规则:

Γinterface I{M1}Γinterface I{M2}Γinterface I{M1M2}\frac{\Gamma \vdash \text{interface } I \{ M_1 \} \quad \Gamma \vdash \text{interface } I \{ M_2 \}}{\Gamma \vdash \text{interface } I \{ M_1 \uplus M_2 \}}

其中 \uplus 是成员合并操作,具体规则为:

  • 非函数成员:必须类型一致,否则编译错误。
  • 函数成员(方法):合并为重载(Overload),后声明的签名排在前面。
  • 索引签名:必须类型一致,否则编译错误。

这一规则比交叉类型更严格:交叉类型允许冲突产生 never,而接口声明合并在冲突时直接报错,提供更强的类型安全。

子类型关系与交叉类型

交叉类型与子类型关系存在以下对偶性:

T1&T2<:T1T1&T2<:T2T_1 \mathbin{\&} T_2 <: T_1 \quad \text{且} \quad T_1 \mathbin{\&} T_2 <: T_2

即交叉类型是各成员类型的子类型。反之,对联合类型:

T1<:T1T2T2<:T1T2T_1 <: T_1 \mathbin{|} T_2 \quad \text{且} \quad T_2 <: T_1 \mathbin{|} T_2

即各成员类型是联合类型的子类型。这一对偶关系构成了 TypeScript 类型系统的代数骨架,也是证明类型安全性(Type Safety)的基础。

理论推导

推导一:交叉类型的交换律与结合律

定理:交叉类型满足交换律与结合律。

证明

由交集的集合论性质直接可得:

Sem(T1&T2)=Sem(T1)Sem(T2)=Sem(T2)Sem(T1)=Sem(T2&T1)\text{Sem}(T_1 \mathbin{\&} T_2) = \text{Sem}(T_1) \cap \text{Sem}(T_2) = \text{Sem}(T_2) \cap \text{Sem}(T_1) = \text{Sem}(T_2 \mathbin{\&} T_1) Sem((T1&T2)&T3)=(Sem(T1)Sem(T2))Sem(T3)=Sem(T1)(Sem(T2)Sem(T3))=Sem(T1&(T2&T3))\text{Sem}((T_1 \mathbin{\&} T_2) \mathbin{\&} T_3) = (\text{Sem}(T_1) \cap \text{Sem}(T_2)) \cap \text{Sem}(T_3) = \text{Sem}(T_1) \cap (\text{Sem}(T_2) \cap \text{Sem}(T_3)) = \text{Sem}(T_1 \mathbin{\&} (T_2 \mathbin{\&} T_3))

因此:

T1&T2=T2&T1T_1 \mathbin{\&} T_2 = T_2 \mathbin{\&} T_1 (T1&T2)&T3=T1&(T2&T3)(T_1 \mathbin{\&} T_2) \mathbin{\&} T_3 = T_1 \mathbin{\&} (T_2 \mathbin{\&} T_3)

这一性质保证了多个类型交叉的顺序不影响结果,是构建可组合工具类型的理论基础。

推导二:交叉与联合的分配律

定理:交叉类型对联合类型满足分配律。

证明

Sem(T1&(T2T3))=Sem(T1)(Sem(T2)Sem(T3))\text{Sem}(T_1 \mathbin{\&} (T_2 \mathbin{|} T_3)) = \text{Sem}(T_1) \cap (\text{Sem}(T_2) \cup \text{Sem}(T_3))

由集合论的分配律:

A(BC)=(AB)(AC)A \cap (B \cup C) = (A \cap B) \cup (A \cap C)

因此:

Sem(T1&(T2T3))=(Sem(T1)Sem(T2))(Sem(T1)Sem(T3))=Sem((T1&T2)(T1&T3))\text{Sem}(T_1 \mathbin{\&} (T_2 \mathbin{|} T_3)) = (\text{Sem}(T_1) \cap \text{Sem}(T_2)) \cup (\text{Sem}(T_1) \cap \text{Sem}(T_3)) = \text{Sem}((T_1 \mathbin{\&} T_2) \mathbin{|} (T_1 \mathbin{\&} T_3))

即:

T1&(T2T3)=(T1&T2)(T1&T3)T_1 \mathbin{\&} (T_2 \mathbin{|} T_3) = (T_1 \mathbin{\&} T_2) \mathbin{|} (T_1 \mathbin{\&} T_3)

这一性质是条件类型 T extends U ? X : Y 在联合类型上自动分发(Distribute)的理论基础。

推导三:函数类型的交叉即为重载

定理:函数类型的交叉类型等价于函数重载。

证明

定义函数类型 F1=(x:A1)B1F_1 = (x: A_1) \to B_1F2=(x:A2)B2F_2 = (x: A_2) \to B_2。其交叉类型 F1&F2F_1 \mathbin{\&} F_2 的语义解释为:

Sem(F1&F2)={ffSem(F1)fSem(F2)}\text{Sem}(F_1 \mathbin{\&} F_2) = \{ f \mid f \in \text{Sem}(F_1) \land f \in \text{Sem}(F_2) \}

即函数 ff 必须同时满足 F1F_1F2F_2 的契约。在调用时,TypeScript 会尝试用 F1F_1 的签名匹配,若失败则尝试 F2F_2 的签名。这正是函数重载的语义。

type F1 = (x: string) => number;
type F2 = (x: number) => string;
type F = F1 & F2;

// 调用 f 时,TypeScript 按 F1、F2 顺序匹配
const f: F = (x: any) => x; // 实现签名不受重载约束
f('hello'); // 匹配 F1,返回 number
f(42);      // 匹配 F2,返回 string

这一定理表明:交叉类型不仅是对象组合的工具,也是函数重载的形式化基础。

推导四:递归类型的终止性

定理:深度合并等递归类型在满足结构递减条件时终止。

证明

考虑 DeepMerge<T, U> 的递归结构:

type DeepMerge<T, U> = {
  [K in keyof T | keyof U]: K extends keyof T
    ? K extends keyof U
      ? T[K] extends object
        ? U[K] extends object
          ? DeepMerge<T[K], U[K]>  // 递归
          : U[K]
        : U[K]
      : T[K]
    : K extends keyof U
      ? U[K]
      : never;
};

递归终止条件为:当 T[K]T[K]U[K]U[K] 不再是 object 类型时,递归停止。

定义类型深度 d(T)d(T) 为类型嵌套层数:

d(primitive)=0,d({p:T})=1+d(T)d(\text{primitive}) = 0, \quad d(\{ p: T \}) = 1 + d(T)

每次递归调用 DeepMerge<T[K], U[K]> 时,d(T[K])<d(T)d(T[K]) < d(T)d(U[K])<d(U)d(U[K]) < d(U)。由于深度是有限非负整数,递归必然终止。

这一证明保证了 DeepMerge 等递归工具类型在实际使用时不会无限递归。但需要注意,TypeScript 编译器对递归深度有上限(约 50 层),超过会报 “Type instantiation is excessively deep” 错误。

代码示例

示例 1:基础交叉类型

// 组合两个独立类型的属性
type Person = { name: string; age: number };
type Employee = { employeeId: number; department: string };

// 交叉类型:同时满足 Person 与 Employee
type EmployeePerson = Person & Employee;

// 等价于 { name: string; age: number; employeeId: number; department: string }
const worker: EmployeePerson = {
  name: '张三',
  age: 30,
  employeeId: 1001,
  department: '工程部',
};

// 交叉类型是其成员类型的子类型
function greet(person: Person) {
  console.log(`你好,${person.name}`);
}
greet(worker); // 合法:EmployeePerson 是 Person 的子类型

示例 2:属性冲突与 never

// 同名属性类型不同时产生 never
type A = { id: string };
type B = { id: number };
type C = A & B;

// C 的 id 类型为 string & number = never
// 因为不存在一个值既是 string 又是 number

// 尝试构造 C 类型的值会失败
const c: C = {
  id: 'hello' as never, // 必须使用类型断言
};

// 实际使用:避免在交叉类型中产生 never
type A2 = { id: string; name: string };
type B2 = { id: string; age: number }; // id 类型一致
type C2 = A2 & B2;
// C2 = { id: string; name: string; age: number }
const c2: C2 = { id: '001', name: '张三', age: 30 };

示例 3:接口声明合并

// 同名接口自动合并成员
interface Box {
  height: number;
  width: number;
}

interface Box {
  depth: number;
  // height: string; // 错误:后续声明的同名属性必须类型一致
}

// Box 合并为 { height: number; width: number; depth: number }
const box: Box = { height: 10, width: 20, depth: 30 };

// 方法合并为重载
interface Calculator {
  calculate(input: string): number;
}

interface Calculator {
  calculate(input: number): string;
}

// Calculator.calculate 有两个重载签名
const calc: Calculator = {
  calculate(input: any): any {
    return typeof input === 'string' ? input.length : `result: ${input}`;
  },
};

calc.calculate('hello'); // 调用第一个重载,返回 number
calc.calculate(42);      // 调用第二个重载,返回 string

示例 4:扩展第三方库类型

// 扩展 Express 的 Request 接口,添加自定义字段
import { Request } from 'express';

// 声明合并:为 Request 添加 user 字段
declare module 'express' {
  interface Request {
    user?: {
      id: string;
      username: string;
      role: 'admin' | 'user';
    };
  }
}

// 此后所有 Request 对象都有 user 字段
function authMiddleware(req: Request, res: Response, next: NextFunction) {
  // req.user 类型安全
  if (req.user?.role === 'admin') {
    next();
  } else {
    res.status(403).send('Forbidden');
  }
}

// 扩展 Window 接口
interface Window {
  __APP_CONFIG__?: {
    apiBaseUrl: string;
    version: string;
  };
}

// 此后 window.__APP_CONFIG__ 类型安全
const config = window.__APP_CONFIG__;
if (config) {
  console.log(`API: ${config.apiBaseUrl}`);
}

示例 5:Mixin 模式

// 使用交叉类型实现 Mixin 模式
type Constructor<T = {}> = new (...args: any[]) => T;

// 时间戳 Mixin:为类添加 createdAt 与 updatedAt 字段
function Timestamped<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    createdAt: Date = new Date();
    updatedAt: Date = new Date();

    touch() {
      this.updatedAt = new Date();
    }
  };
}

// 软删除 Mixin:为类添加软删除能力
function SoftDeletable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    deletedAt: Date | null = null;
    isDeleted = false;

    softDelete() {
      this.isDeleted = true;
      this.deletedAt = new Date();
    }

    restore() {
      this.isDeleted = false;
      this.deletedAt = null;
    }
  };
}

// 可序列化 Mixin:为类添加序列化方法
function Serializable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    serialize(): string {
      return JSON.stringify(this);
    }

    static deserialize<T extends InstanceType<TBase>>(
      this: new (...args: any[]) => T,
      json: string
    ): T {
      const data = JSON.parse(json);
      return new this(data);
    }
  };
}

// 基础类
class User {
  constructor(
    public id: string,
    public name: string,
    public email: string
  ) {}
}

// 组合多个 Mixin
const EnhancedUser = Serializable(SoftDeletable(Timestamped(User)));

// 类型推断:User & Timestamped & SoftDeletable & Serializable
const user = new EnhancedUser('001', '张三', 'zhangsan@example.com');
user.touch();           // 来自 Timestamped
user.softDelete();      // 来自 SoftDeletable
const json = user.serialize(); // 来自 Serializable
console.log(user.createdAt, user.isDeleted, json);

示例 6:Override 工具类型

// Override:用 U 覆盖 T 中的同名属性
type Override<T, U extends Partial<Record<keyof T, unknown>>> = Omit<T, keyof U> & U;

// 示例:修改 API 响应中的日期字段类型
interface APIUser {
  id: string;
  name: string;
  createdAt: string;  // API 返回的是字符串
  updatedAt: string;
  role: string;
}

// 覆盖日期字段为 Date 类型,role 为联合类型
interface UserOverrides {
  createdAt: Date;
  updatedAt: Date;
  role: 'admin' | 'user' | 'guest';
}

type UIUser = Override<APIUser, UserOverrides>;
// { id: string; name: string; createdAt: Date; updatedAt: Date; role: 'admin' | 'user' | 'guest' }

// 转换函数:从 APIUser 到 UIUser
function transformUser(api: APIUser): UIUser {
  return {
    ...api,
    createdAt: new Date(api.createdAt),
    updatedAt: new Date(api.updatedAt),
    role: api.role as 'admin' | 'user' | 'guest',
  };
}

// StrictOverride:确保 U 的键必须是 T 的子集(编译时约束)
type StrictOverride<T, U extends Partial<Record<keyof T, unknown>>> = Omit<T, keyof U> & U;

interface Original {
  id: string;
  name: string;
  age: number;
}

// 正确:覆盖已有属性
type Modified1 = StrictOverride<Original, { id: number }>;
// { name: string; age: number; id: number }

// 错误:'email' 不是 Original 的属性,类型约束失败
// type Modified2 = StrictOverride<Original, { email: string }>;

示例 7:条件合并

// 根据条件添加属性
type WithPagination<T, P extends boolean = true> = T &
  (P extends true
    ? { page: number; pageSize: number; total: number }
    : {});

// 带分页的响应
interface UserList {
  items: User[];
}
type PaginatedUserList = WithPagination<UserList, true>;
// { items: User[]; page: number; pageSize: number; total: number }

// 不带分页的响应
type SimpleUserList = WithPagination<UserList, false>;
// { items: User[] }

// 根据角色决定可用字段
type UserWithPermissions<R extends 'admin' | 'user'> = {
  id: string;
  name: string;
} & (R extends 'admin'
  ? { permissions: string[]; canDelete: boolean }
  : { permissions?: string[] });

示例 8:深度合并

// DeepMerge:递归合并两个对象类型
type DeepMerge<T, U> = {
  [K in keyof T | keyof U]: K extends keyof T
    ? K extends keyof U
      ? T[K] extends object
        ? U[K] extends object
          ? DeepMerge<T[K], U[K]>
          : U[K]
        : U[K]
      : T[K]
    : K extends keyof U
      ? U[K]
      : never;
};

// 示例:合并默认配置与用户配置
interface DefaultConfig {
  api: {
    baseURL: string;
    timeout: number;
  };
  features: {
    darkMode: boolean;
    analytics: boolean;
  };
  debug: boolean;
}

interface UserConfig {
  api: {
    timeout: number;
    retries: number;
  };
  features: {
    analytics: boolean;
  };
}

type MergedConfig = DeepMerge<DefaultConfig, UserConfig>;
// {
//   api: { baseURL: string; timeout: number; retries: number };
//   features: { darkMode: boolean; analytics: boolean };
//   debug: boolean;
// }

// 运行时深度合并函数
function deepMerge<T extends object, U extends object>(t: T, u: U): DeepMerge<T, U> {
  const result: any = { ...t };
  for (const key in u) {
    if (
      typeof t[key as keyof T] === 'object' &&
      typeof u[key as keyof U] === 'object' &&
      t[key as keyof T] !== null &&
      u[key as keyof U] !== null
    ) {
      result[key] = deepMerge(t[key as keyof T], u[key as keyof U]);
    } else {
      result[key] = u[key as keyof U];
    }
  }
  return result;
}

示例 9:Props 组合(React)

import type { ReactNode, CSSProperties, MouseEvent } from 'react';

// 基础 Props
interface BaseProps {
  className?: string;
  style?: CSSProperties;
  children?: ReactNode;
  id?: string;
}

// 按钮特有 Props
interface ButtonProps {
  onClick: (e: MouseEvent<HTMLButtonElement>) => void;
  disabled?: boolean;
  variant: 'primary' | 'secondary' | 'danger';
  size: 'small' | 'medium' | 'large';
}

// 链接特有 Props
interface LinkProps {
  href: string;
  target?: '_blank' | '_self';
  rel?: string;
}

// 通过交叉类型组合 Props
type EnhancedButtonProps = BaseProps & ButtonProps;
type EnhancedLinkProps = BaseProps & LinkProps;

// 条件 Props:根据 variant 决定额外字段
type ConditionalProps<V extends 'button' | 'link'> = V extends 'button'
  ? { type: 'submit' | 'reset' | 'button' }
  : { download?: string };

// 复合组件 Props
type ComponentProps<V extends 'button' | 'link'> = BaseProps &
  (V extends 'button' ? ButtonProps : LinkProps) &
  ConditionalProps<V>;

// 使用
function SmartComponent<V extends 'button' | 'link'>(props: ComponentProps<V>) {
  // props 类型安全
  if ('href' in props) {
    // 链接模式
    return <a href={props.href}>{props.children}</a>;
  } else {
    // 按钮模式
    return <button onClick={props.onClick}>{props.children}</button>;
  }
}

示例 10:事件类型组合

// 组合多种事件的类型
interface ClickEvent {
  type: 'click';
  x: number;
  y: number;
  timestamp: number;
}

interface KeyEvent {
  type: 'keydown' | 'keyup';
  key: string;
  code: string;
  timestamp: number;
}

interface FocusEvent {
  type: 'focus' | 'blur';
  timestamp: number;
}

// 联合类型:事件可能是任意一种
type UIEvent = ClickEvent | KeyEvent | FocusEvent;

// 使用交叉类型添加追踪信息
type TrackedClickEvent = ClickEvent & { sessionId: string };
type TrackedKeyEvent = KeyEvent & { sessionId: string };

// 类型守卫收窄联合类型
function handleEvent(event: UIEvent) {
  console.log(`事件时间: ${event.timestamp}`);
  switch (event.type) {
    case 'click':
      console.log(`点击位置: (${event.x}, ${event.y})`);
      break;
    case 'keydown':
    case 'keyup':
      console.log(`按键: ${event.key}`);
      break;
    case 'focus':
    case 'blur':
      console.log(`焦点变化: ${event.type}`);
      break;
  }
}

示例 11:递归交叉类型扩展

// 递归扩展嵌套对象类型
type RecursiveExtend<T, U> = T &
  U & {
    [K in keyof T & keyof U]: T[K] extends object
      ? U[K] extends object
        ? RecursiveExtend<T[K], U[K]>
        : U[K]
      : U[K];
  };

// 示例:递归扩展主题配置
interface BaseTheme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
  };
  spacing: {
    small: number;
    medium: number;
    large: number;
  };
  typography: {
    fontFamily: string;
    fontSize: number;
  };
}

interface ThemeOverride {
  colors: {
    primary: string;  // 覆盖
    accent: string;   // 新增
  };
  typography: {
    lineHeight: number;  // 新增
  };
}

type ExtendedTheme = RecursiveExtend<BaseTheme, ThemeOverride>;
// colors: { primary: string; secondary: string; background: string; accent: string }
// spacing: { small: number; medium: number; large: number }
// typography: { fontFamily: string; fontSize: number; lineHeight: number }

示例 12:函数类型的交叉(重载)

// 通过交叉类型组合函数签名,实现重载
type StringParser = (input: string) => string;
type NumberParser = (input: number) => number;
type ArrayParser = (input: any[]) => any[];

// 交叉类型 = 重载
type UniversalParser = StringParser & NumberParser & ArrayParser;

// 实现时使用 any 签名
const parse: UniversalParser = (input: any): any => {
  if (typeof input === 'string') return input.toUpperCase();
  if (typeof input === 'number') return input * 2;
  if (Array.isArray(input)) return input.length;
  return input;
};

// 调用时按重载签名匹配
parse('hello'); // 返回 string
parse(42);      // 返回 number
parse([1, 2]);  // 返回 number

示例 13:状态管理中的类型合并

// 在 Redux/Vuex 状态管理中合并 reducer 状态
interface UserState {
  user: { id: string; name: string } | null;
  isLoading: boolean;
  error: string | null;
}

interface PostsState {
  posts: Array<{ id: string; title: string }>;
  isLoading: boolean;
  error: string | null;
}

interface SettingsState {
  theme: 'light' | 'dark';
  language: 'zh-CN' | 'en-US';
}

// 根状态是各 reducer 状态的交叉
type RootState = UserState & PostsState & SettingsState;

// 类型安全的选择器
function selectUser(state: RootState) {
  return state.user;
}

function selectPosts(state: RootState) {
  return state.posts;
}

function selectTheme(state: RootState) {
  return state.theme;
}

// Action 类型也可以交叉组合
type UserAction =
  | { type: 'USER_LOGIN'; payload: { id: string; name: string } }
  | { type: 'USER_LOGOUT' };

type PostsAction =
  | { type: 'POSTS_LOAD'; payload: Array<{ id: string; title: string }> }
  | { type: 'POSTS_ERROR'; payload: string };

type RootAction = UserAction | PostsAction;

对比分析

交叉类型 vs 接口声明合并

维度交叉类型(&接口声明合并
语法type T = A & Binterface I {...} interface I {...}
适用范围类型别名、接口、对象字面量仅限 interface
冲突处理产生 never,不报错编译错误,强制类型一致
方法合并产生重载产生重载,后声明在前
可扩展性可组合任意数量类型只能合并同名接口
第三方扩展不适用(无法扩展已有 interface)适用(declare module
类型推导复杂交叉可能推导困难简单清晰
编译性能大量交叉(>10)性能下降性能稳定
可读性声明处可见全部组合需搜索所有同名接口
泛型支持完整支持不支持泛型参数合并

选型建议

  1. 组合独立的类型:优先使用交叉类型,声明处可见全部组合,可读性好。
  2. 扩展第三方库类型:必须使用接口声明合并(declare module),交叉类型无法扩展已有 interface。
  3. 需要严格冲突检测:使用接口声明合并,冲突时编译错误比 never 更早暴露问题。
  4. Mixin 模式:使用交叉类型,因为 Mixin 是动态组合,接口声明合并无法表达。
  5. 方法重载:两者均可,但接口声明合并的重载顺序更直观(后声明在前)。

交叉类型 vs 联合类型

| 维度 | 交叉类型(&) | 联合类型(|) | |---|---|---| | 集合论语义 | 交集 \cap | 并集 \cup | | 值集合 | 各成员值集合的交集 | 各成员值集合的并集 | | 属性数量 | 增多(合并所有属性) | 减少(仅公共属性) | | 子类型关系 | 交叉是成员的子类型 | 成员是联合的子类型 | | 赋值方向 | 交叉类型可赋值给成员 | 成员可赋值给联合类型 | | 代数角色 | 积类型(Product Type) | 和类型(Sum Type) |

深度合并工具对比

工具类型行为适用场景
Omit<T, K> & U浅层覆盖修改顶层字段类型
DeepMerge<T, U>递归合并嵌套对象配置合并、主题扩展
DeepPartial<T>所有属性变可选部分更新、表单初始值
DeepReadonly<T>所有属性变只读不可变状态
RecursiveExtend<T, U>递归扩展主题系统、配置扩展

常见陷阱与反模式

陷阱 1:忽视属性冲突的 never(生产事故案例)

场景:2021 年某 SaaS 平台在用户权限系统中使用交叉类型组合 AdminUser 类型,两者都有 permissions 字段但类型不同(string[]readonly string[]),导致交叉后 permissions 变为 never[]。运行时赋值时 TypeScript 未报错(因为 never[] 接受任何数组),但后续对 permissions 的操作全部失败,导致管理员无法执行任何操作,持续 4 小时。

// 反模式:忽视属性冲突
type Admin = { permissions: string[]; canDelete: boolean };
type User = { permissions: readonly string[]; name: string };
type AdminUser = Admin & User;
// AdminUser.permissions 类型为 string[] & readonly string[] = never[]

// 运行时赋值不会报错,但操作会出问题
const admin: AdminUser = {
  permissions: ['read', 'write'] as never[],  // 看似合法
  canDelete: true,
  name: '管理员',
};

// admin.permissions.push('delete') 会失败,因为类型是 never[]

正确做法:在交叉前检查同名属性类型,使用 Override 工具显式处理冲突。

陷阱 2:接口声明合并的意外触发

// 反模式:无意中触发接口声明合并
interface User {
  name: string;
}

// 在另一个文件中,又定义了同名 interface
interface User {
  age: number;
}

// 两个声明会合并,所有 User 类型都同时有 name 和 age
// 这可能导致意外的类型不匹配
function greet(user: User) {
  console.log(user.name, user.age); // 必须访问 age
}

greet({ name: '张三' }); // 错误:缺少 age

陷阱 3:循环类型引用

// 反模式:交叉类型导致循环引用
type Tree<T> = T & {
  children: Tree<T>[];
};

// TypeScript 会无限递归展开 Tree<T>
// 报错:Type instantiation is excessively deep and possibly infinite

// 正确模式:使用 interface 避免立即展开
interface TreeNode<T> extends Array<TreeNode<T>> {}
// 或使用递归类型别名
type SafeTree<T> = {
  value: T;
  children: SafeTree<T>[];
};

陷阱 4:函数交叉的签名顺序

// 反模式:忽视重载签名顺序
type F1 = (x: string) => void;
type F2 = (x: any) => void;
type F = F1 & F2;

// 调用时 F2 的 any 签名会先匹配,导致 F1 永远不会被调用
const f: F = (x: any) => console.log(typeof x);
f('hello'); // 匹配 F2,但行为与 F1 一致
f(42);      // 匹配 F2,但 F1 不会匹配

// 正确模式:将更具体的签名放在前面
type CorrectF = F1 & F2; // F1 先声明
// 但 TypeScript 的交叉类型签名顺序不保证,应使用 interface 重载
interface CorrectF2 {
  (x: string): void;
  (x: any): void;
}

陷阱 5:交叉类型过多导致编译性能下降

// 反模式:交叉 15 个类型
type MegaType = A & B & C & D & E & F & G & H & I & J & K & L & M & N & O;
// 编译器需要计算 15 个类型的属性合并,性能急剧下降

// 正确模式:使用 interface extends 减少计算
interface MegaInterface extends A, B, C, D, E, F, G, H, I, J, K, L, M, N, O {}
// 或分批交叉
type Group1 = A & B & C & D & E;
type Group2 = F & G & H & I & J;
type Group3 = K & L & M & N & O;
type MegaType = Group1 & Group2 & Group3;

陷阱 6:泛型交叉的类型推导失败

// 反模式:复杂泛型交叉导致推导失败
type Complex<T, U> = T & U & { [K in keyof (T & U)]: (T & U)[K] };

// 调用时 TypeScript 无法正确推导
type Result = Complex<{ a: string }, { b: number }>;
// Result 的类型推导可能不准确

// 正确模式:简化泛型,避免在交叉中再次引用交叉
type Simple<T, U> = T & U;
type Result2 = Simple<{ a: string }, { b: number }>; // { a: string; b: number }

陷阱 7:接口声明合并不支持泛型参数合并

// 反模式:期望泛型参数合并
interface Container<T> {
  value: T;
}

interface Container<T> {
  default: T;
  // 无法合并不同泛型参数的接口
}

// 正确模式:使用交叉类型
type Container<T> = {
  value: T;
} & {
  default: T;
};
// 或直接在一个接口中声明
interface Container2<T> {
  value: T;
  default: T;
}

陷阱 8:深度合并的数组合并语义

// 反模式:忽视 DeepMerge 对数组的处理
type DeepMerge<T, U> = {
  [K in keyof T | keyof U]: K extends keyof T
    ? K extends keyof U
      ? T[K] extends object
        ? U[K] extends object
          ? DeepMerge<T[K], U[K]>
          : U[K]
        : U[K]
      : T[K]
    : K extends keyof U
      ? U[K]
      : never;
};

interface A { items: string[] }
interface B { items: number[] }
type Merged = DeepMerge<A, B>;
// Merged.items 类型是 DeepMerge<string[], number[]>
// 实际推导可能为 number[] 或联合类型,语义不明确

// 正确模式:对数组字段单独处理
interface A2 { items: string[]; meta: { count: number } }
interface B2 { items: number[]; meta: { updated: boolean } }

type MergedArray<T, U> = (T extends any[] ? U : DeepMerge<T, U>);

type Merged2 = {
  items: B2['items']; // 数组直接覆盖
  meta: DeepMerge<A2['meta'], B2['meta']>; // 对象深度合并
};

工程实践

实践一:Props 组合的工程规范

在 React 项目中,组件 Props 的组合是交叉类型最常见的应用场景。建议遵循以下规范:

// 1. 定义基础 Props 作为公共契约
interface BaseComponentProps {
  className?: string;
  style?: React.CSSProperties;
  children?: React.ReactNode;
  'data-testid'?: string;
}

// 2. 每个组件定义自己特有的 Props
interface ButtonSpecificProps {
  variant: 'primary' | 'secondary' | 'danger';
  size: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
}

// 3. 通过交叉类型组合
type ButtonProps = BaseComponentProps & ButtonSpecificProps;

// 4. 使用 Override 扩展原生 HTML 属性
type EnhancedButtonProps = Override<
  React.ButtonHTMLAttributes<HTMLButtonElement>,
  ButtonSpecificProps
>;

// 5. 条件 Props:根据属性值决定其他属性
type ConditionalProps =
  | { mode: 'link'; href: string; target?: '_blank' }
  | { mode: 'button'; onClick: () => void };

type ButtonComponentProps = BaseComponentProps & ConditionalProps;

// 6. 使用类型守卫收窄
function Button(props: ButtonComponentProps) {
  if (props.mode === 'link') {
    // props 类型收窄为 { mode: 'link'; href: string; ... }
    return <a href={props.href} />;
  }
  // props 类型收窄为 { mode: 'button'; onClick: () => void; ... }
  return <button onClick={props.onClick} />;
}

实践二:配置管理的类型设计

// 默认配置
interface DefaultAppConfig {
  api: {
    baseURL: string;
    timeout: number;
    retries: number;
  };
  features: {
    darkMode: boolean;
    analytics: boolean;
    experimental: boolean;
  };
  ui: {
    theme: 'light' | 'dark';
    locale: 'zh-CN' | 'en-US';
  };
}

// 用户可覆盖的部分(DeepPartial)
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

type UserAppConfig = DeepPartial<DefaultAppConfig>;

// 合并函数
function mergeConfig(
  defaults: DefaultAppConfig,
  user: UserAppConfig
): DefaultAppConfig {
  return {
    api: { ...defaults.api, ...user.api },
    features: { ...defaults.features, ...user.features },
    ui: { ...defaults.ui, ...user.ui },
  };
}

// 使用
const config = mergeConfig(
  {
    api: { baseURL: '/api', timeout: 5000, retries: 3 },
    features: { darkMode: false, analytics: true, experimental: false },
    ui: { theme: 'light', locale: 'zh-CN' },
  },
  {
    api: { timeout: 10000 },  // 仅覆盖 timeout
    ui: { theme: 'dark' },    // 仅覆盖 theme
  }
);

实践三:第三方库类型扩展

// 扩展 Express 的 Request 类型
declare module 'express-serve-static-core' {
  interface Request {
    user?: {
      id: string;
      username: string;
      role: 'admin' | 'user';
      permissions: string[];
    };
    traceId?: string;
  }

  interface Response {
    sendSuccess<T>(data: T): void;
    sendError(code: string, message: string): void;
  }
}

// 扩展 Jest 的 expect 匹配器
declare global {
  namespace jest {
    interface Matchers<R> {
      toBeWithinRange(floor: number, ceiling: number): R;
    }
  }
}

// 扩展 Node.js 的 process.env
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      DATABASE_URL: string;
      JWT_SECRET: string;
      NODE_ENV: 'development' | 'production' | 'test';
    }
  }
}

实践四:递归类型的性能优化

// 深度合并的递归实现可能触发编译器深度限制
// 优化:限制递归深度

type DeepMerge<T, U, Depth extends number = 5> = Depth extends 0
  ? T & U
  : {
      [K in keyof T | keyof U]: K extends keyof T
        ? K extends keyof U
          ? T[K] extends object
            ? U[K] extends object
              ? DeepMerge<T[K], U[K], Subtract<Depth, 1>>
              : U[K]
            : U[K]
          : T[K]
        : K extends keyof U
          ? U[K]
          : never;
    };

// 辅助类型:减法
type Subtract<A extends number, B extends number> = ...;

// 使用:默认深度 5,超过后直接交叉
type Merged = DeepMerge<ComplexA, ComplexB>;

案例研究

案例一:企业级权限系统

背景:某企业系统需要支持细粒度权限控制,不同角色拥有不同的权限组合。

// 基础权限
type ReadPermission = { canRead: true; readScope: 'own' | 'all' };
type WritePermission = { canWrite: true; writeScope: 'own' | 'all' };
type DeletePermission = { canDelete: true; deleteScope: 'own' | 'all' };
type AdminPermission = {
  canAdmin: true;
  canManageUsers: boolean;
  canViewAuditLog: boolean;
};

// 角色权限组合
type ViewerRole = ReadPermission;
type EditorRole = ReadPermission & WritePermission;
type ManagerRole = ReadPermission & WritePermission & DeletePermission;
type AdminRole = ReadPermission & WritePermission & DeletePermission & AdminPermission;

// 用户类型根据角色确定权限
type UserWithRole<R> = {
  id: string;
  name: string;
  email: string;
} & R;

// 类型安全的服务函数
function deleteResource<R extends { canDelete: true }>(
  user: UserWithRole<R>,
  resourceId: string
) {
  // user.canDelete 在类型层面保证为 true
  if (user.deleteScope === 'own') {
    // 仅能删除自己的资源
  } else {
    // 可以删除任何资源
  }
}

// 调用
const admin: UserWithRole<AdminRole> = {
  id: '001',
  name: '管理员',
  email: 'admin@example.com',
  canRead: true,
  readScope: 'all',
  canWrite: true,
  writeScope: 'all',
  canDelete: true,
  deleteScope: 'all',
  canAdmin: true,
  canManageUsers: true,
  canViewAuditLog: true,
};

deleteResource(admin, 'res-001'); // 合法

const viewer: UserWithRole<ViewerRole> = {
  id: '002',
  name: '访客',
  email: 'viewer@example.com',
  canRead: true,
  readScope: 'own',
};

// deleteResource(viewer, 'res-001'); // 编译错误:缺少 canDelete

案例二:GraphQL 类型增强

背景:使用 GraphQL Code Generator 生成的类型需要扩展自定义字段。

// Code Generator 生成的 User 类型
namespace Generated {
  interface User {
    id: string;
    name: string;
    email: string;
    role: string;
    createdAt: string;
    updatedAt: string;
  }
}

// 自定义扩展:添加计算字段与类型修正
interface User extends Generated.User {
  // 类型修正:role 改为联合类型
  role: 'admin' | 'user' | 'guest';
  // 类型修正:日期字段改为 Date
  createdAt: Date;
  updatedAt: Date;
  // 新增计算字段
  displayName: string;
  initials: string;
}

// 转换函数
function transformUser(raw: Generated.User): User {
  return {
    ...raw,
    role: raw.role as 'admin' | 'user' | 'guest',
    createdAt: new Date(raw.createdAt),
    updatedAt: new Date(raw.updatedAt),
    displayName: raw.name,
    initials: raw.name.split(' ').map(n => n[0]).join('').toUpperCase(),
  };
}

案例三:插件化架构

背景:构建一个支持插件扩展的应用框架,核心类型与插件类型通过交叉组合。

// 核心应用接口
interface CoreApp {
  version: string;
  config: AppConfig;
  start(): Promise<void>;
  stop(): Promise<void>;
}

// 插件接口
interface Plugin<T extends {} = {}> {
  name: string;
  install(app: CoreApp): T;
}

// 插件化的应用类型
type PluggableApp<P extends Plugin[]> = CoreApp & {
  [K in keyof P]: P[K] extends Plugin<infer T> ? T : never;
}[number];

// 示例插件
class AuthPlugin implements Plugin<{ auth: AuthService }> {
  name = 'auth';
  install(app: CoreApp) {
    return { auth: new AuthService(app.config) };
  }
}

class DatabasePlugin implements Plugin<{ db: DatabaseService }> {
  name = 'database';
  install(app: CoreApp) {
    return { db: new DatabaseService(app.config) };
  }
}

// 创建应用
async function createApp(plugins: Plugin[]) {
  const core: CoreApp = {
    version: '1.0.0',
    config: loadConfig(),
    async start() { /* ... */ },
    async stop() { /* ... */ },
  };

  const extensions = plugins.reduce((acc, plugin) => {
    return { ...acc, ...plugin.install(core) };
  }, {});

  return { ...core, ...extensions };
}

// 使用
const app = await createApp([new AuthPlugin(), new DatabasePlugin()]);
// app.auth 类型为 AuthService
// app.db 类型为 DatabaseService

案例四:状态机类型设计

背景:使用交叉类型与联合类型设计有限状态机的类型安全模型。

// 状态机基础状态
interface BaseState {
  context: { userId: string };
  timestamp: number;
}

// 各状态定义
interface IdleState extends BaseState {
  status: 'idle';
}

interface LoadingState extends BaseState {
  status: 'loading';
  progress: number;
}

interface SuccessState extends BaseState {
  status: 'success';
  data: { id: string; name: string };
}

interface ErrorState extends BaseState {
  status: 'error';
  error: { code: string; message: string };
  retryCount: number;
}

// 状态联合类型
type AppState = IdleState | LoadingState | SuccessState | ErrorState;

// 状态转换函数:类型安全
function transition(state: AppState, event: string): AppState {
  switch (state.status) {
    case 'idle':
      if (event === 'LOAD') {
        return { ...state, status: 'loading', progress: 0 };
      }
      break;
    case 'loading':
      if (event === 'SUCCESS') {
        return { ...state, status: 'success', data: { id: '1', name: 'result' } };
      }
      if (event === 'ERROR') {
        return { ...state, status: 'error', error: { code: 'E1', message: 'Failed' }, retryCount: 0 };
      }
      if (event === 'PROGRESS') {
        return { ...state, progress: state.progress + 10 };  // state.progress 类型安全
      }
      break;
    case 'error':
      if (event === 'RETRY') {
        return { ...state, status: 'loading', progress: 0, retryCount: state.retryCount + 1 };
      }
      break;
  }
  return state;
}

习题

基础题

  1. 概念理解:解释交叉类型 A & B 的集合论语义,说明为何称为 “Intersection Type”。

  2. 属性冲突:给定以下类型,写出 C 的完整类型定义并解释:

    type A = { id: string; name: string; tags: string[] };
    type B = { id: number; age: number; tags: number[] };
    type C = A & B;
  3. 接口合并:以下代码合并后 Window 接口的完整类型是什么?

    interface Window { myAPI: { version: string } }
    interface Window { myAPI: { version: string; debug: boolean } }
  4. Override 工具:实现 Override<T, U> 工具类型,确保 U 的键必须是 T 的子集,并返回用 U 覆盖 T 后的新类型。

进阶题

  1. Mixin 实现:使用交叉类型实现一个 Loggable Mixin,为任意类添加 log(message: string): void 方法,并保留原类的所有成员与方法。

  2. 深度合并:实现 DeepMerge<T, U> 工具类型,要求:

    • 递归合并嵌套对象
    • 非对象字段以 U 为准
    • 处理数组合并语义(覆盖而非合并)
    • 限制递归深度为 5 层
  3. 条件 Props:为一个表单组件设计 Props 类型,满足:

    • type: 'text' 时有 placeholdermaxLength
    • type: 'select' 时有 optionsmultiple
    • type: 'date' 时有 minmax(Date 类型)
    • 所有类型共享 valueonChangedisabled
  4. 第三方库扩展:扩展 Express 的 Request 接口,添加:

    • user?: { id: string; role: 'admin' | 'user' }
    • traceId: string
    • 确保扩展在全局生效。

挑战题

  1. 类型安全的事件系统:设计一个事件总线类型,要求:

    • 支持注册多个事件处理器
    • 事件类型与处理器参数类型安全
    • 使用交叉类型组合多个事件源
    • 支持事件取消与优先级
  2. 递归类型终止性证明:证明以下 DeepMerge 类型在输入为有限深度对象时必然终止,并计算其最大递归深度:

    type DeepMerge<T, U> = Omit<T, keyof U> & {
      [K in keyof U]: K extends keyof T
        ? T[K] extends object
          ? U[K] extends object
            ? DeepMerge<T[K], U[K]>
            : U[K]
          : U[K]
        : U[K];
    };
  3. 插件化框架:设计一个支持插件动态加载的应用框架类型系统,要求:

    • 插件可以扩展核心接口
    • 插件之间可以声明依赖
    • 插件提供的接口类型安全
    • 支持插件卸载后的类型收窄

参考文献

  1. Pierce, B. C. (2002). Types and programming languages. MIT Press. https://doi.org/10.7551/mitpress/4236.001.0001

  2. Pierce, B. C. (2004). Advanced topics in types and programming languages. MIT Press. https://doi.org/10.7551/mitpress/4236.001.0001

  3. Microsoft. (2024). TypeScript Handbook: Intersection Types. https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types

  4. Microsoft. (2024). TypeScript Handbook: Declaration Merging. https://www.typescriptlang.org/docs/handbook/declaration-merging.html

  5. Bierman, G., Abadi, M., & Torgersen, M. (2014). Understanding TypeScript. Proceedings of the 28th European Conference on Object-Oriented Programming (ECOOP ‘14), 257-281. https://doi.org/10.1007/978-3-662-44202-9_11

  6. Cardelli, L., & Wegner, P. (1985). On understanding types, data abstraction, and polymorphism. ACM Computing Surveys, 17(4), 471-523. https://doi.org/10.1145/6041.6042

  7. Cook, W. R., Hill, W. L., & Canning, P. S. (1990). Inheritance is not subtyping. Proceedings of the 17th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL ‘90), 125-135. https://doi.org/10.1145/96709.96721

  8. Bruce, K. B., Cardelli, L., Castagna, G., Eifrig, J., Smith, S. F., Trifonov, V., White, D., & Leavens, G. T. (1995). On binary methods. Theory and Practice of Object Systems, 1(3), 221-242. https://doi.org/10.1002/tpao.1

  9. Compagnoni, A. B. (1995). Higher-order subtyping with intersection types. PhD Thesis, University of Edinburgh. https://doi.org/10.1.1.35.9280

  10. Reynolds, J. C. (1997). Design of the programming language Forsythe. Technical Report CMU-CS-96-136, Carnegie Mellon University.

  11. Apple Inc. (2014). The Swift Programming Language: Protocols. https://docs.swift.org/swift-book/LanguageGuide/Protocols.html (参考 Trait 组合语义)

  12. TypeScript Community. (2024). Type-level programming patterns. https://github.com/type-challenges/type-challenges

延伸阅读

  • TypeScript Deep Divehttps://basarat.gitbook.io/typescript/type-system — 深入讲解 TypeScript 类型系统,包括交叉类型与声明合并。
  • Type Challengeshttps://github.com/type-challenges/type-challenges — 通过练习深入理解类型体操,包含交叉类型与递归类型的进阶题目。
  • Effect Schemahttps://effect.website/docs/schema/introduction — Effect 生态的 Schema 库,大量使用交叉类型与条件类型。
  • type-festhttps://github.com/sindresorhus/type-fest — 实用工具类型集合,包含 MergeDeepMergeSimplify 等。
  • Types and Programming Languages(Benjamin C. Pierce 著):类型系统的经典教材,深入理解子类型、交集类型的理论基础。
  • Advanced Topics in Types and Programming Languages(Pierce 编):涵盖交叉类型、行多态(Row Polymorphism)等高级主题。
  • Object-Oriented Programming: The CLOS Perspective(Paepcke 编):理解 Mixin 与多继承的运行时语义。
  • Scala by Example(Martin Odersky 著):Scala Trait 线性化的实现,对比 TypeScript 交叉类型的设计差异。
返回入门指南