专题知识学习:TypeScript

一、TypeScript 基础与核心概念

1.1 类型系统深入

1.1.1 基本类型与类型推断机制

基本类型(Primitive Types)

类型 描述 示例
string 字符串类型 let name: string = “Alice”
number 数值类型(含整数/浮点数) let age: number = 30
boolean 布尔类型 let isDone: boolean = true
null 空值(需开启 strictNullChecks) let n: null = null
undefined 未定义值 let u: undefined = undefined
symbol 唯一标识符(ES6) const sym: symbol = Symbol()
bigint 大整数(ES2020) let big: bigint = 100n
void 无返回值(函数) function warn(): void { console.log() }

类型推断机制(Type Inference)

(1) 变量初始化推断

let a = 10;          // 推断为 number
const b = "text";    // 推断为 "text"(字面量类型)
const c = { x: 1 };  // 推断为 { x: number }

(2) 函数返回值推断

function add(x: number, y: number) {
  return x + y;      // 自动推断返回 number
}
const fn = () => true; // 推断返回 boolean

(3) 上下文推断(Contextual Typing)

// 回调函数参数根据上下文推断类型
[1, 2, 3].map(v => v * 2);       // v 推断为 number

// 事件处理函数参数推断
window.addEventListener("click", e => {
  console.log(e.clientX);         // e 推断为 MouseEvent
});

特殊推断场景

场景 推断行为 示例
未初始化变量 推断为 any(非严格模式) let x; → x: any
联合类型初始化 推断所有可能类型的联合 let arr = [0, “a”] → (number string)[]
const 常量断言 推断为字面量类型 const size = 100 as const → 100

类型推断与显示注解对比

场景 推荐方式 原因
函数参数 显示注解 避免 any 传播风险
复杂对象字面量 显示注解或接口 确保结构符合预期
简单变量初始化 依赖类型推断 减少冗余代码
函数返回值 依赖类型推断 保持与实现同步(除公开API文档外)

1.1.2 字面量类型与联合类型实战

字面量类型(Literal Types)

核心概念:将值作为类型使用,限制变量只能取特定值。

分类 语法示例 类型表示
字符串字面量 let dir: “UP” = “UP” “UP”
数字字面量 let size: 100 = 100 100
布尔字面量 let flag: true = true true
模板字面量 type T = prefix_${string}`` 模式匹配类型

工程价值:

// 替代枚举(更轻量)
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function fetchData(method: HttpMethod) {...}

fetchData("GET");  // ✅
fetchData("COPY"); // 🚫 编译错误:非允许值

联合类型(Union Types)

核心概念:组合多个类型,表示值可以是其中任意一种类型。

语法与特性:

type ID = string | number;         // 基础联合
type Status = "idle" | "loading";  // 字面量联合
type Shape = Circle | Square;      // 对象联合

// 联合类型特性:
let id: ID = "abc123"; // ✅ 允许字符串
id = 100;              // ✅ 允许数字
id = true;             // 🚫 不允许布尔值

类型收窄实战(Type Narrowing)

核心方法:在代码分支中缩小联合类型范围

技术 示例 适用场景
typeof 守卫 if (typeof val === "string") { val.toUpperCase() } 基本类型联合
instanceof 守卫 if (err instanceof Error) { console.log(err.message) } 类实例联合
相等性检查 if (status === "loading") { showSpinner() } 字面量联合
自定义守卫 function isFish(pet: Fish Bird): pet is Fish { return "swim" in pet } 复杂对象联合
可辨识联合 通过共同字段区分类型(见下例) 对象联合(设计模式)

可辨识联合实战:

// Step 1: 定义具有kind字段的联合类型
type Shape = 
  | { kind: "circle"; radius: number } 
  | { kind: "square"; size: number };

// Step 2: 使用kind字段收窄类型
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;  // 此分支shape被收窄为圆形
    case "square":
      return shape.size ** 2;              // 此分支shape被收窄为方形
  }
}

工程最佳实践

(1) 避免过度使用字面量联合

// 不推荐:直接使用字面量
function setColor(color: "red" | "green" | "blue") {...}

// 推荐:使用命名类型
type PrimaryColor = "red" | "green" | "blue";
function setColor(color: PrimaryColor) {...}

(2) 联合类型与函数重载配合

// 根据参数类型返回不同结果
function padLeft(value: string, padding: string | number): string {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;  // 数字处理
  }
  return padding + value;                // 字符串处理
}

1.1.3 any/unknown/never 的区别与应用场景

核心类型对比

特性 any unknown never
类型安全性 ❌ 完全禁用类型检查 ✅ 安全容器(需显式类型断言) ✅ 表示不可能存在的值
可赋值给 任意类型 仅 any 和 unknown 任意类型(底部类型)
可接受赋值 任意类型 任意类型 不可赋值(除 never 本身)
操作自由度 直接访问属性/方法 必须先断言具体类型 无法实例化

any 类型详解

设计目的:用于兼容 JavaScript 无类型代码或第三方库缺失类型声明。

典型场景:

// 场景1:迁移遗留JS代码
const legacyData: any = getUntypedData();
legacyData.invalidMethod(); // 编译通过,运行时可能报错

// 场景2:动态属性访问
const obj: any = {};
obj.arbitraryProperty = 123; // 允许任意属性赋值

风险警示:使用 any 会破坏类型系统的安全性,应通过 tsconfig.json 开启 noImplicitAny 严格检查。

unknown 类型详解

设计目的:提供类型安全的顶层容器,替代 any 处理不确定类型。

标准使用模式:

// 1. 接收不确定类型值
const userInput: unknown = fetchUserInput();

// 2. 使用时显式断言
if (typeof userInput === "string") {
  console.log(userInput.toUpperCase()); // 收窄为string
}

// 3. 或使用类型断言
const parsed = JSON.parse(jsonString) as unknown; // 安全容器

工程实践:

// 安全反序列化函数
function safeParse(json: string): unknown {
  try {
    return JSON.parse(json);
  } catch {
    return undefined;
  }
}
const data = safeParse('{"name":"Alice"}');
if (data && typeof data === "object" && "name" in data) {
  console.log(data.name); // 通过类型守卫安全访问
}

never 类型详解

设计目的:表示永远不会发生的值或不可达的代码分支。

核心应用场景:

// 场景1:抛出异常的函数
function error(message: string): never {
  throw new Error(message);
}

// 场景2:死循环函数
function infiniteLoop(): never {
  while (true) {}
}

// 场景3:穷尽检查(Exhaustiveness checking)
type Shape = "circle" | "square";
function getArea(shape: Shape): number {
  switch (shape) {
    case "circle": return Math.PI * r**2;
    case "square": return s**2;
    default: 
      const _exhaustiveCheck: never = shape; // 若新增shape类型会报错
      return _exhaustiveCheck;
  }
}

类型系统特性:

// never 是任意类型的子类型
declare const n: never;
const a: string = n; // ✅ 编译通过

// 空数组类型推断
const emptyArray = []; // 推断为 never[]

工程选择指南

场景 推荐类型 原因
第三方JS库迁移 any 快速过渡期方案
API响应解析 unknown 安全处理动态数据结构
错误处理函数 never 明确表示函数不会正常返回
类型守卫中不可达分支 never 触发穷尽检查
泛型编程中的空集合 never 表示不可能存在的类型(如 keyof never = never)

1.1.4 类型断言与类型守卫实现原理

类型断言(Type Assertions)

本质:开发者向编译器提供更具体的类型信息(不改变运行时值)

两种语法形式:

// 尖括号语法(JSX中不可用)
const strLength: number = (<string>someValue).length;

// as 语法(推荐)
const el = document.getElementById('app') as HTMLDivElement;

使用场景:

(1) DOM 元素类型细化

const input = document.querySelector('input[type="text"]') as HTMLInputElement;
input.value = 'new value'; // 安全访问value属性

(2) 联合类型收窄

function padLeft(value: string, padding: string | number) {
  if ((padding as number) > 0) { // 断言为number
    return " ".repeat(padding as number) + value;
  }
  return (padding as string) + value;
}

(3) 处理any类型

const unknownData: any = fetchData();
const validData = unknownData as UserData; // 断言为具体类型

注意事项:

  • 断言必须满足”结构兼容”(如不能将 string 断言为 number)
  • 双重断言规避限制(慎用):
    const str = 'hello';
    const num = str as any as number; // 绕过类型检查

类型守卫(Type Guards)

原理:通过运行时检查触发编译时类型收窄

内置守卫机制:

类型守卫 语法示例 收窄效果
typeof typeof val === “string” 基本类型 → 具体类型
instanceof obj instanceof Date 类实例 → 具体类
in 操作符 “prop” in obj 对象 → 包含该属性类型
字面量相等检查 status === “success” 联合 → 具体字面量类型

示例:

function printValue(val: string | number) {
  if (typeof val === "string") {
    console.log(val.toUpperCase()); // val收窄为string
  } else {
    console.log(val.toFixed(2)); // val收窄为number
  }
}

自定义类型守卫

实现原理:返回类型谓词(arg is T)

标准模式:

function isString(val: unknown): val is string {
  return typeof val === "string";
}

function process(input: unknown) {
  if (isString(input)) {
    input.trim(); // input被收窄为string
  }
}

复杂对象守卫实战:

interface Cat { meow(): void }
interface Dog { bark(): void }

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined;
}

function handleAnimal(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow(); // 收窄为Cat
  } else {
    animal.bark(); // 收窄为Dog
  }
}

可辨识联合(Discriminated Unions)

特殊类型守卫:通过共同字段自动收窄

实现原理:

  • 定义具有相同判别字段的联合类型
  • 通过判别字段值收窄类型
    type NetworkState =
      | { state: "loading" }
      | { state: "success", response: string }
      | { state: "error", message: string };
    
    function logState(state: NetworkState) {
      switch (state.state) {
        case "loading":
          console.log("Loading..."); // state: { state: "loading" }
          break;
        case "success":
          console.log(state.response); // state: { state: "success", response: string }
          break;
        case "error":
          console.error(state.message); // state: { state: "error", message: string }
          break;
      }
    }

工程实践要点

  1. 优先使用类型守卫:比类型断言更安全(运行时验证)
  2. 避免过度断言:过度使用 as 会弱化类型检查
  3. 守卫函数复用:提取复杂判断逻辑为可复用守卫
  4. 可辨识联合设计:适合管理复杂状态机类型
    // 状态机类型守卫示例
    type State = { status: 'idle' } | { status: 'fetching'; startTime: number };
    
    function handleState(state: State) {
      if (state.status === 'fetching') {
        const duration = Date.now() - state.startTime; // 安全访问startTime
      }
    }

1.1.5 结构化类型系统与鸭子类型

核心概念解析

术语 定义 对比模型
结构化类型系统 类型兼容性基于类型的结构属性(而非名称) TypeScript, Go
名义类型系统 类型兼容性基于类型声明名称(需显式继承) Java, C#
鸭子类型 “如果它走起来像鸭子、叫起来像鸭子,那么它就是鸭子”(关注行为而非具体类型) 动态语言哲学

结构化类型系统原理

基本规则:

  • 类型 A 兼容类型 B 当且仅当 A 拥有 B 的所有必需成员(成员名和类型匹配)

示例:

interface Vector2D {
  x: number;
  y: number;
}

// 类无需显式实现接口(结构匹配即可)
class Point2D {
  constructor(public x: number, public y: number) {}
}

const v: Vector2D = new Point2D(1, 2); // ✅ 兼容

属性检查规则:

interface Person {
  name: string;
  age: number;
}

// 直接赋值字面量时触发严格检查
const p: Person = { 
  name: "Alice",
  age: 30,
  gender: "female"  // 🚫 错误!多余属性
};

// 通过中间变量赋值(绕过额外属性检查)
const obj = { name: "Bob", age: 25, gender: "male" };
const p2: Person = obj; // ✅ 兼容(结构满足核心属性)

鸭子类型实战场景

场景1:函数参数兼容

interface Loggable {
  log: () => void;
}

class ConsoleLogger {
  log() { console.log("Logging...") }
}

class FileLogger {
  log() { fs.writeFile("log.txt", "...") }
}

// 接受任何具有log方法的对象
function logOutput(logger: Loggable) {
  logger.log();
}

logOutput(new ConsoleLogger()); // ✅
logOutput(new FileLogger());    // ✅

场景2:API响应处理

interface ApiResponse {
  data: unknown;
  status: number;
}

// 无需强制转换,只要结构匹配
const response = await fetchAPI();
processResponse(response); 

function processResponse(res: ApiResponse) {
  if (res.status === 200) {
    parseData(res.data);
  }
}

结构化类型的边界控制

(1) 精确类型控制(避免意外匹配)

// 使用独特标识防止结构误匹配
interface User {
  _type: "user"; // 唯一标识
  id: string;
  name: string;
}

interface Admin {
  _type: "admin"; // 唯一标识
  id: string;
  privileges: string[];
}

function handleEntity(entity: User | Admin) {
  if (entity._type === "user") {
    // 安全收窄到User类型
  }
}

(2) 可选属性与未知属性处理

interface Config {
  width: number;
  height: number;
  debug?: boolean; // 可选属性
  [key: string]: any; // 允许未知属性
}

const config: Config = {
  width: 100,
  height: 200,
  color: "blue" // ✅ 允许额外属性(因索引签名存在)
};

与名义类型系统的互操作

模拟名义类型(通过交叉类型):

// 创建名义类型标记
type UserID = string & { __brand: "UserID" };
type OrderID = string & { __brand: "OrderID" };

// 类型创建函数
function createUserID(id: string): UserID {
  return id as UserID;
}

// 使用示例
const userId: UserID = createUserID("user-123");
const orderId: OrderID = "order-456" as OrderID;

function getUser(id: UserID) {...}

getUser(userId); // ✅
getUser(orderId); // 🚫 编译错误:类型不兼容

工程实践要点

  1. 优先使用接口而非类:接口更符合结构化类型哲学(轻量级契约)
  2. 避免过度匹配问题:通过最小化接口属性减少意外兼容
  3. 利用类型推导优势:
    // 自动推导返回类型符合结构
    function createPoint(x: number, y: number) {
      return { x, y, z: 0 }; // 自动兼容 Vector2D
    }
  4. 鸭子类型测试策略:
    // 运行时验证对象是否满足结构
    function isVector2D(obj: any): obj is Vector2D {
      return "x" in obj && "y" in obj;
    }

1.2 接口与面向对象

1.2.1 接口高级特性(可选属性、只读属性、索引签名)

可选属性(Optional Properties)

语法:在属性名后添加 ?

作用:允许对象缺少该属性

interface UserProfile {
  name: string;
  age?: number;  // 可选属性
}

const user1: UserProfile = { name: "Alice" };          // ✅ age 可省略
const user2: UserProfile = { name: "Bob", age: 30 };  // ✅

运行时检查:

if ("age" in user1) {
  console.log(user1.age.toFixed()); // 安全访问
}

只读属性(Readonly Properties)

语法:使用 readonly 修饰符

作用:初始化后禁止修改

interface Config {
  readonly apiKey: string;
  endpoint: string;
}

const config: Config = {
  apiKey: "abc123",
  endpoint: "/api"
};

config.endpoint = "/new"; // ✅ 允许修改
config.apiKey = "xyz";    // 🚫 编译错误:只读属性

特殊场景 - 只读数组:

interface ImmutableData {
  readonly tags: string[];
}

const data: ImmutableData = { tags: ["frontend", "react"] };
data.tags.push("typescript"); // ✅ 允许(数组引用未变)
data.tags = ["new"];         // 🚫 禁止重新赋值

索引签名(Index Signatures)

语法:[key: KeyType]: ValueType

作用:定义动态属性名

基础用法:

interface StringMap {
  [key: string]: number; // 允许任意字符串属性名
}

const scores: StringMap = {
  math: 95,
  science: 90
};
scores["english"] = 88; // ✅ 动态添加

混合使用(固定属性 + 索引签名):

interface HybridInterface {
  id: string;          // 固定属性
  [key: string]: any;  // 动态属性
}

const obj: HybridInterface = {
  id: "123",
  tempData: { /*...*/ }, // ✅ 动态属性
  count: 10             // ✅
};
obj.id = "456";         // ✅ 固定属性可修改

索引类型限制:

索引类型 示例 说明
string [key: string]: T 支持所有字符串键
number [index: number]: T 数组式访问(实际键仍为字符串)
symbol [sym: symbol]: T ES6 Symbol 键
模板字面量 [key: test_${string}]: T TS 4.4+ 模式匹配

高级组合应用

场景:配置对象

interface AppConfig {
  readonly version: string;   // 只读版本号
  env?: "dev" | "prod";       // 可选环境
  [option: string]: unknown;  // 其他动态配置
}

const config: AppConfig = {
  version: "1.0.0",
  debug: true,       // ✅ 动态属性
  maxRetries: 3      // ✅
};

config.version = "2.0"; // 🚫 禁止修改只读属性

索引签名类型约束:

// 要求所有属性值类型兼容
interface ConsistentValues {
  name: string;      // ✅ 兼容 string | number
  [key: string]: string | number;
}

// 错误示例(类型冲突)
interface Invalid {
  id: boolean;       // 🚫 与索引签名类型不兼容
  [key: string]: string;
}

工程实践要点

  1. 优先显式声明重要属性:避免过度依赖索引签名
  2. 只读属性用于不变数据:如配置ID、版本号等
  3. 可选属性替代空值:比 property: null 更语义化
  4. 索引签名边界控制:
    // 精确控制动态属性类型
    interface SafeDynamic {
      [key: string]: string | undefined; // 必须显式检查
    }
    
    function getValue(obj: SafeDynamic, key: string) {
      const value = obj[key];
      if (value === undefined) {
        throw new Error("Missing property");
      }
      return value; // 此时类型收窄为 string
    }

1.2.2 类继承体系与访问修饰符

访问修饰符(Access Modifiers)

控制类成员的可访问性:

修饰符 类内部 子类 类实例 说明
public 默认修饰符(可省略)
protected 仅类内部和子类访问
private 仅类内部访问
#field ES2022 私有字段(运行时私有)
class Person {
  public name: string;          // 公共属性(默认)
  protected id: string;         // 受保护属性
  private secret: string;       // 私有属性
  #internalCode: number;        // ES私有字段(TypeScript 3.8+)

  constructor(name: string) {
    this.name = name;
    this.id = "P1001";
    this.secret = "confidential";
    this.#internalCode = 12345;
  }
}

class Employee extends Person {
  viewDetails() {
    console.log(this.name);    // ✅ 公共属性
    console.log(this.id);      // ✅ 受保护属性(子类可访问)
    console.log(this.secret);  // 🚫 错误!私有属性不可访问
    console.log(this.#internalCode); // 🚫 错误!ES私有字段不可访问
  }
}

const emp = new Employee("Alice");
console.log(emp.name);        // ✅
console.log(emp.id);          // 🚫 错误!受保护属性
console.log(emp.secret);      // 🚫 错误!私有属性

类继承体系

(1) 基本继承语法:

class Animal {
  move() {
    console.log("Moving...");
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}

const dog = new Dog();
dog.move(); // ✅ 继承自Animal
dog.bark(); // ✅ 自身方法

(2) 构造函数继承:

class Vehicle {
  constructor(public type: string) {}
}

class Car extends Vehicle {
  constructor(type: string, public brand: string) {
    super(type); // 必须调用super
  }
}

const myCar = new Car("Sedan", "Toyota");
console.log(myCar.type);  // "Sedan"
console.log(myCar.brand); // "Toyota"

方法重写(Override)

规则:

  • 使用 override 关键字(TS 4.3+)
  • 子类方法必须兼容父类方法签名
    class Base {
      greet() {
        console.log("Hello!");
      }
    }
    
    class Derived extends Base {
      override greet(name?: string) {
        if (name) {
          console.log(`Hello, ${name}!`);
        } else {
          super.greet(); // 调用父类方法
        }
      }
    }
    
    const d = new Derived();
    d.greet();       // "Hello!" (调用父类方法)
    d.greet("Alice");// "Hello, Alice!"

错误重写示例:

class InvalidDerived extends Base {
  override greet(name: string) { 
    // 🚫 错误!缺少可选参数的无参调用方式
  }
}

访问器修饰符(Getter/Setter)

class Temperature {
  private _celsius = 0;

  // 带访问修饰符的getter/setter
  public get celsius(): number {
    return this._celsius;
  }

  public set celsius(value: number) {
    if (value < -273.15) throw new Error("Invalid temperature");
    this._celsius = value;
  }

  // 只读属性(无setter)
  public get fahrenheit(): number {
    return (this._celsius * 9) / 5 + 32;
  }
}

const temp = new Temperature();
temp.celsius = 25;      // ✅ 调用setter
console.log(temp.fahrenheit); // 77 ✅ 调用getter
temp.fahrenheit = 100;  // 🚫 错误!无setter

静态成员(Static Members)

特性:

  1. 通过类名直接访问
  2. 不可通过实例访问
  3. 可继承
    class Database {
      private static instance: Database;
      
      public static getInstance(): Database {
        if (!Database.instance) {
          Database.instance = new Database();
        }
        return Database.instance;
      }
      
      public static version = "1.0";
    }
    
    // 访问静态成员
    const db = Database.getInstance();
    console.log(Database.version); // "1.0"
    
    class MyDB extends Database {
      static logVersion() {
        console.log("DB Version:", super.version); // 访问父类静态属性
      }
    }
    
    MyDB.logVersion(); // "DB Version: 1.0"

工程实践要点

  1. 最小化公开接口:
    class SafeContainer {
      private internalData: string[] = [];
      
      public addItem(item: string) {
        this.validate(item);
        this.internalData.push(item);
      }
      
      private validate(item: string) { ... }
    }
  2. 使用protected扩展点:
    class PluginBase {
      protected coreLogic() { ... }
    }
    
    class MyPlugin extends PluginBase {
      run() {
        this.coreLogic(); // 子类可访问
      }
    }
  3. 避免过度继承(组合优于继承):
    // 使用组合替代深层继承
    class Engine { ... }
    class Wheels { ... }
    
    class Car {
      private engine = new Engine();
      private wheels = new Wheels();
    }

1.2.3 抽象类与接口实现规范

抽象类(Abstract Classes)

核心特性:

  1. 不能直接实例化
  2. 包含抽象方法(无实现)
  3. 可包含具体实现方法
  4. 通过 extends 继承
    abstract class Animal {
      // 抽象属性(必须被子类实现)
      abstract habitat: string;
      
      // 抽象方法(无实现)
      abstract makeSound(): void;
      
      // 具体方法
      move(): void {
        console.log("Moving...");
      }
    }
    
    class Bird extends Animal {
      habitat = "sky";  // 实现抽象属性
      
      // 实现抽象方法
      makeSound(): void {
        console.log("Chirp!");
      }
      
      // 扩展新方法
      fly(): void {
        console.log("Flying");
      }
    }
    
    const bird = new Bird();
    bird.makeSound(); // "Chirp!"
    bird.move();      // "Moving..."
    bird.fly();       // "Flying"
    
    const animal = new Animal(); // 🚫 错误!抽象类不能实例化

接口实现(Interface Implementation)

核心特性:

  1. 使用 implements 关键字
  2. 必须实现所有接口成员
  3. 类可同时实现多个接口
    interface Loggable {
      log(message: string): void;
    }
    
    interface Serializable {
      serialize(): string;
    }
    
    // 实现多个接口
    class Logger implements Loggable, Serializable {
      log(message: string) {
        console.log(`[LOG] ${message}`);
      }
      
      serialize() {
        return JSON.stringify(this);
      }
    }

抽象类 vs 接口

| 特性 | 抽象类 | 接口 |
| 实例化 | ❌ 不能直接实例化 | ❌ 不能实例化 |
| 方法实现 | ✅ 可包含具体实现方法 | ❌ 只能声明方法签名 |
| 成员类型 | 可包含字段/构造器/访问器等 | 只能包含方法和抽象属性 |
| 继承机制 | 单继承(extends) | 多实现(implements) |
| 访问修饰符 | ✅ 支持 | ❌ 所有成员默认为 public |
| 版本兼容 | 修改可能破坏子类 | 新增成员不影响现有实现 |

混合使用模式

场景:抽象类实现部分接口

interface Shape {
  area(): number;
  perimeter(): number;
}

abstract class Polygon implements Shape {
  // 实现部分接口方法
  abstract area(): number;  // 仍需子类实现
  
  // 提供默认实现
  perimeter(): number {
    return this.getSides().reduce((sum, side) => sum + side, 0);
  }
  
  // 抽象方法(非接口要求)
  abstract getSides(): number[];
}

class Rectangle extends Polygon {
  constructor(private width: number, private height: number) {
    super();
  }
  
  area() {
    return this.width * this.height;
  }
  
  getSides() {
    return [this.width, this.height, this.width, this.height];
  }
}

实现规范与最佳实践

  1. 接口隔离原则:
    // 拆分大接口
    interface Printer {
      print(document: Document): void;
    }
    
    interface Scanner {
      scan(): Document;
    }
    
    class AllInOnePrinter implements Printer, Scanner {
      // 分别实现小接口
    }
  2. 抽象类作为模板:
    abstract class DataProcessor {
      // 模板方法(固定流程)
      process(data: string) {
        this.validate(data);
        const cleaned = this.clean(data);
        return this.transform(cleaned);
      }
      
      protected abstract clean(data: string): string;
      protected abstract transform(data: string): unknown;
      
      private validate(data: string) {
        if (!data) throw new Error("Invalid data");
      }
    }
  3. 接口扩展抽象类:
    abstract class Component {
      abstract render(): HTMLElement;
    }
    
    interface Stateful {
      setState(state: object): void;
    }
    
    class Button extends Component implements Stateful {
      render() { return document.createElement("button"); }
      setState() { /*...*/ }
    }
  4. 避免空实现(违反接口契约):
    // 反模式:空实现
    class EmptyLogger implements Loggable {
      log() {} // 🚫 违反接口语义
    }
    
    // 正确:抛出明确错误
    class NotImplementedLogger implements Loggable {
      log() {
        throw new Error("Method not implemented");
      }
    }

类型检查要点

  1. 实现兼容性:
    interface ClockInterface {
      currentTime: Date;
      setTime(d: Date): void;
    }
    
    class Clock implements ClockInterface {
      currentTime: Date = new Date();
      
      // ✅ 参数类型兼容(Date → Date)
      setTime(d: Date) {
        this.currentTime = d;
      }
    }
  2. 可选接口成员:
    interface Config {
      required: string;
      optional?: number; // 可选属性
    }
    
    class AppConfig implements Config {
      required = "default";
      // 无需实现 optional
    }
  3. 只读接口属性:
    interface ImmutablePoint {
      readonly x: number;
      readonly y: number;
    }
    
    class Point implements ImmutablePoint {
      constructor(public readonly x: number, public readonly y: number) {}
    }

1.2.4 类静态成员与构造签名

静态成员(Static Members)

核心特性:

  1. 使用 static 关键字声明
  2. 通过类名直接访问(而非实例)
  3. 不可访问实例成员
  4. 可继承(子类可通过 super 或类名访问)
    class MathUtils {
      // 静态属性
      static PI = 3.14159;
      
      // 静态方法
      static calculateCircleArea(radius: number): number {
        return this.PI * radius * radius; // 注意:this指向类本身
      }
      
      // 错误示例:访问实例成员
      static invalidMethod() {
        console.log(this.name); // 🚫 错误!无法访问实例属性
      }
    }
    
    // 访问静态成员
    console.log(MathUtils.PI); // 3.14159
    const area = MathUtils.calculateCircleArea(5); // 78.53975
    
    class AdvancedMath extends MathUtils {
      static logPI() {
        console.log("PI value:", super.PI); // 通过super访问
        // 或 console.log(MathUtils.PI);
      }
    }

静态块(Static Blocks)

TS 4.4+ 特性:用于初始化复杂静态属性

class ConfigLoader {
  static config: Record<string, string>;
  
  static {
    // 执行复杂初始化
    try {
      this.config = JSON.parse(fs.readFileSync("config.json", "utf8"));
    } catch {
      this.config = { default: "value" };
    }
  }
}

构造签名(Constructor Signatures)

核心概念:描述类的构造函数类型

基本语法:

// 构造签名表示法
interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

// 实现构造签名
class DigitalClock implements ClockInterface {
  constructor(h: number, m: number) {} // 实现构造函数
  tick() { console.log("beep beep"); }
}

// 工厂函数使用构造签名
function createClock(
  ctor: ClockConstructor,
  hour: number,
  minute: number
): ClockInterface {
  return new ctor(hour, minute);
}

// 使用
const digital = createClock(DigitalClock, 12, 30);
digital.tick(); // "beep beep"

静态成员与构造签名实战

场景1:单例模式

class AppConfig {
  private static instance: AppConfig;
  
  // 私有构造器防止外部实例化
  private constructor(public env: string) {}
  
  // 静态访问点
  static getInstance(): AppConfig {
    if (!AppConfig.instance) {
      AppConfig.instance = new AppConfig("production");
    }
    return AppConfig.instance;
  }
}

// 使用
const config = AppConfig.getInstance();
console.log(config.env); // "production"

场景2:类注册系统

// 构造签名定义
interface PluginConstructor {
  new (): Plugin;
  pluginName: string; // 静态属性要求
}

abstract class Plugin {
  abstract run(): void;
}

// 注册表
class PluginRegistry {
  private static plugins: Record<string, PluginConstructor> = {};
  
  static register(ctor: PluginConstructor) {
    this.plugins[ctor.pluginName] = ctor;
  }
  
  static create(name: string): Plugin | null {
    const Ctor = this.plugins[name];
    return Ctor ? new Ctor() : null;
  }
}

// 插件实现
class LoggerPlugin implements Plugin {
  static pluginName = "Logger"; // 满足静态属性要求
  run() { console.log("Logging..."); }
}

// 注册并使用
PluginRegistry.register(LoggerPlugin);
const plugin = PluginRegistry.create("Logger");
plugin?.run(); // "Logging..."

高级构造签名模式

  1. 泛型构造签名
    interface Factory<T> {
      new (...args: any[]): T;
    }
    
    function createInstance<T>(Clazz: Factory<T>, ...args: any[]): T {
      return new Clazz(...args);
    }
    
    class Product {
      constructor(public id: string) {}
    }
    
    const p = createInstance(Product, "P1001");
    console.log(p.id); // "P1001"
  2. 构造函数类型别名
    // 构造函数类型定义
    type StringInitializable = {
      new (s: string): object;
    };
    
    class StringWrapper {
      constructor(public value: string) {}
    }
    
    function wrapString(ctor: StringInitializable, s: string) {
      return new ctor(s);
    }
    
    const wrapped = wrapString(StringWrapper, "test");
    console.log((wrapped as StringWrapper).value); // "test"

工程实践要点

  1. 避免过度使用静态成员:静态成员本质是全局状态,不利于测试和扩展
  2. 构造签名用于依赖注入:
    // 依赖注入容器
    class Container {
      private instances = new Map();
      
      register<T>(key: string, ctor: new (...args: any[]) => T) {
        this.instances.set(key, new ctor());
      }
      
      resolve<T>(key: string): T {
        return this.instances.get(key);
      }
    }
    
    // 使用
    const container = new Container();
    container.register("logger", ConsoleLogger);
    const logger = container.resolve<ConsoleLogger>("logger");
  3. 静态成员类型安全:
    class Validator {
      static patterns = {
        email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
        phone: /^\d{10}$/
      };
      
      static validate(type: keyof typeof Validator.patterns, value: string) {
        return this.patterns[type].test(value);
      }
    }
    
    Validator.validate("email", "test@example.com"); // ✅
    Validator.validate("postal", "12345"); // 🚫 错误!无此类型
  4. 构造签名约束:
    // 要求构造函数具有特定静态属性
    interface PluginCtor {
      new (): Plugin;
      version: string;
      dependencies?: string[];
    }
    
    function loadPlugin(ctor: PluginCtor) {
      console.log(`Loading ${ctor.name} v${ctor.version}`);
      // ...
    }

1.3 函数与泛型编程

1.3.1 函数类型定义与上下文推断

函数类型定义方式

  1. 函数声明式
    // 完整类型注解
    function add(x: number, y: number): number {
      return x + y;
    }
  2. 函数表达式
    // 变量类型注解
    const multiply: (x: number, y: number) => number = function(x, y) {
      return x * y;
    };
  3. 箭头函数
    // 类型注解在变量上
    const divide: (dividend: number, divisor: number) => number = 
      (a, b) => a / b;
  4. 对象方法
    const calculator = {
      sum: (x: number, y: number): number => x + y,
      subtract(x: number, y: number): number {
        return x - y;
      }
    };

函数类型签名详解

基本结构:

type AddFunction = (a: number, b: number) => number;

可选参数:

type GreetFunction = (name: string, prefix?: string) => string;

const greet: GreetFunction = (n, p) => `${p || "Hello"} ${n}`;
greet("Alice"); // ✅ 允许省略可选参数

默认参数:

type CreateUser = (
  name: string, 
  role: string = "user" // 默认值
) => void;

剩余参数:

type SumFunction = (...numbers: number[]) => number;

const sum: SumFunction = (...nums) => nums.reduce((a, b) => a + b, 0);
sum(1, 2, 3); // 6

上下文类型推断(Contextual Typing)

核心原理:TypeScript 根据函数被使用的位置自动推断参数类型

典型场景:

// 场景1:回调函数参数推断
[1, 2, 3].map(num => num * 2); // num 推断为 number

// 场景2:事件处理函数
window.addEventListener("click", event => {
  console.log(event.clientX); // event 推断为 MouseEvent
});

// 场景3:函数作为参数传递
function runCallback(cb: (value: string) => void) {
  cb("test");
}

runCallback(str => {
  console.log(str.toUpperCase()); // str 推断为 string
});

显式覆盖上下文类型:

// 使用类型断言覆盖推断
window.addEventListener("keydown", (event as KeyboardEvent) => {
  console.log(event.key); // 强制指定为 KeyboardEvent
});

函数类型兼容性

参数兼容规则:

type Handler = (value: string) => void;

// ✅ 兼容:参数类型更具体
const h1: Handler = (value: string | number) => {};
// ✅ 兼容:参数数量更少
const h2: Handler = () => {};
// 🚫 不兼容:参数类型冲突
const h3: Handler = (value: number) => {}; // 错误!

// 返回值兼容规则
type Factory = () => string;
// ✅ 兼容:返回值更具体
const f1: Factory = () => "hello";
// 🚫 不兼容:返回值类型冲突
const f2: Factory = () => 123; // 错误!

对象方法兼容:

interface Component {
  onEvent: (value: string) => void;
}

// ✅ 兼容:参数数量更少
const comp: Component = {
  onEvent: () => console.log("Event fired")
};

void 返回类型的特殊行为

关键特性:允许返回任意值(但返回值会被忽略)

type VoidFunc = () => void;

// 合法实现(虽然返回了值)
const fn: VoidFunc = () => "hello";

// 使用场景
const strings = ["a", "b", "c"];
const results: void[] = strings.map(s => console.log(s)); // 允许返回void

与undefined的区别:

// 必须显式返回undefined
type StrictVoid = () => undefined;
const strictFn: StrictVoid = () => undefined; // ✅
const invalidFn: StrictVoid = () => "test";   // 🚫 错误

工程实践要点

  1. 优先使用上下文推断:
    // 推荐:让TS自动推断
    [1, 2, 3].map(num => num * 2);
    
    // 不推荐:冗余注解
    [1, 2, 3].map((num: number): number => num * 2);
  2. 回调函数参数命名约定:
    // 使用描述性参数名增强可读性
    fetchUser(userId => {
      console.log(userId); // 比 `id` 更明确
    });
  3. 避免any污染函数链:
    // 危险:any类型传播
    const dangerous: (input: any) => number = input => input.length;
    
    // 安全:明确类型约束
    const safe: (input: string | any[]) => number = input => input.length;
  4. 函数重载优先于联合参数:
    // 不推荐:联合类型参数
    function padLeft(value: string, padding: string | number): string {
      /*...*/
    }
    
    // 推荐:函数重载(下一节内容)
    function padLeft(value: string, padding: string): string;
    function padLeft(value: string, padding: number): string;
    function padLeft(value: string, padding: any): string {
      /*...*/
    }

函数类型高级技巧

函数属性扩展:

interface SearchFunction {
  (source: string, subString: string): boolean;
  // 附加属性
  description: string;
}

const mySearch: SearchFunction = (src, sub) => src.includes(sub);
mySearch.description = "String search function";

构造签名函数:

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

function createClock(ctor: ClockConstructor, h: number, m: number) {
  return new ctor(h, m);
}

1.3.2 函数重载的工程实践

函数重载基础概念

核心目的:为同一函数提供多个类型签名,处理不同参数组合或返回类型

基本结构:

// 重载签名(类型声明)
function parseInput(input: string): number;
function parseInput(input: number): string;

// 实现签名(实际函数体)
function parseInput(input: string | number): number | string {
  if (typeof input === "string") {
    return parseInt(input, 10); // 返回 number
  }
  return input.toString(); // 返回 string
}

// 使用
const num = parseInput("123"); // number
const str = parseInput(123);   // string

重载规则与特性

  1. 优先级规则:
    • 从上到下匹配第一个符合的重载签名
    • 实现签名不可直接调用(对外不可见)
  2. 参数类型兼容:
    // 正确:实现签名必须兼容所有重载
    function fn(a: string): void;    // ✅
    function fn(a: string, b: number): void; // ✅
    function fn(a: string, b?: number): void {} // ✅
    
    // 错误:实现签名不兼容重载
    function fn(a: number): void;    // 🚫
    function fn(a: string): void {}  // 错误:缺少number参数处理
  3. 返回值类型约束:
    // 返回值必须覆盖所有重载
    function getValue(): string;
    function getValue(format: boolean): number;
    function getValue(format?: boolean): string | number {
      return format ? 123 : "abc";
    }

工程实践模式

模式1:参数类型转换

// 字符串和日期互转
function convert(value: string): Date;       // string → Date
function convert(value: Date): string;       // Date → string
function convert(value: string | Date): Date | string {
  if (typeof value === "string") {
    return new Date(value);
  }
  return value.toISOString();
}

const date = convert("2023-01-01");  // Date
const str = convert(new Date());     // string

模式2:配置对象重载

// 简单模式
interface SimpleConfig {
  name: string;
}

// 高级模式
interface AdvancedConfig {
  name: string;
  debug: boolean;
  retries: number;
}

function init(config: SimpleConfig): void;
function init(config: AdvancedConfig): void;
function init(config: SimpleConfig | AdvancedConfig): void {
  // 公共初始化逻辑
  console.log(`Initializing ${config.name}`);
  
  // 类型守卫处理高级配置
  if ("debug" in config) {
    console.log("Debug mode:", config.debug);
  }
}

模式3:DOM 操作重载

function getElement(id: string): HTMLElement | null;
function getElement<T extends HTMLElement>(id: string, type: new() => T): T | null;
function getElement(id: string, type?: new() => HTMLElement): HTMLElement | null {
  const el = document.getElementById(id);
  if (el && type && !(el instanceof type)) {
    throw new Error(`Element type mismatch`);
  }
  return el;
}

// 使用
const div = getElement("header", HTMLDivElement); // HTMLDivElement | null
const generic = getElement("content");            // HTMLElement | null

联合类型 vs 函数重载

场景 推荐方案 原因
参数类型决定返回值类型 函数重载 提供精确的返回类型
参数数量变化 函数重载 明确不同参数组合
参数类型相同但处理逻辑不同 联合类型 + 类型守卫 避免重载签名膨胀
复杂配置对象 函数重载 区分不同配置结构
简单类型变换 联合类型 减少样板代码

联合类型示例(适合简单场景):

// 使用联合类型替代重载
function padLeft(value: string, padding: string | number): string {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;
  }
  return padding + value;
}

最佳实践指南

  1. 控制重载数量:
    • 建议不超过 5 个重载签名
    • 过多重载会导致类型系统负担增加
  2. 公共前置处理:
    function processData(data: string): ResultA;
    function processData(data: Buffer): ResultB;
    function processData(data: string | Buffer): ResultA | ResultB {
      // 公共验证逻辑
      if (data.length === 0) throw new Error("Empty data");
      
      if (typeof data === "string") {
        /* 特定处理 */
      } else {
        /* 特定处理 */
      }
    }
  3. 使用类型参数减少重载:
    // 使用泛型替代多个重载
    function identity<T>(arg: T): T {
      return arg;
    }
    
    // 比以下重载更简洁:
    // function identity(arg: string): string;
    // function identity(arg: number): number;
    // ...
  4. 文档注释规范:
    /**
     * 创建用户
    * @param name - 用户名
    * @returns 用户ID
    */
    function createUser(name: string): string;
    
    /**
     * 创建用户(带配置)
    * @param options - 用户配置
    * @returns 用户ID
    */
    function createUser(options: UserOptions): string;
    
    function createUser(param: string | UserOptions): string {
      // ...
    }

常见错误与解决方案

错误1:忽略实现签名兼容性

// 错误实现
function foo(a: string): void;
function foo(a: number, b: string): void;
function foo(a: string | number, b?: string) {} // ✅ 正确

// 正确:添加必要的参数检查
function foo(a: string | number, b?: string) {
  if (typeof a === "number" && b === undefined) {
    throw new Error("Missing second argument");
  }
}

错误2:返回值类型不完整

// 错误:缺少boolean返回类型
function test(): string;
function test(flag: boolean): number;
function test(flag?: boolean): string | number { 
  return flag ? 123 : "abc"; 
}

// 使用
const result = test(true);  // number ✅
const result2 = test();     // string ✅
const result3 = test(false); // 🚫 错误!未声明返回boolean

解决方案:

// 修正:添加完整重载
function test(): string;
function test(flag: true): number;
function test(flag: false): string;
function test(flag?: boolean): string | number {
  return flag ? 123 : "abc";
}

高级工程案例

API 请求重载:

// 简单GET请求
function request(url: string): Promise<Response>;

// 带参数的GET请求
function request(url: string, params: Record<string, string>): Promise<Response>;

// POST请求
function request(url: string, method: "POST", body: object): Promise<Response>;

// 实现
async function request(
  url: string,
  paramsOrMethod?: Record<string, string> | "POST",
  body?: object
): Promise<Response> {
  if (paramsOrMethod === undefined) {
    return fetch(url);
  }
  
  if (typeof paramsOrMethod === "object") {
    const query = new URLSearchParams(paramsOrMethod).toString();
    return fetch(`${url}?${query}`);
  }
  
  if (paramsOrMethod === "POST" && body) {
    return fetch(url, {
      method: "POST",
      body: JSON.stringify(body)
    });
  }
  
  throw new Error("Invalid arguments");
}

1.3.3 泛型约束与默认类型

泛型约束(Generic Constraints)

核心目的:限制泛型参数必须满足特定条件

基础语法:

function identity<T extends ConstraintType>(arg: T): T {
  // ...
}

基本约束示例:

interface Lengthwise {
  length: number;
}

// T 必须包含 length 属性
function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength("hello"); // ✅ 字符串有 length
logLength([1, 2, 3]); // ✅ 数组有 length
logLength(100); // 🚫 错误!数字没有 length

多约束条件

使用 & 运算符:

interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

// T 必须同时满足 Printable 和 Loggable
function processItem<T extends Printable & Loggable>(item: T) {
  item.print();
  item.log();
}

class Document implements Printable, Loggable {
  print() { console.log("Printing document"); }
  log() { console.log("Logging document"); }
}

processItem(new Document()); // ✅

类型参数约束

约束泛型参数之间的关系:

// 确保 K 是 T 的键名
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const person = { name: "Alice", age: 30 };
getProperty(person, "name"); // ✅
getProperty(person, "email"); // 🚫 错误!不存在该属性

构造函数约束:

// 要求泛型 T 必须可构造
function createInstance<T extends { new (): InstanceType }>(ctor: T) {
  return new ctor();
}

class MyClass {}
const instance = createInstance(MyClass); // ✅

泛型默认类型(Default Types)

核心目的:为泛型参数提供默认值,简化使用

基本语法:

interface ApiResponse<T = any> {
  data: T;
  status: number;
}

// 使用默认类型
const response: ApiResponse = { 
  data: "untyped data", // any 类型
  status: 200
};

// 显式指定类型
const userResponse: ApiResponse<User> = {
  data: { name: "Alice" },
  status: 200
};

约束与默认类型组合

复杂场景应用:

// 1. 约束 + 默认类型
interface PaginatedResult<T = object> {
  items: T[];
  total: number;
}

// 2. 函数中使用
function fetchData<T extends { id: string } = { id: string }>(
  url: string
): Promise<PaginatedResult<T>> {
  // ...
}

// 使用默认约束
fetchData("/users"); // Promise<PaginatedResult<{ id: string }>>

// 指定具体类型
interface Product {
  id: string;
  name: string;
  price: number;
}
fetchData<Product>("/products"); // Promise<PaginatedResult<Product>>

工程实践模式

模式1:部分属性要求

// 要求对象至少包含特定属性
function updateRecord<T extends { id: string }>(record: T) {
  db.save(record.id, record);
}

// 允许额外属性
updateRecord({ id: "1", name: "Alice", age: 30 }); // ✅

模式2:工厂函数约束

// 要求类构造函数
function create<T extends new (...args: any) => any>(
  Ctor: T,
  ...args: ConstructorParameters<T>
): InstanceType<T> {
  return new Ctor(...args);
}

class Widget {
  constructor(public name: string) {}
}

const w = create(Widget, "MyWidget"); // Widget 实例

模式3:API 响应标准化

interface ApiResponse<T = unknown, E = Error> {
  success: boolean;
  data?: T;
  error?: E;
  timestamp: Date;
}

function createResponse<T>(data: T): ApiResponse<T> {
  return {
    success: true,
    data,
    timestamp: new Date()
  };
}

const userResponse = createResponse({ name: "Alice" }); // ApiResponse<{ name: string }>

最佳实践指南

  1. 避免过度约束:
    // 过度约束:限制为特定类型
    function process<T extends string | number>(input: T): T { ... }
    
    // 推荐:更灵活的约束
    function process<T>(input: T): T { ... }
  2. 合理使用默认类型:
    // 为常用类型提供默认值
    type Formatter<T = string> = (input: T) => string;
    
    const dateFormatter: Formatter<Date> = date => date.toISOString();
    const defaultFormatter: Formatter = str => str.toUpperCase(); // 默认 string
  3. 约束文档化:
    /**
     * 合并两个对象
    * @template T - 第一个对象的类型
    * @template U - 第二个对象的类型(必须与T兼容)
    */
    function merge<T, U extends T>(obj1: T, obj2: U): T & U {
      return { ...obj1, ...obj2 };
    }
  4. 类型参数命名约定:
    // 推荐:使用有意义的类型参数名
    function getValue<TObject, TKey extends keyof TObject>(
      obj: TObject, 
      key: TKey
    ): TObject[TKey] {
      return obj[key];
    }

复杂约束案例

递归约束:

// 树节点结构约束
interface TreeNode {
  id: string;
  children?: TreeNode[];
}

function walkTree<T extends TreeNode>(node: T) {
  console.log(node.id);
  node.children?.forEach(child => walkTree(child));
}

const tree = {
  id: "root",
  children: [
    { id: "child1" },
    { id: "child2", children: [{ id: "grandchild" }] }
  ]
};
walkTree(tree); // ✅

条件类型约束:

// 确保泛型是函数类型
type ReturnTypeOrNever<T> = T extends (...args: any) => infer R ? R : never;

function getReturnType<T extends (...args: any) => any>(
  fn: T
): ReturnTypeOrNever<T> {
  return fn() as ReturnTypeOrNever<T>;
}

const str = getReturnType(() => "hello"); // string
const num = getReturnType(() => 123); // number

常见错误解决方案

错误:约束不满足

function getSize<T extends { length: number }>(obj: T): number {
  return obj.length;
}

getSize({ size: 10 }); // 🚫 错误:缺少 length 属性

解决方案:

// 方法1:提供正确类型
getSize({ length: 10 });

// 方法2:扩展约束
interface Sizeable {
  length?: number;
  size?: number;
}

function getSize<T extends Sizeable>(obj: T): number {
  return obj.length ?? obj.size ?? 0;
}

错误:默认类型与约束冲突

interface Config<T extends string = number> { // 🚫 错误!默认类型必须满足约束
  value: T;
}

解决方案:

// 约束和默认类型需兼容
interface Config<T extends string | number = number> {
  value: T;
}

1.3.4 泛型在接口/类中的高级应用

泛型接口(Generic Interfaces)

基础模式:

// 简单泛型接口
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

// 使用
const stringPair: KeyValuePair<string, string> = {
  key: "name",
  value: "Alice"
};

const numberPair: KeyValuePair<number, boolean> = {
  key: 1,
  value: true
};

函数签名泛型:

// 带调用签名的泛型接口
interface Transformer<T, U> {
  (input: T): U;
}

// 使用
const toUpperCase: Transformer<string, string> = 
  str => str.toUpperCase();
  
const toNumber: Transformer<string, number> = 
  str => parseFloat(str);

泛型类(Generic Classes)

基本结构:

class Box<T> {
  private content: T;
  
  constructor(initialValue: T) {
    this.content = initialValue;
  }
  
  getValue(): T {
    return this.content;
  }
  
  setValue(newValue: T): void {
    this.content = newValue;
  }
}

// 使用
const stringBox = new Box<string>("Initial");
stringBox.setValue("New value");

const numberBox = new Box<number>(100);
console.log(numberBox.getValue() * 2); // 200

静态成员限制:

class Factory<T> {
  // 🚫 错误!静态成员不能引用类型参数
  static default: T;
  
  // ✅ 正确:静态成员使用独立泛型
  static create<U>(value: U): Factory<U> {
    return new Factory(value);
  }
  
  constructor(private value: T) {}
}

泛型约束的高级应用

类型参数约束:

// 确保类型T有id属性
interface Identifiable {
  id: string;
}

class Repository<T extends Identifiable> {
  private items: T[] = [];
  
  add(item: T) {
    this.items.push(item);
  }
  
  findById(id: string): T | undefined {
    return this.items.find(item => item.id === id);
  }
}

// 使用
class User implements Identifiable {
  constructor(public id: string, public name: string) {}
}

const userRepo = new Repository<User>();
userRepo.add(new User("1", "Alice"));
const user = userRepo.findById("1");

构造函数约束:

// 要求泛型T必须是类类型
function createInstance<T>(ctor: new () => T): T {
  return new ctor();
}

// 高级:带参数构造函数
function createWithParams<T>(
  ctor: new (...args: any[]) => T,
  ...args: ConstructorParameters<typeof ctor>
): T {
  return new ctor(...args);
}

class Point {
  constructor(public x: number, public y: number) {}
}

const p = createWithParams(Point, 1, 2); // Point { x: 1, y: 2 }

接口与类的组合模式

模式1:工厂接口

interface Factory<T> {
  create(): T;
}

class CarFactory implements Factory<Car> {
  create() {
    return new Car();
  }
}

class BikeFactory implements Factory<Bike> {
  create() {
    return new Bike();
  }
}

function assembleProduct<T>(factory: Factory<T>): T {
  return factory.create();
}

模式2:策略模式

interface PaymentStrategy<T> {
  processPayment(amount: number, context: T): boolean;
}

class CreditCardStrategy implements PaymentStrategy<CreditCardInfo> {
  processPayment(amount: number, card: CreditCardInfo) {
    // 信用卡支付逻辑
    return true;
  }
}

class PayPalStrategy implements PaymentStrategy<PayPalAccount> {
  processPayment(amount: number, account: PayPalAccount) {
    // PayPal支付逻辑
    return true;
  }
}

function processOrder<T>(
  amount: number,
  strategy: PaymentStrategy<T>,
  context: T
) {
  return strategy.processPayment(amount, context);
}

类型推导与默认值

类型参数默认值:

// 接口默认类型
interface PaginatedResponse<T = any> {
  data: T[];
  page: number;
  totalPages: number;
}

// 类默认类型
class Cache<T = string> {
  private map = new Map<string, T>();
  
  set(key: string, value: T) {
    this.map.set(key, value);
  }
  
  get(key: string): T | undefined {
    return this.map.get(key);
  }
}

// 使用默认类型
const stringCache = new Cache(); // Cache<string>
stringCache.set("name", "Alice");

const anyCache = new Cache<any>();
anyCache.set("user", { name: "Bob" });

上下文类型推导:

// 根据实现自动推导类型参数
class StringContainer implements Container<string> {
  add(item: string) { /*...*/ }
}

// 显式指定类型参数
class NumberContainer implements Container<number> {
  add(item: number) { /*...*/ }
}

高级工程案例

案例1:API 客户端封装

interface ApiClient<T> {
  fetchData(endpoint: string): Promise<T>;
  postData(endpoint: string, data: Partial<T>): Promise<T>;
}

class JsonApiClient<T> implements ApiClient<T> {
  constructor(private baseUrl: string) {}
  
  async fetchData(endpoint: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}/${endpoint}`);
    return response.json();
  }
  
  async postData(endpoint: string, data: Partial<T>): Promise<T> {
    const response = await fetch(`${this.baseUrl}/${endpoint}`, {
      method: 'POST',
      body: JSON.stringify(data)
    });
    return response.json();
  }
}

// 使用
interface User {
  id: string;
  name: string;
  email: string;
}

const userClient = new JsonApiClient<User>("https://api.example.com/users");
const user = await userClient.fetchData("123");

案例2:状态管理

interface State<T> {
  current: T;
  history: T[];
  update(newState: T): void;
}

class HistoryState<T> implements State<T> {
  history: T[] = [];
  
  constructor(public current: T) {
    this.history.push(current);
  }
  
  update(newState: T) {
    this.current = newState;
    this.history.push(newState);
  }
  
  getHistory(): T[] {
    return [...this.history];
  }
}

// 使用
const counterState = new HistoryState<number>(0);
counterState.update(1);
counterState.update(2);
console.log(counterState.getHistory()); // [0, 1, 2]

最佳实践指南

  1. 避免过度泛型化:
    // 不推荐:不必要的泛型
    class Printer<T> {
      print(value: T) {
        console.log(value);
      }
    }
    
    // 推荐:直接使用具体类型
    class ConsolePrinter {
      print(value: any) {
        console.log(value);
      }
    }
  2. 提供有意义的类型参数名:
    // 推荐
    interface Repository<EntityType> {
      save(entity: EntityType): void;
    }
    
    // 不推荐
    interface Repository<T> {
      save(e: T): void;
    }
  3. 默认类型用于通用场景:
    // 通用缓存默认存储对象
    class Cache<T = object> {
      private storage = new Map<string, T>();
      // ...
    }
    
    // 使用默认类型
    const objectCache = new Cache();
  4. 约束文档化:
    /**
     * 排序服务
    * @template T - 排序元素的类型
    * @template K - 排序依据的键名(必须是T的键)
    */
    class Sorter<T, K extends keyof T> {
      sort(items: T[], key: K) {
        return items.sort((a, b) => 
          a[key] > b[key] ? 1 : -1
        );
      }
    }
  5. 类与接口协同:
    // 定义泛型接口
    interface Processor<T> {
      process(input: T): void;
    }
    
    // 实现具体处理器
    class NumberProcessor implements Processor<number> {
      process(input: number) {
        console.log(input * 2);
      }
    }
    
    // 创建处理器工厂
    const processors = {
      number: new NumberProcessor()
    };
    
    function processData<T>(type: string, data: T) {
      if (processors[type]) {
        (processors[type] as Processor<T>).process(data);
      }
    }

常见问题解决方案

问题:泛型类实例方法类型丢失

class Wrapper<T> {
  constructor(private value: T) {}
  
  getValue(): T {
    return this.value;
  }
}

// 类型信息丢失
const wrapper = new Wrapper(42);
const value = wrapper.getValue(); // value 类型为 number ✅

解决方案:使用成员变量保存类型

class TypeSafeWrapper<T> {
  // 显式保存类型引用
  public readonly valueType!: T;
  
  constructor(private value: T) {}
  
  getValue(): T {
    return this.value;
  }
}

// 使用
const safeWrapper = new TypeSafeWrapper("text");
type ValueType = typeof safeWrapper.valueType; // string

问题:泛型接口实现冲突

interface Adder<T> {
  add(a: T, b: T): T;
}

// 错误:必须为所有类型参数提供实现
class NumberAdder implements Adder {
  add(a: number, b: number) { return a + b; }
}

解决方案:

// 正确:指定具体类型
class NumberAdder implements Adder<number> {
  add(a: number, b: number) { return a + b; }
}

// 或使用泛型类
class GenericAdder<T> implements Adder<T> {
  constructor(private zeroValue: T) {}
  
  add(a: T, b: T): T {
    // 实际实现需依赖具体类型
    return a; // 简化示例
  }
}

1.4 模块系统

1.4.1 ES Module 与 CommonJS 互操作

基本互操作模式

导入方向 语法示例 说明
ES → CommonJS import fs from 'fs';
import { readFile } from 'fs';
默认导入或具名导入
CommonJS → ES const esModule = require('./es-module'); 通过require加载
CommonJS → ES (解构) const { namedExport } = require('./es-module'); 解构ES模块的具名导出

ESM 导入 CommonJS 模块

  1. 默认导入:
    // 导入整个CommonJS模块
    import express from 'express';
    const app = express();
  2. 具名导入:
    // 导入特定属性(需模块支持)
    import { readFile } from 'fs';
    readFile('file.txt', 'utf8', () => {});
  3. 动态导入:
    // 异步加载CommonJS模块
    const util = await import('util');
    util.promisify(setTimeout)(1000);

类型声明要求:

// 类型声明文件需支持两种导出格式
declare module 'cjs-module' {
  const _default: any;
  export = _default;  // CommonJS导出
  export default _default; // ES默认导出
}

CommonJS 导入 ESM 模块

  1. 默认导入:
    // CommonJS 环境
    const esModule = require('./es-module').default; // 必须访问.default
    esModule.someMethod();
  2. 具名导入:
    // 解构具名导出
    const { namedExport } = require('./es-module');
  3. 异步加载:
    // 在支持top-level await的环境
    const { default: esModule } = await import('./es-module');

互操作中的类型处理

  1. CommonJS 模块类型声明:
    // 标准声明
    declare module 'cjs-module' {
      function someFunction(): void;
      const someValue: number;
      
      export = { 
        someFunction,
        someValue
      };
    }
    
    // 使用
    import cjs from 'cjs-module';
    cjs.someFunction();
  2. ES 模块类型声明:
    // ES模块声明
    declare module 'es-module' {
      export function namedExport(): void;
      export default function defaultExport(): void;
    }
    
    // CommonJS使用
    const esm = require('es-module');
    esm.default(); // 访问默认导出
    esm.namedExport(); // 访问具名导出
  3. 兼容类型声明:
    // 同时支持两种导入方式
    declare module 'dual-module' {
      const defaultExport: any;
      export default defaultExport; // ES默认导出
      export = defaultExport;       // CommonJS导出
    }

互操作陷阱与解决方案

陷阱1:默认导出混淆

// CommonJS模块
module.exports = function() {}; // 导出函数

// ES模块导入
import cjsFunc from 'cjs-module'; // ✅ 正确
import { default as cjsFunc } from 'cjs-module'; // ✅ 正确
import * as cjsModule from 'cjs-module'; 
cjsModule.default(); // ✅ 正确

陷阱2:__esModule 标志

// Babel/TS编译的ESM模块会添加
Object.defineProperty(exports, "__esModule", { value: true });

// 互操作意义:标记模块为ESM转换而来
if (module.__esModule) {
  // 按ESM规范处理
}

陷阱3:动态导入差异

// ES模块
export const value = 42;

// CommonJS导入
require('./es-module').value; // ❌ 可能未定义
require('./es-module').default.value; // ✅ 正确

工程最佳实践

  1. 统一模块格式:
    // tsconfig.json
    {
      "compilerOptions": {
        "module": "ESNext",       // 输出ESM
        "moduleResolution": "Node" // 支持Node解析规则
      }
    }
  2. 条件导出(package.json):
    {
      "exports": {
        "import": "./dist/esm/index.js",   // ESM入口
        "require": "./dist/cjs/index.js",  // CommonJS入口
        "default": "./dist/cjs/index.js"   // 回退
      }
    }
  3. 双格式发布:
    my-package/
    ├── dist/
    │   ├── esm/        // ES模块版本
    │   │   └── index.js
    │   └── cjs/        // CommonJS版本
    │       └── index.js
    ├── package.json
  4. 互操作封装层:
    // cjs-compat.ts - CommonJS封装层
    import esModule from './es-module';
    export = esModule; // 转换为CommonJS导出

TypeScript 编译选项

选项 互操作影响
esModuleInterop true (推荐) 生成兼容代码,允许默认导入CommonJS模块
allowSyntheticDefaultImports true 允许类型系统中的默认导入
module CommonJS/Node16 控制输出模块格式
moduleResolution Node16 (推荐) 支持package.json的exports和imports字段

esModuleInterop 工作原理:

// 开启前
var fs = require('fs');
exports.default = fs;

// 开启后
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var fs_1 = __importDefault(require("fs"));
exports.default = fs_1.default;

Node.js 环境互操作

  1. .mjs 与 .cjs 扩展名:
    // ES模块 (module.mjs)
    export function hello() {}
    
    // CommonJS导入 (script.cjs)
    const { hello } = require('./module.mjs'); // ❌ Node不允许
    const esm = import('./module.mjs'); // ✅ 必须使用动态导入
  2. package.json type 字段:
    {
      "type": "module", // 所有.js文件视为ES模块
      "type": "commonjs" // 默认值
    }
  3. 文件扩展名规则:
    文件扩展名 模块类型 导入方式
    .js 由type字段决定 根据type字段处理
    .mjs ES模块 只能import导入
    .cjs CommonJS 只能require导入

浏览器环境互操作

  1. 脚本类型声明:
    <!-- 加载ES模块 -->
    <script type="module" src="app.js"></script>
    
    <!-- 加载CommonJS模块(需打包转换) -->
    <script src="cjs-bundle.js"></script>
  2. 动态导入兼容:
    // 浏览器中的动态导入
    if (typeof module === 'object') {
      // Node环境
      const mod = require('./cjs-module');
    } else {
      // 浏览器环境
      import('./es-module.js').then(mod => {});
    }

互操作类型安全策略

  1. 模块扩充声明:
    // 扩充CommonJS模块类型
    declare module 'fs' {
      export function customMethod(): void;
    }
    
    // 使用
    import fs from 'fs';
    fs.customMethod(); // ✅ 类型安全
  2. 条件类型处理:
    // 区分模块类型
    type ModuleType<T> = T extends { __esModule: true } ? 
      { default: infer U } : T;
    
    function importCompat<T>(module: T): ModuleType<T> {
      // 实现互操作转换
    }
  3. 运行时类型守卫:
    // 检测ES模块
    function isESModule(module: any): module is { default: any } {
      return module.__esModule || 
        (typeof Symbol !== 'undefined' && 
        Symbol.toStringTag in module);
    }

1.4.2 命名空间与现代模块对比

命名空间(Namespaces)

核心概念:TypeScript 早期提供的逻辑分组机制,用于组织全局作用域代码

基本语法:

// 定义命名空间
namespace MyUtils {
  export function log(message: string) {
    console.log(message);
  }
  
  export namespace Math {
    export const PI = 3.14;
    export function circleArea(r: number) {
      return PI * r * r;
    }
  }
}

// 使用
MyUtils.log("Hello");
const area = MyUtils.Math.circleArea(5);

编译结果:

// 转换为闭包形式
var MyUtils;
(function (MyUtils) {
  function log(message) {
    console.log(message);
  }
  MyUtils.log = log;
  
  let Math;
  (function (Math) {
    Math.PI = 3.14;
    function circleArea(r) {
      return Math.PI * r * r;
    }
    Math.circleArea = circleArea;
  })(Math = MyUtils.Math || (MyUtils.Math = {}));
})(MyUtils || (MyUtils = {}));

现代模块(ES Modules)

核心概念:ES6 标准引入的文件级作用域隔离机制

基本语法:

// math.ts
export const PI = 3.14;
export function circleArea(r: number) {
  return PI * r * r;
}

// logger.ts
export function log(message: string) {
  console.log(message);
}

// app.ts
import { log } from './logger';
import * as MathUtils from './math';

log("Calculating area");
const area = MathUtils.circleArea(5);

核心差异对比

特性 命名空间 现代模块
作用域 全局/文件内 文件级作用域
依赖管理 手动排序<script>标签 自动依赖解析(import/export)
代码组织 逻辑分组 物理文件分组
代码分割 不支持 原生支持(动态导入)
摇树优化 不支持 原生支持(Tree Shaking)
运行时性能 所有代码立即加载 按需加载
类型声明 通过/// <reference>指令 自动解析.d.ts文件
全局污染 可能污染全局命名空间 无全局污染

使用场景指南

命名空间适用场景:

  1. 浏览器环境下的旧项目维护
  2. 需要全局可访问的库类型声明(如window.myLib)
  3. 将大型库拆分为多个文件但仍需共享作用域
    // 多文件命名空间
    // Validation.ts
    namespace Validation {
      export interface Validator {
        isValid(s: string): boolean;
      }
    }
    
    // LettersOnlyValidator.ts
    /// <reference path="Validation.ts" />
    namespace Validation {
      export class LettersOnlyValidator implements Validator {
        isValid(s: string) { /*...*/ }
      }
    }

现代模块适用场景:

  1. 新项目开发
  2. 需要代码分割的应用
  3. 组件化架构(React/Vue)
  4. Node.js 服务端应用
  5. 需要 Tree Shaking 优化的库
    // 现代模块化组件
    // Button.tsx
    import React from 'react';
    
    export default function Button({ text }: { text: string }) {
      return <button>{text}</button>;
    }
    
    // App.tsx
    import Button from './Button';
    
    function App() {
      return <Button text="Click" />;
    }

混合使用模式

场景:模块扩展命名空间

// shapes.ts - 命名空间定义
namespace Shapes {
  export class Circle { /*...*/ }
}

// extensions.ts - 模块扩展命名空间
import './shapes'; // 确保命名空间存在

declare global {
  namespace Shapes {
    export class Triangle { /*...*/ } // 扩展命名空间
  }
}

// 使用
const triangle = new Shapes.Triangle();

场景:命名空间封装模块类型

// 为第三方模块声明全局类型
import * as React from 'react';

declare global {
  namespace JSX {
    interface IntrinsicElements {
      customElement: React.DetailedHTMLProps<
        React.HTMLAttributes<HTMLElement>,
        HTMLElement
      >;
    }
  }
}

工程迁移策略

步骤1:命名空间转模块

// 原命名空间代码
namespace Utilities {
  export function log() { /*...*/ }
}

// 转换为模块
// utilities.ts
export function log() { /*...*/ }

步骤2:处理全局依赖

// 原全局依赖
namespace App {
  export const config = { debug: true };
}

// 转换为单例模块
// config.ts
export const config = { debug: true };

// 使用处改为导入
import { config } from './config';

步骤3:处理多文件命名空间

// 原多文件命名空间
/// <reference path="validation.ts" />
namespace Validation {
  export class EmailValidator { /*...*/ }
}

// 转换为模块
// EmailValidator.ts
import { Validator } from './validation'; // 转换为模块

export class EmailValidator implements Validator { /*...*/ }

最佳实践指南

  1. 新项目强制使用模块:
    // tsconfig.json
    {
      "compilerOptions": {
        "module": "ESNext",
        "moduleResolution": "NodeNext"
      }
    }
  2. 旧项目渐进迁移:
    • 新文件使用模块语法
    • 旧文件保持命名空间
    • 使用export as namespace提供全局访问
      // legacy.d.ts
      export = MyLib;
      export as namespace myLib;
  3. 避免全局扩展污染:
    // 不推荐:污染全局命名空间
    declare global {
      interface Window {
        myGlobalVar: any;
      }
    }
    
    // 推荐:模块封装
    // my-module.ts
    export function init() {
      window.myGlobalVar = { /*...*/ };
    }
  4. 类型声明策略:
    // 现代模块类型声明
    declare module 'modern-lib' {
      export function calculate(): number;
    }
    
    // 命名空间类型声明
    declare namespace LegacyLib {
      export const version: string;
    }

常见问题解决方案

问题:全局类型冲突

// file1.ts
namespace MyApp {
  export const version = "1.0";
}

// file2.ts
namespace MyApp { // ❌ 可能与其他文件合并
  export function log() {}
}

解决方案:

// 转换为模块化
// version.ts
export const version = "1.0";

// logger.ts
export function log() {}

问题:第三方库全局扩展

// 扩展jQuery
declare namespace JQuery {
  interface Static {
    newPlugin(): void;
  }
}

// 现代方案:模块增强
declare module 'jquery' {
  interface JQueryStatic {
    newPlugin(): void;
  }
}

问题:循环依赖

// 命名空间中的循环引用
namespace A {
  export const b = B.value; // ❌ B尚未定义
}

namespace B {
  export const value = 10;
}

模块化解决方案:

// a.ts
import { value } from './b';
export const b = value;

// b.ts
import { b } from './a';
export const value = b + 10; // ❌ 循环依赖

// 重构:提取公共逻辑
// constants.ts
export const BASE_VALUE = 5;

// a.ts
import { BASE_VALUE } from './constants';
export const a = BASE_VALUE * 2;

// b.ts
import { BASE_VALUE } from './constants';
export const b = BASE_VALUE * 3;

1.4.3 模块解析策略与路径映射

模块解析策略

TypeScript 支持两种模块解析策略:

策略 触发条件 解析逻辑 适用场景
Classic “moduleResolution”: “Classic” 1. 相对路径:./module → ./module.ts
2. 非相对路径:module → ./module.ts → ../module.ts
TS 1.6 前旧项目
Node “moduleResolution”: “Node”
(默认值)
遵循 Node.js 解析规则:
1. 检查文件
2. 检查目录
3. 检查 package.json
4. 检查 @types 现代
Node.js/Web 项目

Node 策略解析流程:

import { helper } from 'my-utils';
// 解析步骤:
// 1. 检查文件: node_modules/my-utils.ts
// 2. 检查文件: node_modules/my-utils.tsx
// 3. 检查文件: node_modules/my-utils.d.ts
// 4. 检查目录: node_modules/my-utils/package.json (main/types 字段)
// 5. 检查目录: node_modules/my-utils/index.ts
// 6. 检查上级 node_modules: ../node_modules/my-utils

路径映射(Path Mapping)

核心目的:简化模块导入路径,避免相对路径嵌套

配置方式 (tsconfig.json):

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["components/*"],
      "@utils": ["utils/index"],
      "shared/*": ["../shared/*"]
    }
  }
}

映射规则解析:

映射配置 导入语句 解析路径
"@components/*"
["components/*"]
import Button from '@components/Button' ./src/components/Button.ts
"@utils"
["utils/index"]
import { log } from '@utils' ./src/utils/index.ts
"shared/*"
["../shared/*"]
import config from 'shared/config' ../shared/config.ts

路径映射类型声明

场景:为路径映射提供类型支持

// src/global.d.ts
declare module '@components/*' {
  import { ComponentType } from 'react';
  const component: ComponentType;
  export default component;
}

declare module '@utils' {
  export function log(message: string): void;
  export const version: string;
}

工程最佳实践

  1. 基础路径配置:
    {
      "compilerOptions": {
        "baseUrl": "src", // 所有非绝对路径相对src解析
      }
    }
    
    // 使用
    import utils from 'utils'; // 解析为 src/utils.ts
  2. 多路径映射模式:
    {
      "paths": {
        // 多路径回退
        "@services/*": [
          "services/v2/*", // 优先尝试v2
          "services/v1/*"  // 回退到v1
        ]
      }
    }
  3. 通配符扩展:
    {
      "paths": {
        "@assets/*": ["assets/*", "assets/images/*"] // 多目录映射
      }
    }
  4. 绝对路径映射:
    {
      "baseUrl": ".",
      "paths": {
        "src/*": ["src/*"] // 允许 import from 'src/components'
      }
    }

路径映射与打包器集成

Webpack 配置:

// webpack.config.js
const path = require('path');

module.exports = {
  resolve: {
    alias: {
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/utils')
    }
  }
};

Vite 配置:

// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    }
  }
});

常见问题解决方案

问题1:路径映射在运行时失效

原因:TypeScript 只处理编译时路径,运行时需打包器支持

解决方案:

# 安装路径转换工具
npm install tsconfig-paths --save-dev

# 运行时加载
node -r tsconfig-paths/register app.ts

问题2:循环依赖警告

场景:

src/
  components/
    Button.ts  // import { theme } from '@utils'
  utils/
    index.ts   // import { Button } from '@components'

解决方案:

  1. 重构提取公共模块
  2. 使用依赖注入
  3. 动态导入打破循环
    // utils/index.ts
    let Button: any;
    
    async function loadButton() {
      if (!Button) {
        Button = (await import('@components/Button')).default;
      }
      return Button;
    }

问题3:声明文件缺失

解决方案:

// 为路径映射创建声明文件
// src/path-mappings.d.ts
declare module '@components/*' {
  const component: React.ComponentType;
  export default component;
}

declare module '@utils' {
  export function formatDate(date: Date): string;
}

高级路径映射技术

  1. 条件路径映射:
    {
      "paths": {
        "config": [
          "config/production", // 生产环境
          "config/development" // 开发环境
        ]
      }
    }
  2. 多项目共享映射:
    // 基座项目 tsconfig.base.json
    {
      "compilerOptions": {
        "paths": {
          "@shared/*": ["../shared/*"]
        }
      }
    }
    
    // 子项目 tsconfig.json
    {
      "extends": "../tsconfig.base.json",
      "compilerOptions": {
        "baseUrl": "src"
      }
    }
  3. 路径映射重定向:
    {
      "paths": {
        "old-module": ["new-module"] // 重定向旧路径
      }
    }

路径映射规范指南

  1. 统一前缀约定:

    @app/      // 应用核心代码
    @lib/      // 公共库
    @assets/   // 静态资源
    @test/     // 测试工具
  2. 禁止深层相对路径:

    // 禁用
    import util from '../../../../utils';
    
    // 启用
    import util from '@core/utils';
  3. 映射路径长度限制:

    // 优先使用短路径
    {
      "paths": {
        "@c": ["components"] // 不推荐:过于简短
      }
    }
  4. 文档化映射规则:

    • 路径映射规范:
      映射路径 实际路径 说明
      @components/* src/components/* 全局组件
      @utils src/utils/index.ts 工具函数入口

1.4.4 动态导入与代码分割类型安全

动态导入基础语法

核心语法:import() 函数返回 Promise,在模块加载完成后解析为模块对象

// 基本用法
const modulePromise = import('./module');

// 带路径变量
const moduleName = 'utils';
const utilsModule = import(`./${moduleName}`);

类型安全处理:

interface MathModule {
  add: (a: number, b: number) => number;
  PI: number;
}

const mathModule = import('./math') as Promise<{ default: MathModule }>;

mathModule.then(mod => {
  console.log(mod.default.add(2, 3)); // 类型安全访问
});

代码分割类型声明

  1. 模块声明文件:
    // math.module.d.ts
    declare const mathModule: {
      add: (a: number, b: number) => number;
      PI: number;
    };
    export default mathModule;
  2. 动态模块类型:
    // 使用泛型定义返回类型
    function dynamicImport<T>(path: string): Promise<T> {
      return import(path) as Promise<T>;
    }
    
    // 使用
    dynamicImport<{ default: MathModule }>('./math')
      .then(({ default: math }) => math.add(1, 2));

React 动态组件类型安全

  1. React.lazy 基础用法:
    const LazyComponent = React.lazy(() => import('./HeavyComponent'));
    
    function App() {
      return (
        <React.Suspense fallback={<div>Loading...</div>}>
          <LazyComponent />
        </React.Suspense>
      );
    }
  2. 带属性的组件类型:
    // HeavyComponent.tsx
    interface Props {
      title: string;
      count: number;
    }
    const HeavyComponent: React.FC<Props> = ({ title, count }) => (
      <div>{title}: {count}</div>
    );
    
    // 动态导入
    const LazyComponent = React.lazy(() => 
      import('./HeavyComponent').then(mod => ({
        default: mod.HeavyComponent
      }))
    );
    
    // 使用(保留属性类型检查)
    <LazyComponent title="Items" count={5} />

Vue 异步组件类型

  1. defineAsyncComponent:
    import { defineAsyncComponent } from 'vue';
    
    const AsyncComponent = defineAsyncComponent(() => 
      import('./AsyncComponent.vue')
    );
    
    // 使用
    <template>
      <AsyncComponent :count="5" />
    </template>
  2. 类型推断组件属性:
    // 声明组件类型
    interface AsyncComponentProps {
      count: number;
    }
    
    const AsyncComponent = defineAsyncComponent({
      loader: () => import('./AsyncComponent.vue'),
      loadingComponent: LoadingSpinner,
      delay: 200,
    }) as DefineComponent<AsyncComponentProps>;

代码分割模式

  1. 路由级分割:
    // React Router v6
    const router = createBrowserRouter([
      {
        path: '/dashboard',
        element: (
          <React.Suspense fallback={<Spinner />}>
            {import('./Dashboard')}
          </React.Suspense>
        )
      },
      {
        path: '/reports',
        lazy: () => import('./Reports') // 自动处理Suspense
      }
    ]);
  2. 功能级分割:
    // 按需加载工具库
    function processData(data: any[]) {
      if (data.length > 1000) {
        import('./heavy-utils').then(({ sortBigData }) => {
          sortBigData(data);
        });
      }
    }
  3. 条件分割:
    // 根据设备加载不同模块
    const loadEditor = () => {
      if (isMobileDevice()) {
        return import('./mobile-editor');
      }
      return import('./desktop-editor');
    };

类型安全策略

  1. 动态导入泛型封装:
    // safeImport.ts
    export function safeImport<T>(modulePath: string): Promise<T> {
      return import(/* webpackChunkName: "[request]" */ modulePath) as Promise<T>;
    }
    
    // 使用
    interface ChartModule {
      renderChart: (data: any) => void;
    }
    
    safeImport<ChartModule>('./charting-lib')
      .then(chart => chart.renderChart(data));
  2. 模块类型验证:
    // 运行时类型验证
    import { is } from 'typescript-is';
    
    const loadModule = async () => {
      const mod = await import('./plugin');
      
      if (!is<PluginInterface>(mod)) {
        throw new Error('Invalid plugin interface');
      }
      
      return mod;
    };
  3. 类型断言辅助:
    // 创建类型断言函数
    function assertModule<T>(module: any, key: keyof T): asserts module is T {
      if (!(key in module)) {
        throw new Error(`Module missing required export: ${String(key)}`);
      }
    }
    
    // 使用
    const mod = await import('./validator');
    assertModule<{ validate: Function }>(mod, 'validate');
    mod.validate(input); // 安全调用

Webpack 魔法注释

  1. 基础注释:
    // 命名代码块
    import(/* webpackChunkName: "math" */ './math');
    
    // 预获取
    import(/* webpackPrefetch: true */ './prefetch-module');
    
    // 预加载
    import(/* webpackPreload: true */ './critical-module');
  2. 组合使用:
    // 生产环境启用预取
    const isProd = process.env.NODE_ENV === 'production';
    const comments = isProd ? 'webpackPrefetch: true' : '';
    
    const mod = import(/* webpackChunkName: "utils", ${comments} */ './utils');

最佳实践指南

  1. 代码分割策略:
    分割粒度 适合场景 示例
    路由级 SPA应用 lazy(() => import('./page'))
    组件级 大型组件/三方组件 const Heavy = lazy(() => import(...))
    库级 第三方大体积库 import('lodash/debounce')
    功能级 非首屏关键功能 点击后加载编辑器模块
  2. 加载状态管理:
    // 自定义加载状态
    function useLazyModule<T>(loader: () => Promise<T>) {
      const [module, setModule] = useState<T | null>(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState<Error | null>(null);
    
      useEffect(() => {
        loader()
          .then(setModule)
          .catch(setError)
          .finally(() => setLoading(false));
      }, []);
    
      return { module, loading, error };
    }
    
    // 使用
    const { module: math, loading } = useLazyModule(() => 
      import('./math').then(m => m.default)
    );
  3. 错误边界处理:
    // React 错误边界
    class LazyErrorBoundary extends React.Component {
      state = { hasError: false };
      
      static getDerivedStateFromError() {
        return { hasError: true };
      }
      
      render() {
        if (this.state.hasError) {
          return <FallbackUI />;
        }
        return this.props.children;
      }
    }
    
    // 使用
    <LazyErrorBoundary>
      <React.Suspense fallback={<Spinner />}>
        <LazyComponent />
      </React.Suspense>
    </LazyErrorBoundary>

性能优化技巧

  1. 预加载关键模块:
    // 首屏渲染后预加载
    useEffect(() => {
      import('./next-page').catch(console.error);
    }, []);
  2. 批量加载策略:
    // 同时加载多个模块
    const loadDashboardModules = () => Promise.all([
      import('./chart'),
      import('./stats'),
      import('./recent-activity')
    ]);
    
    // 使用
    loadDashboardModules().then(([chart, stats, activity]) => {
      chart.render();
      stats.update();
    });
  3. 请求合并:
    // 使用Webpack魔法注释合并请求
    const getEditor = () => import(/* webpackMode: "lazy-once" */ './editor');
    
    // 不同地方调用只加载一次
    button1.onclick = () => getEditor().then(...);
    button2.onclick = () => getEditor().then(...);

类型安全陷阱与解决方案

陷阱1:模块类型漂移

现象:模块更新后类型不匹配

解决方案:

// 版本化类型声明
declare module './chart' {
  export interface ChartV2 {
    newMethod(): void;
  }
  const chart: ChartV2;
  export default chart;
}

陷阱2:动态路径类型丢失

解决方案:

// 路径映射类型
type ModulePaths = 
  | './math' 
  | './chart' 
  | './utils';

function loadModule<T>(path: ModulePaths): Promise<T> {
  return import(path) as Promise<T>;
}

陷阱3:CJS/ESM 互操作问题

解决方案:

// 统一封装
async function loadCompatModule(path: string) {
  const mod = await import(path);
  return mod.default || mod;
}

二、高级类型系统

2.1 工具类型原理

2.1.1 内置工具类型源码解析

TypeScript 内置工具类型通过条件类型和映射类型实现

基础工具类型

  1. Partial<T>:将类型 T 的所有属性变为可选
    type Partial<T> = {
      [P in keyof T]?: T[P];
    };
    • 实现原理:映射类型 + ? 修饰符
    • 示例:
      interface User {
        name: string;
        age: number;
      }
      type PartialUser = Partial<User>; // { name?: string; age?: number }
  2. Required<T>:将类型 T 的所有属性变为必填
    type Required<T> = {
      [P in keyof T]-?: T[P];
    };
    • 实现原理:映射类型 + -? 移除可选修饰符
    • 示例:
      type RequiredUser = Required<PartialUser>; // { name: string; age: number }
  3. Readonly<T>:将类型 T 的所有属性变为只读
    type Readonly<T> = {
      readonly [P in keyof T]: T[P];
    };
    • 实现原理:映射类型 + readonly 修饰符
    • 示例:
      type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number }

结构操作工具类型

  1. Pick<T, K extends keyof T>: 从类型 T 中选取指定属性 K
    type Pick<T, K extends keyof T> = {
      [P in K]: T[P];
    };
    • 实现原理:映射类型 + 键名约束
    • 示例:
      type NameOnly = Pick<User, 'name'>; // { name: string }
  2. Omit<T, K extends keyof any>: 从类型 T 中排除指定属性 K
    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
    • 实现原理:组合 Pick 和 Exclude
    • 源码依赖:
      type Exclude<T, U> = T extends U ? never : T;
    • 示例:
      type WithoutAge = Omit<User, 'age'>; // { name: string }
  3. Record<K extends keyof any, T>: 创建键为 K、值为 T 的类型
    type Record<K extends keyof any, T> = {
      [P in K]: T;
    };
    • 实现原理:动态键映射
    • 示例:
      type UserMap = Record<string, User>; // { [key: string]: User }

类型操作工具类型

  1. Exclude<T, U>: 从 T 中排除可分配给 U 的类型
    type Exclude<T, U> = T extends U ? never : T;
    • 实现原理:条件类型 + never
    • 示例:
      type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
  2. Extract<T, U>: 从 T 中提取可分配给 U 的类型
    type Extract<T, U> = T extends U ? T : never;
    • 实现原理:条件类型反向操作
    • 示例:
      type T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
  3. NonNullable<T>: 从 T 中排除 null 和 undefined
    type NonNullable<T> = T extends null | undefined ? never : T;
    • 实现原理:条件类型过滤
    • 示例:
      type T2 = NonNullable<string | null | undefined>; // string

函数操作工具类型

  1. Parameters<T extends (...args: any) => any>: 获取函数参数类型元组
    type Parameters<T extends (...args: any) => any> = 
      T extends (...args: infer P) => any ? P : never;
    • 实现原理:infer 推导参数类型
    • 示例:
      type FnParams = Parameters<(a: string, b: number) => void>; // [string, number]
  2. ReturnType<T extends (...args: any) => any>: 获取函数返回值类型
    type ReturnType<T extends (...args: any) => any> = 
      T extends (...args: any) => infer R ? R : any;
    • 实现原理:infer 推导返回类型
    • 示例:
      type FnReturn = ReturnType<() => boolean>; // boolean
  3. ConstructorParameters<T extends new (...args: any) => any>: 获取构造函数参数类型元组
    type ConstructorParameters<T extends new (...args: any) => any> = 
      T extends new (...args: infer P) => any ? P : never;
    • 实现原理:infer 推导构造函数参数
    • 示例:
      class Person {
        constructor(name: string, age: number) {}
      }
      type Params = ConstructorParameters<typeof Person>; // [string, number]

高级工具类型

  1. ThisParameterType<T>: 提取函数的 this 参数类型
    type ThisParameterType<T> = 
      T extends (this: infer U, ...args: any[]) => any ? U : unknown;
    • 实现原理:infer 捕获 this 类型
    • 示例:
      function fn(this: Window) {}
      type ThisType = ThisParameterType<typeof fn>; // Window
  2. OmitThisParameter<T>: 移除函数的 this 参数类型
    type OmitThisParameter<T> = 
      unknown extends ThisParameterType<T> ? T : 
      T extends (...args: infer A) => infer R ? (...args: A) => R : T;
    • 实现原理:组合 ThisParameterType + 参数推导
    • 示例:
      type WithoutThis = OmitThisParameter<typeof fn>; // () => void
  3. Awaited<T>: 递归解包 Promise 类型
    type Awaited<T> =
        T extends null | undefined ? T : // 特殊处理 null/undefined
        T extends object & { then(onfulfilled: infer F): any } ? // 检查 thenable 对象
            F extends ((value: infer V, ...args: any) => any) ? 
                Awaited<V> : // 递归解包
            never : 
        T; // 非 Promise 类型直接返回
    • 实现原理:条件类型递归 + thenable 检测
    • 示例:
      type T3 = Awaited<Promise<Promise<string>>>; // string

源码设计模式分析

  1. 分布式条件类型
    • 当泛型参数为联合类型时,条件类型会分布式应用:
    // Exclude 中的分布式特性
    type Exclude<T, U> = T extends U ? never : T;
    type Result = Exclude<'a' | 'b' | 'c', 'a' | 'b'>; // 'c'
    
    // 等价于:
    ('a' extends 'a' | 'b' ? never : 'a') |
    ('b' extends 'a' | 'b' ? never : 'b') |
    ('c' extends 'a' | 'b' ? never : 'c')
  2. 类型递归模式
    • 处理嵌套结构的关键技术:
    // 内置工具类型中的递归
    type DeepReadonly<T> = {
      readonly [P in keyof T]: T[P] extends object 
        ? DeepReadonly<T[P]> 
        : T[P];
    }
  3. 类型推断优先级
    • infer 关键字的工作机制:
    // 协变位置(返回值)推断
    type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
    
    // 逆变位置(参数)推断
    type GetFirstArg<T> = T extends (arg: infer A, ...args: any[]) => any ? A : never;

工程实践要点

  1. 避免过度嵌套:
    // 危险:深层嵌套可能导致类型实例化过深错误
    type DeepNested = Awaited<Promise<Promise<Promise<string>>>>;
    
    // 解决方案:设置递归深度限制
    type SafeAwaited<T, Depth extends number = 5> = 
      Depth extends 0 ? T :
      T extends Promise<infer U> ? SafeAwaited<U, [-1, 0, 1, 2, 3, 4][Depth]> : T;
  2. 性能敏感场景优化:
    // 使用内置工具类型替代自定义复杂类型
    // 不推荐:
    type MyPartial<T> = { [P in keyof T]?: T[P] };
    
    // 推荐:直接使用 Partial
  3. 类型安全检查:
    // 确保工具类型安全
    type SafeExtract<T, U> = [T] extends [U] ? T : never;
    
    // 防止分布式行为
    type DistributiveCheck<T> = [T] extends [never] ? true : false;
  4. 源码调试技巧:
    // 使用类型打印调试
    type _Debug<T> = T extends infer U ? U : never;
    type Reveal<T> = { [P in keyof T]: T[P] } & {};
    
    // 查看实际类型
    type DebugView = Reveal<SomeComplexType>;

内置工具类型关系图

基础操作
├─ Partial
├─ Required
├─ Readonly
│
结构操作
├─ Pick
├─ Omit → 依赖 Exclude
├─ Record
│
集合操作
├─ Exclude
├─ Extract
├─ NonNullable
│
函数操作
├─ Parameters
├─ ReturnType
├─ ConstructorParameters
├─ ThisParameterType
└─ OmitThisParameter

2.1.2 类型映射修饰符(+/-)原理

修饰符基础概念

类型映射修饰符用于控制属性修饰符(? 和 readonly)的添加或移除:

修饰符 作用 示例
+ 添加修饰符(默认行为) { +readonly [P in K]: T }
- 移除修饰符 { -? [P in K]: T }

核心语法解析

  1. 添加修饰符(显式+):
    // 显式添加只读属性
    type AddReadonly<T> = {
      +readonly [P in keyof T]: T[P];
    };
    
    // 显式添加可选属性
    type AddOptional<T> = {
      [P in keyof T]+?: T[P];
    };
  2. 移除修饰符(-):
    // 移除只读属性
    type RemoveReadonly<T> = {
      -readonly [P in keyof T]: T[P];
    };
    
    // 移除可选属性
    type RemoveOptional<T> = {
      [P in keyof T]-?: T[P];
    };

实现原理分析

  1. 修饰符的编译转换:
    // 源码:
    type Mutable<T> = {
      -readonly [P in keyof T]: T[P];
    };
    
    // 编译后等价于:
    type Mutable<T> = {
      [P in keyof T]: T[P];
    };
  2. 类型系统内部处理:
    • + 操作:添加属性描述符标志
    • - 操作:清除属性描述符标志
    • 映射过程本质是创建新属性描述符

内置工具类型应用

  1. Required<T> 实现:
    type Required<T> = {
      [P in keyof T]-?: T[P];
    };
  2. Readonly<T> 实现:
    type Readonly<T> = {
      +readonly [P in keyof T]: T[P];
    };
  3. Mutable<T>(非内置但常用):
    type Mutable<T> = {
      -readonly [P in keyof T]: T[P];
    };

组合修饰符技巧

  1. 同时添加/移除多个修饰符:
    // 创建可变且必填的类型
    type MutableRequired<T> = {
      -readonly [P in keyof T]-?: T[P];
    };
    
    // 等价于:
    type MutableRequired<T> = Mutable<Required<T>>;
  2. 条件修饰符:
    // 仅对函数属性添加只读
    type ReadonlyMethods<T> = {
      readonly [P in keyof T]: T[P] extends Function ? T[P] : never;
    } & {
      [P in keyof T as T[P] extends Function ? never : P]: T[P];
    };

工程实践指南

  1. 不可变状态管理:
    // 创建深度只读状态
    type ImmutableState<T> = {
      +readonly [P in keyof T]: ImmutableState<T[P]>;
    };
    
    // 使用
    interface AppState {
      user: { name: string };
      settings: { darkMode: boolean };
    }
    const state: ImmutableState<AppState> = {
      user: { name: "Alice" },
      settings: { darkMode: true }
    };
    state.user.name = "Bob"; // 🚫 编译错误
  2. API 响应处理:
    // 移除API响应中的可选属性
    type ConcreteResponse<T> = {
      [P in keyof T]-?: T[P];
    };
    
    // 使用
    interface ApiResponse {
      data?: unknown;
      error?: string;
    }
    function handleResponse(res: ConcreteResponse<ApiResponse>) {
      // res.data 和 res.error 一定存在
    }
  3. 安全补丁策略:
    // 创建部分更新类型
    type SafePatch<T> = Partial<Mutable<T>> & {
      readonly id: string; // 保持ID只读
    };
    
    // 使用
    interface Product {
      readonly id: string;
      name: string;
      price: number;
    }
    const update: SafePatch<Product> = {
      id: "p1", // ✅ 允许(但实际不能修改)
      name: "New Name" // ✅
    };

性能优化技巧

  1. 避免深度递归:
    // 限制递归深度
    type DeepReadonly<T, Depth extends number = 3> = 
      Depth extends 0 ? T : 
      T extends object ? {
        readonly [P in keyof T]: DeepReadonly<T[P], [-1, 0, 1, 2][Depth]>;
      } : T;
  2. 条件短路优化:
    type OptimizedMutable<T> = 
      T extends readonly any[] ? Array<OptimizedMutable<T[number]>> :
      T extends object ? { -readonly [P in keyof T]: OptimizedMutable<T[P]> } :
      T;

编译原理透视

  1. 修饰符的 AST 表示:
    // TypeScript AST 节点示例
    interface MappingModifier {
      +?: boolean; // 添加修饰符
      -?: boolean; // 移除修饰符
      readonly?: "+" | "-" | null;
      optional?: "+" | "-" | null;
    }
  2. 类型实例化流程:
    1. 解析映射语法
    2. 获取源类型属性描述符
    3. 应用修饰符操作(+/-)
    4. 创建新属性描述符
    5. 组合为新对象类型

常见问题解决方案

问题:修饰符对方法无效

class Test {
  readonly method() {} // 方法本质是只读的
}
type T = Mutable<Test>; // 仍然只读

解决方案:

type MutableMethods<T> = {
  -readonly [P in keyof T]: T[P];
} & {
  [P in keyof T]: T[P] extends Function ? (...args: any[]) => any : T[P];
};

问题:修饰符与交叉类型冲突

type A = { readonly id: string };
type B = { id: string };
type C = A & B; // id 变为 string(只读被覆盖)

解决方案:

type EnforceReadonly<T> = {
  readonly [P in keyof T]: T[P];
};
type SafeMerge<A, B> = EnforceReadonly<A> & EnforceReadonly<B>;

问题:数组和元组处理

// 元组可变化
type MutableTuple<T extends readonly any[]> = {
  -readonly [K in keyof T]: T[K];
};

// 使用
const arr: MutableTuple<readonly [string, number]> = ["a", 1];
arr[0] = "b"; // ✅

修饰符与内置工具类型关系

映射修饰符体系
├─ 添加操作 (+)
│  ├─ +readonly → Readonly<T>
│  └─ +? → Partial<T>
│
└─ 移除操作 (-)
   ├─ -readonly → Mutable<T>
   └─ -? → Required<T>

2.1.3 条件类型分布式特性

分布式条件类型核心概念

触发条件:当条件类型作用于裸类型参数(naked type parameter)的联合类型时,会触发分布式行为

基本模式:

T extends U ? X : Y
  • T 是裸类型参数(未包裹在数组、元组或函数中)
  • T 是联合类型 A | B | C
  • 结果等价于 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

分布式行为解析

  1. 基础示例:
    type StringOrNumber<T> = T extends string ? "string" : "number";
    
    // 分布式计算
    type Result = StringOrNumber<"a" | 1>; 
    // 等价于:
    // ("a" extends string ? "string" : "number") |
    // (1 extends string ? "string" : "number")
    // → "string" | "number"
  2. 非分布式对比:
    // 包裹类型参数(非裸类型)
    type Wrapped<T> = [T] extends [string] ? "string" : "number";
    
    // 非分布式计算
    type Result2 = Wrapped<"a" | 1>;
    // 等价于:
    // ["a" | 1] extends [string] ? "string" : "number"
    // → "number"(因为 ["a" | 1] 不继承 [string])

分布式规则详解

  1. 空联合类型处理:
    type Test<T> = T extends string ? true : false;
    type Result = Test<never>; // never(空联合类型返回never)
  2. 联合类型包含never:
    type Test<T> = T extends string ? true : false;
    type Result = Test<"a" | never>; // true(never被忽略)
  3. 联合类型优先级:
    type Test<T> = T extends any ? T : never;
    type Result = Test<string | number>; // string | number

内置工具类型应用

  1. Exclude<T, U>
    type Exclude<T, U> = T extends U ? never : T;
    
    // 分布式计算
    type Result = Exclude<"a" | "b" | "c", "a" | "b">;
    // → ("a" extends "a"|"b" ? never : "a") |
    //   ("b" extends "a"|"b" ? never : "b") |
    //   ("c" extends "a"|"b" ? never : "c")
    // → never | never | "c" → "c"
  2. Extract<T, U>
    type Extract<T, U> = T extends U ? T : never;
    
    // 分布式计算
    type Result = Extract<"a" | "b" | 1, string>;
    // → ("a" extends string ? "a" : never) |
    //   ("b" extends string ? "b" : never) |
    //   (1 extends string ? 1 : never)
    // → "a" | "b" | never → "a" | "b"
  3. NonNullable<T>
    type NonNullable<T> = T extends null | undefined ? never : T;
    
    // 分布式计算
    type Result = NonNullable<string | null | undefined>;
    // → (string extends null|undefined ? never : string) |
    //   (null extends null|undefined ? never : null) |
    //   (undefined extends null|undefined ? never : undefined)
    // → string | never | never → string

避免分布式行为

  1. 包裹类型参数:
    // 方法1:元组包裹
    type NoDistribute<T> = [T] extends [string] ? true : false;
    
    // 方法2:数组包裹
    type NoDistribute2<T> = T[] extends string[] ? true : false;
    
    // 方法3:函数包裹
    type NoDistribute3<T> = (() => T) extends () => string ? true : false;
  2. 特殊类型约束:
    // 使用never约束
    type IsNever<T> = [T] extends [never] ? true : false;
    
    // 使用any约束
    type IsAny<T> = 0 extends (1 & T) ? true : false;

高级应用场景

  1. 联合类型过滤:
    // 过滤出函数类型
    type FilterFunctions<T> = T extends (...args: any[]) => any ? T : never;
    
    type Functions = FilterFunctions<string | (() => void) | number>; // () => void
  2. 类型差异检测:
    // 检测类型差异
    type Diff<T, U> = T extends U ? never : T;
    
    // 使用
    type MissingProps = Diff<"name" | "age", keyof User>; // User中缺失的属性
  3. 递归类型操作:
    // 联合类型转元组(简化版)
    type UnionToTuple<T> = 
      T extends any ? (arg: T) => void : never extends (arg: infer U) => void ? U : never;

分布式与映射类型结合

  1. 分布式键名重映射:
    type AddPrefix<T> = {
      [K in keyof T as K extends string ? `data_${K}` : never]: T[K]
    };
    
    // 使用
    interface Props { id: string; value: number; }
    type PrefixedProps = AddPrefix<Props>;
    // { data_id: string; data_value: number }
  2. 条件键名过滤:
    // 仅保留字符串键
    type StringKeys<T> = {
      [K in keyof T]: K extends string ? K : never
    }[keyof T];
    
    // 使用
    type Keys = StringKeys<{ 1: number; "name": string }>; // "name"

编译原理透视

  1. 分布式处理流程:
    1. 解析条件类型:T extends U ? X : Y
    2. 检查T是否为裸类型参数的联合类型
    3. 若是,对联合类型每个成员进行独立计算
    4. 将计算结果组合为新联合类型
  2. 类型实例化优化:
    • TS编译器对分布式条件类型进行缓存优化
    • 相同类型参数的计算结果会被复用
    • 避免重复计算提升性能

工程实践指南

  1. 明确设计意图:
    // 需要分发时
    type Distributed<T> = T extends any ? T[] : never;
    
    // 不需要分发时
    type NonDistributed<T> = [T] extends [any] ? T[] : never;
  2. 文档注释规范:
    /**
     * 提取数组元素类型(分发模式)
    * @template T - 联合类型数组
    * @example 
    * type Element = ElementType<Array<string> | Array<number>>; // string | number
    */
    type ElementType<T> = T extends (infer U)[] ? U : never;
  3. 性能监控策略:
    // 复杂类型性能测试
    type StressTest<T> = T extends any 
      ? `prefix_${T & string}_suffix` 
      : never;
    
    // 测量实例化时间
    console.time("type instantiation");
    type Result = StressTest<"a" | "b" | ...>; // 100+ 项
    console.timeEnd("type instantiation");
  4. 错误处理模式:
    // 安全分发类型
    type SafeDistribute<T, Fallback = never> = 
      [T] extends [never] ? Fallback :
      T extends any ? /* 分发逻辑 */ : never;
    
    // 使用
    type Result = SafeDistribute<never, "empty">; // "empty"

分布式特性与内置工具

分布式条件类型应用体系
├─ 集合操作
│  ├─ Exclude(差集)
│  ├─ Extract(交集)
│  └─ NonNullable(过滤)
│
├─ 函数操作
│  ├─ Parameters
│  └─ ReturnType
│
└─ 高级工具
   ├─ UnionToIntersection
   └─ UnionToTuple

2.1.4 infer 关键字与类型提取

infer 基础概念

核心功能:在条件类型中声明泛型类型变量,用于推导和捕获类型信息

基本语法:

T extends infer U ? U : never
  • infer U 声明一个类型变量 U
  • U 的类型由 TypeScript 根据上下文推导

位置约束:

  • 只能在条件类型的 extends 子句中使用
  • 只能出现在协变位置(返回值、属性值等)

基础提取模式

  1. 提取函数返回类型:
    type ReturnType<T> = 
      T extends (...args: any[]) => infer R ? R : never;
    
    // 使用
    type FnReturn = ReturnType<() => number>; // number
  2. 提取函数参数类型:
    type FirstParam<T> = 
      T extends (first: infer P, ...rest: any[]) => any ? P : never;
    
    // 使用
    type Param = FirstParam<(a: string, b: number) => void>; // string
  3. 提取数组元素类型:
    type ArrayElement<T> = 
      T extends (infer U)[] ? U : never;
    
    // 使用
    type Element = ArrayElement<string[]>; // string

高级提取技术

  1. 递归类型解包:
    // 解包嵌套Promise
    type Awaited<T> = 
      T extends Promise<infer U> ? Awaited<U> : T;
    
    // 使用
    type Result = Awaited<Promise<Promise<string>>>; // string
  2. 多个 infer 变量:
    // 提取函数参数元组
    type Params<T> = 
      T extends (...args: infer P) => any ? P : never;
    
    // 使用
    type FnParams = Params<(a: string, b: number) => void>; // [string, number]
  3. 逆变位置提取:
    // 提取函数this类型
    type ThisType<T> = 
      T extends (this: infer U, ...args: any[]) => any ? U : unknown;
    
    // 使用
    function fn(this: Window) {}
    type Context = ThisType<typeof fn>; // Window

模式匹配技术

  1. 字符串模板提取:
    // 提取模板字符串变量
    type ExtractVariable<T> = 
      T extends `user_${infer Id}` ? Id : never;
    
    // 使用
    type UserId = ExtractVariable<"user_123">; // "123"
  2. 对象键值提取:
    // 提取特定键的值类型
    type ValueOfKey<T, K extends string> = 
      T extends { [key in K]: infer V } ? V : never;
    
    // 使用
    type NameType = ValueOfKey<{ name: string; age: number }, "name">; // string
  3. 条件分支提取:
    // 提取满足条件的子类型
    type ExtractByType<T, U> = 
      T extends U ? T : never;
    
    // 使用
    type Strings = ExtractByType<"a" | 1 | true, string>; // "a"

内置工具类型解析

  1. Parameters<T> 实现:
    type Parameters<T extends (...args: any) => any> = 
      T extends (...args: infer P) => any ? P : never;
  2. ReturnType<T> 实现:
    type ReturnType<T extends (...args: any) => any> = 
      T extends (...args: any) => infer R ? R : any;
  3. ConstructorParameters<T> 实现:
    type ConstructorParameters<T extends new (...args: any) => any> = 
      T extends new (...args: infer P) => any ? P : never;

工程应用模式

  1. 组件 Props 提取:
    // 提取React组件Props类型
    type ComponentProps<T> = 
      T extends React.ComponentType<infer P> ? P : never;
    
    // 使用
    const MyComponent: React.FC<{ id: string }> = () => null;
    type Props = ComponentProps<typeof MyComponent>; // { id: string }
  2. API 响应处理:
    // 提取API响应中的data字段
    type ApiResponse<T> = {
      data: T;
      status: number;
    };
    
    type ResponseData<T> = 
      T extends ApiResponse<infer D> ? D : never;
    
    // 使用
    type UserData = ResponseData<ApiResponse<{ name: string }>>; // { name: string }
  3. 路由参数提取:
    // 提取动态路由参数
    type RouteParams<T> = 
      T extends `${string}/:${infer Param}/${string}` 
        ? Param | RouteParams<T> 
        : T extends `${string}/:${infer Param}` 
          ? Param 
          : never;
    
    // 使用
    type Params = RouteParams<"/user/:id/profile/:section">; // "id" | "section"

编译原理透视

  1. infer 实现机制:
    • 类型系统实现模式匹配
    • 在类型关系求解时绑定类型变量
    • 基于约束的类型变量实例化
  2. 类型推导过程:
    1. 匹配条件类型结构
    2. 提取候选类型
    3. 绑定 infer 变量
    4. 验证类型约束
    5. 实例化结果类型

最佳实践指南

  1. 类型变量命名规范:
    // 推荐:描述性命名
    type ExtractReturn<T> = 
      T extends (...args: any) => infer ReturnType ? ReturnType : never;
    
    // 避免:单字母命名
    type Bad<T> = T extends (...a: any) => infer R ? R : never;
  2. 错误处理模式:
    // 安全提取模式
    type SafeExtract<T, Fallback = never> = 
      T extends { [key: string]: infer V } ? V : Fallback;
    
    // 使用
    type Value = SafeExtract<string>; // never
  3. 复杂类型分解:
    // 分解复杂提取逻辑
    type Step1<T> = /* ... */;
    type Step2<T> = Step1<T> extends infer U ? /* ... */ : never;
    type Final<T> = Step2<T> extends infer V ? /* ... */ : never;
  4. 类型测试策略:
    // 使用类型断言验证
    type Assert<T, Expected> = 
      T extends Expected ? true : false;
    
    type Test1 = Assert<ReturnType<() => string>, string>; // true
    type Test2 = Assert<FirstParam<(a: number) => void>, string>; // false

infer 类型关系图

infer 应用体系
├─ 基础提取
│  ├─ 函数返回值 → ReturnType
│  ├─ 函数参数 → Parameters
│  └─ 数组元素 → ArrayElement
│
├─ 模式匹配
│  ├─ 字符串模板 → ExtractVariable
│  ├─ 对象结构 → ValueOfKey
│  └─ 条件分支 → ExtractByType
│
└─ 高级体操
   ├─ 元组转换 → MapTuple
   ├─ 柯里化 → Curry
   └─ 递归展开 → Expand

2.2 类型体操实战

2.2.1 函数柯里化类型定义

柯里化核心概念

  • 柯里化(Currying):将多参数函数转化为一系列单参数函数的过程
    // 原始函数
    const add = (a: number, b: number, c: number) => a + b + c;
    
    // 柯里化后
    const curriedAdd = (a: number) => (b: number) => (c: number) => a + b + c;
  • 类型挑战:
    • 动态推导剩余参数类型
    • 处理参数数量不确定的情况
    • 保持返回函数的链式调用类型安全

基础柯里化类型实现

场景1:固定参数长度的柯里化

type Curry1<T> = T extends (a: infer A) => infer R ? (a: A) => R : never;
type Curry2<T> = T extends (a: infer A, b: infer B) => infer R 
  ? (a: A) => (b: B) => R 
  : never;
type Curry3<T> = T extends (a: infer A, b: infer B, c: infer C) => infer R 
  ? (a: A) => (b: B) => (c: C) => R 
  : never;

// 使用示例
declare function curry<T>(fn: T): 
  T extends (...args: any) => any ? Curry3<T> : never;

const curriedAdd = curry(add); // 类型: (a: number) => (b: number) => (c: number) => number

场景2:动态参数长度的柯里化

type Curry<F> = F extends (...args: infer Args) => infer Return
  ? Args extends [infer First, ...infer Rest]
    ? (arg: First) => Curry<(...args: Rest) => Return>
    : Return // 终止条件:无剩余参数
  : never;

// 使用示例
const dynamicCurry = <F extends (...args: any) => any>(fn: F): Curry<F> => {
  // 实现略...
};

const curried = dynamicCurry((a: string, b: number, c: boolean) => {});
// 类型: (arg: string) => (arg: number) => (arg: boolean) => void

高级柯里化类型技巧

技巧1:支持部分应用(Partial Application)

type PartialCurry<F> = F extends (...args: infer Args) => infer R
  ? <P extends Partial<Args>>(...args: P) => 
      Args extends [...SameLength<P>, ...infer Rest] 
        ? Rest extends [] 
          ? R 
          : PartialCurry<(...args: Rest) => R>
        : never
  : never;

// 使用示例
const partialAdd = dynamicCurry(add);
const add10 = partialAdd(10); // 类型: (b: number) => (c: number) => number

技巧2:占位符支持(Placeholder)

const _ = Symbol('placeholder');
type Placeholder = typeof _;

type CurryWithPlaceholder<F, Original = F> = 
  F extends (...args: infer Args) => infer R
    ? <P extends CurryArgs<Args>>(...args: P) => 
        IsComplete<P, Args> extends true 
          ? R 
          : CurryWithPlaceholder<(...args: Remaining<P, Args>) => R, Original>
    : never;

// 完整实现需定义辅助类型:
// - CurryArgs:处理占位符的参数类型
// - Remaining:计算剩余参数
// - IsComplete:检查参数是否完整

工程实践与边界处理

边界情况处理方案:

场景 解决方案
空参数调用 返回原始函数
参数数量超过原函数 编译时报错
混合类型参数 使用泛型约束保持类型安全
可选参数处理 使用条件类型判断参数是否必须

性能优化:

// 避免深层递归(限制参数最大数量)
type MaxParams = 8; // 根据实际需求设定

type SafeCurry<F, Depth extends number = 0> = 
  Depth extends MaxParams 
    ? F // 达到深度限制时终止
    : F extends (...args: infer Args) => infer R
      ? Args extends [infer First, ...infer Rest]
        ? (arg: First) => SafeCurry<(...args: Rest) => R, Add<Depth, 1>>
        : R
      : never;

面试级考点

  • 类型递归深度限制:TypeScript 默认递归深度限制(约50层)
  • 条件类型分发机制:
    // 联合类型在条件类型中的分发行为
    type T = [string] | [number] extends [infer A] ? A : never;
    // 结果: string | number
  • 参数逆变问题:
    declare function curry<P extends any[], R>(fn: (...args: P) => R): 
      <A extends Partial<P>>(...args: A) => unknown; // 需要处理逆变
  • 实际应用场景:
    • Redux 的 connect 函数
    • React 的高阶组件
    • 配置化表单验证链

2.2.2 Promise 链式调用类型推导

核心类型挑战

问题 类型表现 解决方案
初始值类型丢失 Promise.resolve(42).then(v => v) → v: unknown 泛型捕获初始类型
链式返回值类型推导 .then(x => x.toFixed()) → 推导失败 条件类型 + 函数返回值推断
错误类型传递中断 .catch(e => e.message) → e: any 泛型约束错误类型
同步/异步混合链 Promise.resolve().then(() => 42) → 类型断裂 递归解包 Promise 嵌套

基础链式调用类型实现

场景1:基础 Promise 类型定义

interface MyPromise<T> {
  then<U>(
    onFulfilled?: (value: T) => U | PromiseLike<U>
  ): MyPromise<U>;
  
  catch<U>(
    onRejected?: (reason: any) => U | PromiseLike<U>
  ): MyPromise<T | U>;
}

// 使用示例
declare function createPromise<T>(executor: (resolve: (value: T) => void) => MyPromise<T>;

场景2:链式返回值类型推导

type UnwrapPromise<T> = 
  T extends Promise<infer U> ? UnwrapPromise<U> : T;

interface MyPromise<T> {
  then<U>(
    onFulfilled: (value: T) => U
  ): MyPromise<UnwrapPromise<U>>;
}

// 测试
const chain = createPromise(42)
  .then(v => v.toFixed())    // v: number → string
  .then(s => parseInt(s));   // s: string → number

高级链式类型技巧

技巧1:自动解包嵌套 Promise

type UnwrapPromise<T> = 
  T extends Promise<infer U> ? UnwrapPromise<U> : 
  T extends (...args: any) => Promise<infer V> ? UnwrapPromise<V> : 
  T;

// 处理多层嵌套
const nested = createPromise(Promise.resolve(Promise.resolve(42)));
// 类型: MyPromise<number> 而非 MyPromise<Promise<Promise<number>>>

技巧2:错误类型约束

interface MyPromise<T, E = Error> {
  catch<U>(
    onRejected: (reason: E) => U
  ): MyPromise<T | UnwrapPromise<U>, E>;
}

// 使用
createPromise<number, RangeError>(...)
  .catch(e => e.message)  // e: RangeError → string

技巧3:静态方法类型化

class MyPromise {
  static resolve<T>(value: T): MyPromise<UnwrapPromise<T>>;
  static all<T extends any[]>(values: [...T]): MyPromise<{
    [K in keyof T]: UnwrapPromise<T[K]>
  }>;
}

边界情况处理

场景1:空 then 处理

interface MyPromise<T> {
  then(): MyPromise<T>;  // 透传当前类型
  then<U>(onFulfilled: null, onRejected?: (reason: any) => U): MyPromise<T | U>;
}

// 示例
createPromise(42)
  .then()                // 类型仍为 MyPromise<number>
  .then(null, e => {});  // 跳过成功回调

场景2:混合同步/异步返回值

type AsyncValue<T> = T | PromiseLike<T>;
type UnwrapAsync<T> = T extends PromiseLike<infer U> ? U : T;

interface MyPromise<T> {
  then<U>(
    onFulfilled: (value: T) => AsyncValue<U>
  ): MyPromise<UnwrapAsync<U>>;
}

// 处理同步返回值
createPromise(42)
  .then(v => v * 2)      // 同步返回 number → MyPromise<number>
  .then(v => Promise.resolve(String(v))); // 异步返回 → MyPromise<string>

工程实践与面试考点

Promise 组合模式:

// 1. race 类型实现
static race<T extends any[]>(values: T): MyPromise<UnwrapPromise<T[number]>>;

// 2. 顺序执行链
const tasks: (() => Promise<number>)[] = [...];
tasks.reduce((chain, task) => 
  chain.then(() => task()), Promise.resolve()
);

性能优化:

// 避免深层递归类型 (设置最大解包深度)
type UnwrapPromiseDeep<T, Depth extends number = 5> = 
  Depth extends 0 ? T :
  T extends Promise<infer U> 
    ? UnwrapPromiseDeep<U, Subtract<Depth, 1>> 
    : T;

终极挑战:实现 async/await 类型推导

// 模拟 async 函数返回类型
type AsyncFunction<T> = (...args: any[]) => MyPromise<T>;

// 模拟 await 类型推导
type Awaited<T> = T extends MyPromise<infer U> ? Awaited<U> : T;

declare function runAsync<F extends AsyncFunction<any>>(fn: F): 
  ReturnType<F> extends MyPromise<infer R> ? R : never;

面试问题示例:

Q:为什么 Promise<Promise<number>> 会自动扁平化为 Promise<number>

A:由 then 方法的类型定义决定:

interface Promise<T> {
  then<TResult>(
    onfulfilled: (value: T) => TResult | PromiseLike<TResult>
  ): Promise<TResult>;
}

当 T 本身是 Promise<U> 时,value: T 实际接收 Promise<U>,但返回 TResult | PromiseLike<TResult> 会被再次包装为 Promise<TResult>,形成递归解包效果。

2.2.3 Redux reducer 类型安全实现

核心类型架构

Redux 类型四要素:

// 1. 状态类型
type State = {
  counter: number;
  todos: { id: string; text: string; completed: boolean }[];
};

// 2. Action 类型(联合类型)
type Action = 
  | { type: 'INCREMENT'; payload: number }
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: string };

// 3. Reducer 函数签名
type Reducer<S> = (state: S, action: Action) => S;

// 4. Dispatch 类型
type Dispatch = (action: Action) => void;

类型安全 reducer 实现

基础实现:

const reducer: Reducer<State> = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      // ✅ 自动推断 payload 为 number
      return { ...state, counter: state.counter + action.payload };
    
    case 'ADD_TODO':
      // ✅ 自动推断 payload 为 string
      const newTodo = { 
        id: nanoid(), 
        text: action.payload, 
        completed: false 
      };
      return { ...state, todos: [...state.todos, newTodo] };
    
    case 'TOGGLE_TODO':
      // ✅ 自动推断 payload 为 string
      return {
        ...state,
        todos: state.todos.map(todo => 
          todo.id === action.payload 
            ? { ...todo, completed: !todo.completed } 
            : todo
        )
      };
    
    default:
      // ❌ 捕获未处理 action
      const _exhaustiveCheck: never = action;
      return state;
  }
};

高级模式:类型生成器

自动创建类型安全 action:

// Action 创建函数工厂
function createAction<T extends string, P>(type: T) {
  return (payload: P) => ({ type, payload });
}

// 使用示例
const increment = createAction('INCREMENT', (amount: number) => amount);
const addTodo = createAction('ADD_TODO', (text: string) => text);

// 自动生成 Action 类型
type Actions = ReturnType<typeof increment> | ReturnType<typeof addTodo>;

类型安全 reducer 生成器:

type ActionMap = {
  INCREMENT: number;
  ADD_TODO: string;
  TOGGLE_TODO: string;
};

function createReducer<S, A extends Record<string, any>>(
  initialState: S,
  handlers: {
    [K in keyof A]: (state: S, payload: A[K]) => S
  }
) {
  return (state: S = initialState, action: { 
    [K in keyof A]: { type: K; payload: A[K] } 
  }[keyof A]) => {
    if (handlers[action.type]) {
      return handlers[action.type](state, action.payload);
    }
    return state;
  };
}

// 使用
const reducer = createReducer<State, ActionMap>(initialState, {
  INCREMENT: (state, payload) => ({ ...state, counter: state.counter + payload }),
  ADD_TODO: (state, text) => ({ ...state, todos: [...state.todos, { id: nanoid(), text }] })
});

Redux Toolkit 集成

现代 Redux 类型安全实践:

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: [] as Todo[],
  reducers: {
    addTodo: {
      reducer: (state, action: PayloadAction<Todo>) => {
        state.push(action.payload);
      },
      prepare: (text: string) => ({
        payload: { id: nanoid(), text, completed: false }
      })
    },
    toggleTodo: (state, action: PayloadAction<string>) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) todo.completed = !todo.completed;
    }
  }
});

// 自动生成 Action 类型
type TodosAction = ReturnType<typeof todosSlice.actions.addTodo> 
  | ReturnType<typeof todosSlice.actions.toggleTodo>;

复杂场景处理

异步 Action 类型:

import { ThunkAction } from 'redux-thunk';

type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  State,
  unknown,
  Action
>;

const fetchTodos = (): AppThunk => async dispatch => {
  dispatch({ type: 'FETCH_TODOS_REQUEST' });
  try {
    const todos = await api.getTodos();
    dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos });
  } catch (error) {
    dispatch({ type: 'FETCH_TODOS_FAILURE', payload: error.message });
  }
};

Reducer 组合类型:

type CombinedState = {
  todos: TodoState;
  user: UserState;
};

// 类型安全的 combineReducers
function combineTypedReducers<M>(reducers: M): Reducer<{
  [K in keyof M]: M[K] extends Reducer<infer S> ? S : never
}> {
  return (state, action) => {
    const newState = {} as any;
    for (const key in reducers) {
      newState[key] = reducers[key](state?.[key], action);
    }
    return newState;
  };
}

边界处理与优化

边界情况 解决方案
空 action 处理 使用 never 类型进行穷尽检查
嵌套状态更新 使用 Immer 库进行可变写法
历史状态类型 添加 undoable reducer 类型扩展
动态 reducer 注册 类型安全的 injectReducer 模式

性能优化:

// 1. 避免深层嵌套状态
type State = {
  // ❌ 避免
  deep: { a: { b: { c: number } } };
  // ✅ 推荐
  flat: Record<string, FlatItem>;
};

// 2. 使用 Reselect 记忆化选择器
import { createSelector } from 'reselect';

const selectTodos = (state: State) => state.todos;
const selectCompletedTodos = createSelector(
  [selectTodos],
  todos => todos.filter(t => t.completed)
);

面试级考点

  1. 类型收窄原理:
    // 利用可辨识联合(discriminated union)
    if (action.type === 'INCREMENT') {
      // 此处 action 自动收窄为 { type: 'INCREMENT'; payload: number }
    }
  2. 类型安全与性能平衡:
    // ❌ 过度泛化
    type Action<T = any> = { type: string; payload: T };
    
    // ✅ 精确控制
    type StrictAction = { type: 'A'; payload: number } | { type: 'B'; payload: string };
  3. Redux 中间件类型:
    interface MiddlewareAPI<D extends Dispatch, S> {
      dispatch: D;
      getState(): S;
    }
    
    type Middleware = <S>(api: MiddlewareAPI<Dispatch, S>) => 
      (next: Dispatch) => (action: any) => any;
  4. 时间旅行调试类型:
    type TimeTravelState = {
      past: State[];
      present: State;
      future: State[];
    };
  5. 终极挑战:实现类型安全的 Redux-Observable
    type Epic = (
      action$: Observable<Action>,
      state$: StateObservable<State>
    ) => Observable<Action>;
    
    const fetchEpic: Epic = (action$, state$) => 
      action$.pipe(
        ofType('FETCH_REQUEST'),
        switchMap(action => 
          from(api.fetchData(action.payload)).pipe(
            map(response => ({ type: 'FETCH_SUCCESS', payload: response })),
            catchError(error => of({ type: 'FETCH_ERROR', payload: error }))
        )
      );

2.2.4 路由参数自动类型推导

核心挑战

  1. 路径解析:将字符串路径(如 /user/:id/posts/:postId)转换为类型结构
  2. 参数提取:识别动态段(:param)和可选段(?)
  3. 类型转换:将参数名映射为对象键({ id: string; postId: string })
  4. 嵌套路由:支持多层路由参数合并

基础路径参数推导

场景1:提取单一路径参数

type ExtractParam<Path> = 
  Path extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParam<Rest>
    : Path extends `${string}:${infer Param}`
      ? Param
      : never;

type Test1 = ExtractParam<'/user/:id'>;  // "id"
type Test2 = ExtractParam<'/post/:postId/comment/:commentId'>;  // "postId" | "commentId"

场景2:转换为参数对象类型

type PathParams<Path> = {
  [K in ExtractParam<Path>]: string;
};

type Params1 = PathParams<'/user/:id'>;  // { id: string }
type Params2 = PathParams<'/category/:categoryId/product/:productId'>; 
// { categoryId: string; productId: string }

高级路由特性实现

特性1:可选参数(?)

type ExtractOptionalParam<Path> =
  Path extends `${string}:${infer Param}?/${infer Rest}`
    ? Param | ExtractOptionalParam<Rest>
    : Path extends `${string}:${infer Param}?`
      ? Param
      : never;

type PathParams<Path> = {
  [K in ExtractParam<Path>]: string;
} & {
  [K in ExtractOptionalParam<Path>]?: string;
};

type Params3 = PathParams<'/search/:query?'>;  // { query?: string }
type Params4 = PathParams<'/user/:id/profile/:tab?'>; 
// { id: string; tab?: string }

特性2:通配符匹配(*)

type ExtractWildcard<Path> =
  Path extends `${string}*${infer Rest}`
    ? 'wildcard' | ExtractWildcard<Rest>
    : never;

type PathParams<Path> = {
  [K in ExtractParam<Path>]: string;
} & {
  [K in ExtractOptionalParam<Path>]?: string;
} & {
  [K in ExtractWildcard<Path>]: string[];
};

type Params5 = PathParams<'/files/*'>;  // { wildcard: string[] }

特性3:正则约束参数

type ExtractConstrainedParam<Path> =
  Path extends `${string}:${infer Param}(${infer Regex})/${infer Rest}`
    ? { param: Param; regex: Regex } | ExtractConstrainedParam<Rest>
    : Path extends `${string}:${infer Param}(${infer Regex})`
      ? { param: Param; regex: Regex }
      : never;

type MapRegexToType<R> = 
  R extends '\\d+' ? number :
  R extends '\\w+' ? string :
  string;  // 默认回退

type PathParams<Path> = {
  [K in ExtractParam<Path>]: string;
} & {
  [K in ExtractOptionalParam<Path>]?: string;
} & {
  [K in ExtractWildcard<Path>]: string[];
} & {
  [P in ExtractConstrainedParam<Path> as P['param']]: 
    MapRegexToType<P['regex']>;
};

type Params6 = PathParams<'/user/:id(\\d+)'>;  // { id: number }
type Params7 = PathParams<'/post/:slug(\\w+)'>; // { slug: string }

React Router 集成实践

类型安全路由组件:

import { useParams } from 'react-router-dom';

// 创建类型安全版本
export function useTypedParams<Path extends string>() {
  type Params = PathParams<Path>;
  return useParams() as Partial<Params>; // 可能未匹配所有参数
}

// 在组件中使用
const UserProfile = () => {
  const params = useTypedParams<'/user/:id/:tab?'>(); 
  // params: { id?: string; tab?: string }
  
  return <div>User ID: {params.id || 'Unknown'}</div>;
};

类型安全路由配置:

type RouteConfig<Path extends string> = {
  path: Path;
  element: React.ReactNode;
  children?: RouteConfig<any>[];
};

function createRoute<Path extends string>(config: RouteConfig<Path>) {
  return config;
}

const routes = createRoute({
  path: '/user/:id',
  element: <UserPage />,
  children: [
    { 
      path: 'posts/:postId',  // 完整路径: /user/:id/posts/:postId
      element: <PostPage />
    }
  ]
});

// 自动推导参数类型
type UserPageParams = PathParams<typeof routes.path>;          // { id: string }
type PostPageParams = PathParams<typeof routes.children[0].path>; 
// { id: string; postId: string } (包含父级参数)

边界处理与优化

递归深度控制:

type ExtractParam<Path, Depth extends number = 5> = 
  Depth extends 0 ? never :
  Path extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParam<Rest, Subtract<Depth, 1>>
    : Path extends `${string}:${infer Param}`
      ? Param
      : never;

路径冲突检测:

type DetectConflict<Path> = 
  ExtractParam<Path> extends infer P
    ? P extends any
      ? { [K in P]: K } extends Record<P, P>
        ? Path  // 无冲突
        : never // 存在重复参数
      : never
    : never;

// 使用示例
type SafePath = DetectConflict<'/user/:id/posts/:id'>;  // never (检测到重复id)

面试级考点

  1. 模板字面量类型操作:
    type SplitPath<P> = P extends `/${infer Part}/${infer Rest}` 
      ? [Part, ...SplitPath<`/${Rest}`>] 
      : P extends `/${infer Part}` 
        ? [Part] 
        : [];
  2. 类型递归限制:TS 4.5+ 支持尾递归优化
  3. React Router v6 类型实现:
    // 官方ParamKey类型定义
    type ParamKey<TPath> = TPath extends `${infer _}:${infer Param}/${infer Rest}`
      ? Param | ParamKey<Rest>
      : TPath extends `${infer _}:${infer Param}`
        ? Param
        : never;
  4. Vue Router 对比:
    // Vue Router的RouteRecordRaw类型
    interface RouteRecordRaw {
      path: string;
      component: Component;
      children?: RouteRecordRaw[];
    }
  5. 终极挑战:实现Next.js文件路由类型
    type NextPageParams<Path> = 
      Path extends `pages/${infer Route}.tsx` 
        ? PathParams<Route>
        : never;
    
    // 文件路径: pages/user/[id]/[tab].tsx
    type Params = NextPageParams<'pages/user/[id]/[tab].tsx'>; 
    // { id: string; tab: string }

2.3 模板字面量类型

2.3.1 字符串模板类型操作

2.3.2 Uppercase/Lowercase 类型转换

2.3.3 路由路径模式匹配

2.3.4 国际化键名自动补全

2.4 类型编程优化

2.4.1 类型递归与尾递归优化

2.4.2 类型实例化深度控制

2.4.3 条件类型短路技巧

2.4.4 类型性能分析工具

三、工程化实践

3.1 TSConfig 深度配置

3.1.1 严格模式全家桶配置

严格模式核心配置

启用所有严格检查:

{
  "compilerOptions": {
    "strict": true
  }
}

此配置等价于同时启用以下所有子选项(TypeScript 2.3+)。

严格模式子选项详解

选项 默认值 作用 推荐值
strict false 所有严格检查的总开关 true
noImplicitAny false 禁止隐式 any 类型 true
noImplicitThis false 禁止隐式 any 类型的 this true
strictNullChecks false 严格的 null/undefined 检查 true
strictFunctionTypes false 严格的函数类型检查(逆变参数) true
strictBindCallApply false 严格的 bind/call/apply 方法检查 true
strictPropertyInitialization false 严格的类属性初始化检查(需配合 strictNullChecks) true
alwaysStrict false 以严格模式解析代码,并在每个文件顶部添加 “use strict” true
useUnknownInCatchVariables false catch 子句变量默认为 unknown 类型(TS 4.4+) true

配置详解与工程实践

  1. noImplicitAny
    • 问题代码:
      function log(message) { // 🚫 参数隐式 any
        console.log(message);
      }
    • 解决方案:
      function log(message: string) { // ✅ 显式声明类型
        console.log(message);
      }
  2. strictNullChecks
    • 问题代码:
      const element = document.getElementById("app");
      element.innerHTML = "Hello"; // 🚫 可能为 null
    • 解决方案:
      const element = document.getElementById("app");
      if (element) { // ✅ 安全检查
        element.innerHTML = "Hello";
      }
  3. strictPropertyInitialization
    • 问题代码:
      class User {
        name: string; // 🚫 未初始化
      }
    • 解决方案:
      class User {
        name!: string; // ✅ 明确赋值断言
        // 或
        constructor(public name: string) {}
      }
  4. useUnknownInCatchVariables
    • 问题代码:
      try { /* ... */ } 
      catch (err) { // any 类型
        console.log(err.message); // 🚫 可能不存在
      }
    • 解决方案:
      try { /* ... */ } 
      catch (err: unknown) { // ✅ TS 4.4+
        if (err instanceof Error) {
          console.log(err.message);
        }
      }

四、严格模式配置策略

  1. 渐进启用策略:
    {
      "compilerOptions": {
        "strict": true,
        "noImplicitAny": false // 临时关闭最严格的规则
      }
    }
  2. 文件级覆盖:
    // @ts-nocheck  // 禁用整个文件检查
    // @ts-ignore   // 禁用下一行检查
    
    function legacyCode() { 
      // @ts-expect-error // 预期错误(TS 3.9+)
      const x: number = "string"; 
    }
  3. 目录差异化配置:
    // 根目录 tsconfig.json
    {
      "extends": "./tsconfig.base.json",
      "references": [
        { "path": "./src" },        // 应用严格模式
        { "path": "./legacy" }      // 非严格模式
      ]
    }
    
    // legacy/tsconfig.json
    {
      "extends": "../tsconfig.base.json",
      "compilerOptions": {
        "strict": false
      }
    }

性能与严格模式

  1. 编译速度影响:
    严格选项 性能影响 缓解措施
    strictNullChecks 项目引用隔离修改范围
    noImplicitAny 渐进启用 + @ts-ignore 临时方案
    strictFunctionTypes 无需特别处理
  2. 内存占用优化:
    {
      "compilerOptions": {
        "strict": true,
        "incremental": true,  // 增量编译
        "tsBuildInfoFile": "./buildcache/.tsbuildinfo" // 缓存位置
      }
    }

严格模式错误处理

  1. 常见错误类型:
    错误代码 含义 解决方案
    TS7006 参数隐式 any 添加类型注解
    TS2532 对象可能为 undefined 添加空值检查
    TS2564 属性未初始化 构造函数初始化或明确赋值断言
    TS2345 参数类型不匹配 修正参数类型或使用类型断言
  2. 错误自动修复:
    // .vscode/settings.json
    {
      "typescript.tsserver.experimental.enableProjectDiagnostics": true,
      "editor.codeActionsOnSave": {
        "source.fixAll.ts": true
      }
    }

企业级配置模板

// tsconfig.strict.json
{
  "compilerOptions": {
    "strict": true,
    "noFallthroughCasesInSwitch": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "incremental": true,
    "composite": true
  }
}

3.1.2 模块解析策略实战

模块解析策略概述

TypeScript 支持两种模块解析策略:

策略 触发条件 适用场景
Classic “moduleResolution”: “Classic” TypeScript 1.6 前旧项目
Node “moduleResolution”: “Node” 现代项目(默认值)
Node16/NodeNext “moduleResolution”: “Node16” Node.js 16+ 和 ESM 项目

Node 解析策略详解

解析流程(import { x } from ‘module’):

  1. 检查当前目录的 node_modules/module.ts/module.tsx/module.d.ts
  2. 检查 node_modules/module/package.json 的 types 或 main 字段
  3. 检查 node_modules/module/index.ts/index.d.ts
  4. 递归检查父目录的 node_modules(../node_modules/module)

文件扩展名解析顺序:

[
  ".ts", ".tsx", ".d.ts",         // TypeScript 文件
  ".js", ".jsx", ".cjs", ".mjs",  // JavaScript 文件
  ".json"                         // JSON 文件
]

Node16/NodeNext 策略

ESM 专属特性:

  1. 支持 package.json 的 exports 和 imports 字段
  2. 严格区分 .js (CommonJS) 和 .mjs (ESM) 文件
  3. 支持 type: “module” 声明

exports 字段优先级:

{
  "exports": {
    ".": {
      "import": "./dist/esm/index.js",   // ESM 入口
      "require": "./dist/cjs/index.js",  // CommonJS 入口
      "types": "./types/index.d.ts"      // 类型声明
    },
    "./feature": "./dist/feature.js"     // 子路径导出
  }
}

实战配置示例

基础配置:

// tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Node16",
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["components/*"],
      "@utils": ["utils/index"]
    }
  }
}

多环境配置:

{
  "compilerOptions": {
    "moduleResolution": "Node16",
    "paths": {
      "config": [
        "config/prod", // 生产环境
        "config/dev"   // 开发环境
      ]
    }
  }
}

路径映射实战

  1. 别名配置:
    {
      "baseUrl": ".",
      "paths": {
        "@app/*": ["src/*"],
        "shared/*": ["../shared/*"]
      }
    }
  2. 多路径回退:
    {
      "paths": {
        "legacy-utils": [
          "src/utils/v2", // 优先使用 v2
          "src/utils/v1"  // 回退到 v1
        ]
      }
    }
  3. 通配符扩展:
    {
      "paths": {
        "@assets/*": [
          "assets/*",
          "public/images/*"
        ]
      }
    }

常见问题解决方案

问题1:路径映射运行时失效

解决方案:

# 安装运行时解析器
npm install tsconfig-paths --save-dev

# Node.js 启动命令
node -r tsconfig-paths/register app.ts

问题2:循环依赖解析失败

重构方案:

graph LR
    A[ModuleA] -->|import| B[ModuleB]
    B -->|import| C[ModuleC]
    C -->|import| A[ModuleA]
    
    subgraph 重构后
    D[Common] --> E[ModuleA]
    D[Common] --> F[ModuleB]
    D[Common] --> G[ModuleC]
    end

问题3:动态导入类型丢失

类型安全方案:

// 动态导入类型封装
async function safeImport<T>(path: string): Promise<T> {
  const module = await import(path);
  return module as T;
}

// 使用
interface ChartModule {
  render: (data: any) => void;
}

const chart = await safeImport<ChartModule>("./chart");
chart.render(data);

构建工具集成

  1. Webpack 配置:
    // webpack.config.js
    const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
    
    module.exports = {
      resolve: {
        plugins: [new TsconfigPathsPlugin()],
        extensions: ['.ts', '.tsx', '.js']
      }
    };
  2. Vite 配置:
    // vite.config.ts
    import { defineConfig } from 'vite';
    import tsconfigPaths from 'vite-tsconfig-paths';
    
    export default defineConfig({
      plugins: [tsconfigPaths()]
    });

3.1.3 声明文件生成配置

3.1.4 编译器缓存策略

3.2 声明文件(.d.ts)

3.2.1 第三方库类型扩展技巧

声明文件扩展基础

核心方法:使用 declare module 语法扩展第三方库类型

// global.d.ts
declare module '第三方库名' {
  // 扩展内容
}

常见扩展场景

  1. 添加缺失类型:
    declare module 'untyped-lib' {
      export function calculate(a: number, b: number): number;
      export const version: string;
    }
  2. 扩展现有接口:
    import 'react';
    
    declare module 'react' {
      interface HTMLAttributes<T> {
        // 添加自定义属性
        customAttr?: string;
      }
    }
    
    // 使用
    <div customAttr="value" />; // ✅ 类型安全
  3. 覆盖错误类型:
    declare module 'flawed-lib' {
      // 修正错误返回类型
      export function fetchData(): Promise<Data>; // 原声明为 any
    }

模块合并策略

  1. 接口合并:
    // 原始声明
    interface Config {
      timeout: number;
    }
    
    // 扩展声明
    declare module 'configurable-lib' {
      interface Config {
        retries?: number; // 添加可选属性
      }
    }
  2. 函数重载扩展:
    // 原始函数
    declare function log(message: string): void;
    
    // 扩展重载
    declare module 'logger-lib' {
      function log(data: object): void; // 添加对象支持
    }
  3. 命名空间合并:
    // 原始命名空间
    declare namespace Analytics {
      function track(event: string): void;
    }
    
    // 扩展命名空间
    declare module 'analytics-lib' {
      namespace Analytics {
        function identify(userId: string): void; // 添加新方法
      }
    }

类型扩展最佳实践

  1. 模块化扩展文件:
    src/
      types/
        extensions/
          react.d.ts      # React扩展
          lodash.d.ts     # Lodash扩展
          global.d.ts     # 全局扩展
      tsconfig.json
  2. 条件扩展模式:
    // 根据环境变量扩展
    type Env = 'development' | 'production';
    
    declare module 'configurable-lib' {
      interface Config {
        debug?: Env extends 'development' ? boolean : never;
      }
    }
  3. 类型安全增强:
    // 添加严格类型约束
    declare module 'flexible-lib' {
      function parse(input: string): unknown;
      function parse<T>(input: string): T; // 泛型版本
    }

高级扩展技巧

  1. 模板字面量类型扩展:
    declare module 'router-lib' {
      type DynamicRoute<T extends string> = 
        T extends `${string}:${infer Param}${string}` ? Param : never;
      
      interface Router {
        get(route: string): void;
        params: Record<DynamicRoute<keyof this.routes>, string>;
      }
    }
  2. 泛型约束增强:
    declare module 'storage-lib' {
      interface Storage<T extends Record<string, any>> {
        get<K extends keyof T>(key: K): T[K];
        set<K extends keyof T>(key: K, value: T[K]): void;
      }
    }
  3. 类型谓词扩展:
    declare module 'validator-lib' {
      interface Validators {
        isEmail: (input: unknown) => input is string;
      }
    }

常见问题解决方案

问题1:模块扩展冲突

解决方案:使用唯一标识符

// 原始库
declare module 'ui-lib' {
  interface ButtonProps {
    variant: 'primary' | 'secondary';
  }
}

// 扩展(避免直接修改)
declare module 'ui-lib' {
  interface ButtonProps {
    __extendedVariant?: 'tertiary'; // 使用特殊前缀
  }
}

问题2:全局污染

解决方案:封装扩展

// 安全扩展方案
type ExtendedLib = typeof import('base-lib') & {
  newMethod: () => void;
};

const lib = require('base-lib') as ExtendedLib;
lib.newMethod(); // 安全访问

问题3:类型覆盖丢失

解决方案:版本化扩展

// 根据库版本动态扩展
type LibVersion = '1.x' | '2.x';

declare global {
  var __libVersion: LibVersion;
}

declare module 'versioned-lib' {
  interface Config {
    [__libVersion extends '2.x' ? 'newFeature' : never]: boolean;
  }
}

React 生态扩展

  1. 组件属性扩展:
    declare module 'react-table' {
      export interface ColumnInstance<D extends object = {}> {
        align?: 'left' | 'center' | 'right'; // 添加对齐属性
      }
    }
  2. Hooks 返回值扩展:
    declare module 'react-query' {
      interface QueryResult<T> {
        customMeta?: { timestamp: number }; // 添加元数据
      }
    }
  3. Context 类型增强:
    import { ThemeContext } from 'theme-provider';
    
    declare module 'theme-provider' {
      interface Theme {
        customColor: string; // 扩展主题对象
      }
    }
    
    // 使用
    const theme = useContext(ThemeContext);
    theme.customColor; // ✅ 类型安全

扩展测试策略

  1. 类型测试:
    // 验证扩展是否生效
    type Test = Expect<
      // 检查扩展属性是否存在
      'customAttr' extends keyof React.HTMLAttributes<any> ? true : false
    >;
  2. 运行时验证:
    // 确保扩展与运行时兼容
    if ('newMethod' in lib) {
      lib.newMethod();
    } else {
      console.warn('Library extension not available');
    }

工程化建议

  1. 扩展文档化:
    ## UI 库扩展说明
    
    ### 新增属性
    | 属性名        | 类型   | 说明               |
    |---------------|--------|--------------------|
    | customAttr    | string | 自定义HTML属性     |
    | __extendedVariant | 'tertiary' | 特殊按钮类型    |
  2. 版本管理策略:
    // package.json
    {
      "name": "@types/custom-lib-extension",
      "version": "1.0.0",
      "peerDependencies": {
        "target-lib": "^2.0.0"
      }
    }
  3. 扩展发布流程:
        graph LR
            A[创建扩展声明] --> B[本地测试验证]
            B --> C[发布到内部npm]
            C --> D[项目安装使用]

扩展安全指南

  1. 避免全局污染:
    // 错误:全局扩展基础类型
    interface Array<T> {
      customMethod(): void;
    }
    
    // 正确:模块化扩展
    declare module 'array-extension' {
      interface Array<T> {
        safeCustomMethod(): void;
      }
    }
  2. 防御性类型检查:
    // 安全扩展函数
    declare module 'utils-lib' {
      function parse(input: unknown): unknown;
      function parse<T>(input: T): T extends string ? Data : never;
    }
  3. 兼容性处理:
    // 处理不同版本的库
    declare module 'multi-version-lib' {
      interface CoreAPI {
        commonMethod(): void;
      }
      
      export interface V1API extends CoreAPI {
        v1Method(): void;
      }
      
      export interface V2API extends CoreAPI {
        v2Method(): void;
      }
      
      const api: V1API | V2API;
      export default api;
    }

扩展参考表

扩展类型 适用场景 示例
接口属性扩展 添加新属性/方法 interface { newProp: type }
函数重载扩展 支持新参数类型 function log(data: object)
泛型约束增强 增加类型参数约束 Storage<T extends object>
条件类型扩展 根据环境动态扩展 debug?: Env extends 'dev'
模板字面量扩展 路由参数/国际化键名 DynamicRoute<path>
类型谓词扩展 自定义类型守卫 isEmail(input): input is string

3.2.2 模块声明合并策略

声明合并基础概念

核心机制:TypeScript 允许将多个同名的声明合并为单一类型定义

合并类型:

interface User {
  name: string;
}

interface User {
  age: number;
}

// 合并结果
const user: User = {
  name: "Alice",
  age: 30  // ✅ 合并属性
};

模块声明合并策略

  1. 接口合并:
    // module.d.ts
    declare module 'my-lib' {
      interface Config {
        timeout: number;
      }
    }
    
    // user-extension.d.ts
    declare module 'my-lib' {
      interface Config {
        retries?: number; // 添加新属性
      }
    }
    
    // 合并结果
    const config: import('my-lib').Config = {
      timeout: 1000,
      retries: 3  // ✅
    };
  2. 函数合并:
    // 原始声明
    declare module 'utils' {
      function parse(input: string): any;
    }
    
    // 扩展声明
    declare module 'utils' {
      function parse<T>(input: string): T; // 泛型版本
    }
    
    // 使用
    const data = parse<{ id: string }>('{"id":"123"}'); // ✅ 类型安全
  3. 命名空间合并:
    // 原始命名空间
    declare module 'analytics' {
      namespace Tracking {
        function pageview(url: string): void;
      }
    }
    
    // 扩展命名空间
    declare module 'analytics' {
      namespace Tracking {
        function event(name: string, data: object): void;
      }
    }
    
    // 使用
    Tracking.event("click", { button: "submit" }); // ✅

合并优先级规则

声明类型 合并优先级 示例
接口属性 顺序无关,同名属性必须兼容 interface A { x: number }
interface A { x: string } → 错误
函数重载 后声明优先 最后声明的签名最先匹配
命名空间 按声明顺序合并 后声明的成员覆盖先前的
类与接口 接口声明优先 类实现必须满足所有接口声明

模块合并最佳实践

  1. 分层声明结构:
    types/
      ├── lib/
      │   ├── base.d.ts      // 基础声明
      │   └── extensions/    // 扩展声明
      │       ├── v1.d.ts
      │       └── v2.d.ts
      └── global.d.ts        // 全局合并
  2. 版本化合并策略:
    // 条件合并
    type LibVersion = 'v1' | 'v2';
    
    declare module 'versioned-lib' {
      export interface Api {
        commonMethod(): void;
      }
    }
    
    declare module 'versioned-lib' {
      export interface Api {
        [LibVersion extends 'v2' ? 'newMethod' : never]?: () => void;
      }
    }
  3. 安全覆盖模式:
    declare module 'sensitive-lib' {
      interface __OriginalConfig {
        // 保留原始定义
        originalProp: string;
      }
      
      interface Config extends __OriginalConfig {
        // 安全扩展
        customProp?: number;
      }
    }

React 组件合并案例

  1. 组件属性合并:
    // 原始组件声明
    declare module 'react-ui' {
      export interface ButtonProps {
        variant: 'primary' | 'secondary';
      }
      export function Button(props: ButtonProps): JSX.Element;
    }
    
    // 扩展属性
    declare module 'react-ui' {
      export interface ButtonProps {
        size?: 'sm' | 'md' | 'lg'; // 添加新属性
      }
    }
    
    // 使用
    <Button variant="primary" size="md" />; // ✅
  2. Hooks 返回值合并:
    // 原始Hook
    declare module 'react-hooks' {
      function useData(): { loading: boolean; data: any };
    }
    
    // 扩展返回值
    declare module 'react-hooks' {
      function useData<T>(): { loading: boolean; data: T | null; error?: Error };
    }

合并冲突解决方案

冲突类型 1:属性类型不兼容

// 声明1
interface Config {
  logLevel: string;
}

// 声明2
interface Config {
  logLevel: 'debug' | 'info'; // 🚫 类型冲突
}

解决方案:

// 方法1:使用联合类型
interface Config {
  logLevel: string | 'debug' | 'info';
}

// 方法2:版本化声明
declare const __configVersion: 'v1' | 'v2';
interface Config {
  logLevel: __configVersion extends 'v2' ? 'debug' | 'info' : string;
}

冲突类型 2:函数重载歧义

declare function parse(input: string): any;
declare function parse(input: string): object; // 🚫 相同参数类型

解决方案:

// 明确重载顺序
declare function parse(input: string): any;
declare function parse(input: object): object; // ✅ 不同参数类型

声明合并参考表

合并场景 推荐策略 示例
接口属性扩展 直接添加新属性 interface { newProp: type }
函数重载扩展 添加不同参数类型的重载 function parse(data: object)
泛型约束增强 使用条件类型继承 Store<T extends object>
跨模块类型合并 使用类型查询和组合 type Full = LibA.Type & LibB.Type
版本差异化合并 条件类型 + 环境变量 __version extends 'v2' ? ...
第三方库补丁 模块声明合并 + 防御性检查 declare module 'lib' { ... }

3.2.3 全局变量类型定义

核心概念

全局变量:在模块系统外可直接访问的变量(如 window 对象属性、CDN 引入的库)

定义目标:

  • 为非模块化脚本提供类型支持
  • 扩展全局命名空间(如 window、global)
  • 避免 Cannot find name ‘xxx’ 类型错误

代码示例

// ⭐ 场景1:声明全局变量 (声明文件.d.ts)
declare const VERSION: string;  // 编译时注入的版本号
declare function trackEvent(eventName: string, payload: object): void;  // 全局埋点函数

// ⭐ 场景2:扩展全局对象 (如 Window 接口)
interface Window {
  _env: {
    API_HOST: string;
    DEBUG_MODE: boolean;
  };
  ga: (command: string, ...args: unknown[]) => void;  // Google Analytics
}

// ⭐ 场景3:模块化环境中的全局声明 (使用 declare global)
export {};  // 确保文件是模块环境

declare global {
  interface Array<T> {
    findLast(predicate: (value: T) => boolean): T | undefined;  // 扩展原生Array
  }
  
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production';
      SSR_ENABLED: boolean;
    }
  }
}

工程实践与陷阱

场景 解决方案 注意事项
CDN 引入的库 declare const jQuery: any; 优先安装 @types 包
注入的运行时变量 declare const __INITIAL_STATE__: AppState; 需与构建脚本配合注入
扩展浏览器原生对象 通过 interface 合并声明 避免覆盖标准类型定义
多环境全局变量 使用 .d.ts + 条件类型 区分 process.env 和 import.meta.env

典型错误案例:

// ❌ 错误:直接覆盖Window类型
type Window = CustomWindow;  

// ✅ 正确:通过接口合并
interface Window {
  customProp: number;
}

面试级考点

  • 类型合并机制:接口声明合并 vs 命名空间合并
  • 模块化影响:
    • 全局声明必须放在 .d.ts 文件或使用 declare global
    • 包含 import/export 的文件会被视为模块
  • 类型可见性规则:
    // types.d.ts
    declare interface GlobalType { id: number }  // 全局可见
    
    // utils.ts
    export declare interface LocalType {}  // 仅模块内可见
  • 与 window as any 的取舍:优先声明具体类型保证安全

3.2.4 复杂类型声明最佳实践

核心设计原则

原则 说明 反模式案例
精确性 类型应严格匹配运行时行为 用 any 代替具体结构
可读性 通过类型别名分层抽象 嵌套超过三层的匿名类型
兼容性 考虑旧版 TypeScript 的解析能力 使用 TS 4.1+ 特性但未降级
可扩展性 预留扩展点(如索引签名) 完全封闭的类型定义

复杂类型声明模式

场景1:深度嵌套结构(如 API 响应)

// 使用接口分层 + 类型别名组合
interface UserProfile {
  id: number;
  contacts: {
    email: string;
    social?: { // 可选嵌套
      wechat: string;
      alipay: string;
    };
  };
}

// 提取公共结构
type Pagination<T> = {
  current_page: number;
  data: T[];
};

declare function fetchUserList(): Pagination<UserProfile>;

场景2:动态键名对象(如国际化字典)

// 模板字面量类型 + 索引签名
type Locale = 'zh-CN' | 'en-US';
type I18nKeys = `button.${'submit' | 'cancel'}` | `title.${'home' | 'about'}`;

declare const i18n: {
  [key in `${Locale}/${I18nKeys}`]: string; // 如 "zh-CN/button.submit"
} & {
  get(key: `${Locale}/${I18nKeys}`, vars?: Record<string, string>): string;
};

场景3:函数重载类型(如 SDK 方法)

// 使用联合类型 + 条件类型
type SDKOptions = 
  | { mode: 'sync'; timeout: number }
  | { mode: 'async'; callback: (res: Response) => void };

declare function invokeAPI(
  endpoint: '/user',
  options?: SDKOptions
): Promise<User> | User;

进阶技巧与陷阱规避

技巧1:类型递归深度控制

// ✅ 安全递归:限制深度
type MaxDepth<T, D extends number = 3> = 
  D extends 0 ? never : 
  T extends object ? {
    [K in keyof T]: MaxDepth<T[K], Subtract<D, 1>>
  } : T;

// ❌ 危险:无限递归
type InfiniteRecursion<T> = {
  value: T;
  children: InfiniteRecursion<T>[]; // 可能引发类型实例化过深错误
};

技巧2:防御性类型设计

// 防范不完整数据
type SafeAccess<T> = T extends object ? {
  [K in keyof T]-?: SafeAccess<T[K]>; // 移除可选修饰符
} : T;

// 使用 never 阻断非法路径
type ValidateRoute<Path> = 
  Path extends `/user/${infer Id}` 
    ? Id extends `${number}` 
      ? Path 
      : never 
    : never;

工程化实践

策略1:类型测试(使用 tsd 等工具)

// 测试类型是否符合预期
import { expectType } from 'tsd';

expectType<{ id: number }>(fetchUserList().data[0]);

策略2:声明文件组织规范

types/
├── global.d.ts         # 全局声明
├── libs/               # 第三方库扩展
│   └── vue-extend.d.ts
├── modules/            # 业务模块
│   └── user-api.d.ts
└── utils/              # 工具类型
    └── pagination.d.ts

策略3:版本兼容处理

// 条件编译支持多版本
declare module "some-lib" {
  export interface Config {
    // @ts-ignore-next-line 兼容旧版本类型缺失
    newFeature?: boolean; 
  }
}

面试级考点

  • 类型实例化深度限制:默认 50 层(通过 tsc –typeDepth 调整)
  • 声明合并优先级:
    interface A { x: number }
    interface A { x: string }  // ❌ 后续声明必须兼容前序声明
  • 类型性能优化:
    // ✅ 高效:使用映射类型
    type ReadonlyDeep<T> = { readonly [K in keyof T]: ReadonlyDeep<T[K]> }
    
    // ❌ 低效:递归条件类型
    type SlowType<T> = T extends object ? SlowType<T[keyof T]> : T
  • d.ts 与 ts 文件差异:.d.ts 不包含实现,仅类型声明
  • 实战案例:为 WebSocket 消息协议声明类型安全解析器
    type MessageProtocol = {
      login: { username: string; password: string };
      joinRoom: { roomId: number; timestamp: Date };
      // 使用模板类型自动生成事件名约束
      [E in `sys/${'alert' | 'error'}`]: { code: number };
    };
    
    declare function handleMessage<K extends keyof MessageProtocol>(
      type: K,
      handler: (payload: MessageProtocol[K]) => void
    ): void;

3.3 代码质量保障

3.3.1 ESLint 类型规则配置

3.3.2 单元测试类型检查方案

3.3.3 自定义类型规则开发

3.3.4 CI/CD 中的类型检查

3.4 构建优化

3.4.1 Webpack 构建缓存配置

3.4.2 Babel 与 tsc 编译对比

3.4.3 增量编译原理与实践

3.4.4 Monorepo 类型管理策略

四、框架集成实践

4.1 React 生态集成

4.1.1 组件 Props 高级类型设计

4.1.2 Hooks 泛型类型定义

4.1.3 Redux Toolkit 类型安全

4.1.4 React Router 类型守卫

4.2 Vue 3 类型系统

4.2.1 Composition API 类型推导

4.2.2 组件泛型 Props 实现

4.2.3 Pinia 状态管理类型安全

4.2.4 Volar 类型增强实践

4.3 Node.js 后端开发

4.3.1 Express/Koa 中间件类型

4.3.2 ORM 框架类型安全查询

4.3.3 RPC 通信类型契约

4.3.4 分布式事务类型保障

五、性能优化

5.1 编译性能优化

5.1.1 项目引用(Project References)

5.1.2 增量编译原理剖析

5.1.3 分布式编译方案

5.1.4 编译器缓存策略

5.2 类型性能优化

5.2.1 避免过度类型实例化

5.2.2 条件类型短路技巧

5.2.3 类型缓存策略实现

5.2.4 类型复杂度分析

5.3 产物优化

5.3.1 Tree Shaking 原理与实现

5.3.2 代码分割类型保障

5.3.3 运行时类型检查方案

5.3.4 类型擦除与体积控制

六、综合能力

6.1 设计模式类型实现

6.1.1 装饰器元编程类型安全

6.1.2 依赖注入类型推导

6.1.3 策略模式类型映射

6.1.4 状态机类型实现

6.2 调试与重构

6.2.1 复杂类型错误追踪技巧

6.2.2 声明文件调试方法论

6.2.3 遗留系统渐进式迁移

6.2.4 类型安全的重构保障

6.3 前沿架构

6.3.1 微前端类型通信方案

6.3.2 Serverless 函数类型安全

6.3.3 WebAssembly 类型集成

6.3.4 Web Workers 类型通信

6.4 面试专项

6.4.1 类型体操解题思路

6.4.2 编译原理相关问题

6.4.3 框架集成设计问题

6.4.4 性能优化指标分析