专题知识学习: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; } }
工程实践要点
- 优先使用类型守卫:比类型断言更安全(运行时验证)
- 避免过度断言:过度使用 as 会弱化类型检查
- 守卫函数复用:提取复杂判断逻辑为可复用守卫
- 可辨识联合设计:适合管理复杂状态机类型
// 状态机类型守卫示例 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); // 🚫 编译错误:类型不兼容
工程实践要点
- 优先使用接口而非类:接口更符合结构化类型哲学(轻量级契约)
- 避免过度匹配问题:通过最小化接口属性减少意外兼容
- 利用类型推导优势:
// 自动推导返回类型符合结构 function createPoint(x: number, y: number) { return { x, y, z: 0 }; // 自动兼容 Vector2D } - 鸭子类型测试策略:
// 运行时验证对象是否满足结构 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;
}
工程实践要点
- 优先显式声明重要属性:避免过度依赖索引签名
- 只读属性用于不变数据:如配置ID、版本号等
- 可选属性替代空值:比 property: null 更语义化
- 索引签名边界控制:
// 精确控制动态属性类型 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)
特性:
- 通过类名直接访问
- 不可通过实例访问
- 可继承
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"
工程实践要点
- 最小化公开接口:
class SafeContainer { private internalData: string[] = []; public addItem(item: string) { this.validate(item); this.internalData.push(item); } private validate(item: string) { ... } } - 使用protected扩展点:
class PluginBase { protected coreLogic() { ... } } class MyPlugin extends PluginBase { run() { this.coreLogic(); // 子类可访问 } } - 避免过度继承(组合优于继承):
// 使用组合替代深层继承 class Engine { ... } class Wheels { ... } class Car { private engine = new Engine(); private wheels = new Wheels(); }
1.2.3 抽象类与接口实现规范
抽象类(Abstract Classes)
核心特性:
- 不能直接实例化
- 包含抽象方法(无实现)
- 可包含具体实现方法
- 通过 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)
核心特性:
- 使用 implements 关键字
- 必须实现所有接口成员
- 类可同时实现多个接口
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];
}
}
实现规范与最佳实践
- 接口隔离原则:
// 拆分大接口 interface Printer { print(document: Document): void; } interface Scanner { scan(): Document; } class AllInOnePrinter implements Printer, Scanner { // 分别实现小接口 } - 抽象类作为模板:
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"); } } - 接口扩展抽象类:
abstract class Component { abstract render(): HTMLElement; } interface Stateful { setState(state: object): void; } class Button extends Component implements Stateful { render() { return document.createElement("button"); } setState() { /*...*/ } } - 避免空实现(违反接口契约):
// 反模式:空实现 class EmptyLogger implements Loggable { log() {} // 🚫 违反接口语义 } // 正确:抛出明确错误 class NotImplementedLogger implements Loggable { log() { throw new Error("Method not implemented"); } }
类型检查要点
- 实现兼容性:
interface ClockInterface { currentTime: Date; setTime(d: Date): void; } class Clock implements ClockInterface { currentTime: Date = new Date(); // ✅ 参数类型兼容(Date → Date) setTime(d: Date) { this.currentTime = d; } } - 可选接口成员:
interface Config { required: string; optional?: number; // 可选属性 } class AppConfig implements Config { required = "default"; // 无需实现 optional } - 只读接口属性:
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)
核心特性:
- 使用 static 关键字声明
- 通过类名直接访问(而非实例)
- 不可访问实例成员
- 可继承(子类可通过 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..."
高级构造签名模式
- 泛型构造签名
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" - 构造函数类型别名
// 构造函数类型定义 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"
工程实践要点
- 避免过度使用静态成员:静态成员本质是全局状态,不利于测试和扩展
- 构造签名用于依赖注入:
// 依赖注入容器 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"); - 静态成员类型安全:
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"); // 🚫 错误!无此类型 - 构造签名约束:
// 要求构造函数具有特定静态属性 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 函数类型定义与上下文推断
函数类型定义方式
- 函数声明式
// 完整类型注解 function add(x: number, y: number): number { return x + y; } - 函数表达式
// 变量类型注解 const multiply: (x: number, y: number) => number = function(x, y) { return x * y; }; - 箭头函数
// 类型注解在变量上 const divide: (dividend: number, divisor: number) => number = (a, b) => a / b; - 对象方法
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"; // 🚫 错误
工程实践要点
- 优先使用上下文推断:
// 推荐:让TS自动推断 [1, 2, 3].map(num => num * 2); // 不推荐:冗余注解 [1, 2, 3].map((num: number): number => num * 2); - 回调函数参数命名约定:
// 使用描述性参数名增强可读性 fetchUser(userId => { console.log(userId); // 比 `id` 更明确 }); - 避免any污染函数链:
// 危险:any类型传播 const dangerous: (input: any) => number = input => input.length; // 安全:明确类型约束 const safe: (input: string | any[]) => number = input => input.length; - 函数重载优先于联合参数:
// 不推荐:联合类型参数 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
重载规则与特性
- 优先级规则:
- 从上到下匹配第一个符合的重载签名
- 实现签名不可直接调用(对外不可见)
- 参数类型兼容:
// 正确:实现签名必须兼容所有重载 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参数处理 - 返回值类型约束:
// 返回值必须覆盖所有重载 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;
}
最佳实践指南
- 控制重载数量:
- 建议不超过 5 个重载签名
- 过多重载会导致类型系统负担增加
- 公共前置处理:
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 { /* 特定处理 */ } } - 使用类型参数减少重载:
// 使用泛型替代多个重载 function identity<T>(arg: T): T { return arg; } // 比以下重载更简洁: // function identity(arg: string): string; // function identity(arg: number): number; // ... - 文档注释规范:
/** * 创建用户 * @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 }>
最佳实践指南
- 避免过度约束:
// 过度约束:限制为特定类型 function process<T extends string | number>(input: T): T { ... } // 推荐:更灵活的约束 function process<T>(input: T): T { ... } - 合理使用默认类型:
// 为常用类型提供默认值 type Formatter<T = string> = (input: T) => string; const dateFormatter: Formatter<Date> = date => date.toISOString(); const defaultFormatter: Formatter = str => str.toUpperCase(); // 默认 string - 约束文档化:
/** * 合并两个对象 * @template T - 第一个对象的类型 * @template U - 第二个对象的类型(必须与T兼容) */ function merge<T, U extends T>(obj1: T, obj2: U): T & U { return { ...obj1, ...obj2 }; } - 类型参数命名约定:
// 推荐:使用有意义的类型参数名 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]
最佳实践指南
- 避免过度泛型化:
// 不推荐:不必要的泛型 class Printer<T> { print(value: T) { console.log(value); } } // 推荐:直接使用具体类型 class ConsolePrinter { print(value: any) { console.log(value); } } - 提供有意义的类型参数名:
// 推荐 interface Repository<EntityType> { save(entity: EntityType): void; } // 不推荐 interface Repository<T> { save(e: T): void; } - 默认类型用于通用场景:
// 通用缓存默认存储对象 class Cache<T = object> { private storage = new Map<string, T>(); // ... } // 使用默认类型 const objectCache = new Cache(); - 约束文档化:
/** * 排序服务 * @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 ); } } - 类与接口协同:
// 定义泛型接口 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 模块
- 默认导入:
// 导入整个CommonJS模块 import express from 'express'; const app = express(); - 具名导入:
// 导入特定属性(需模块支持) import { readFile } from 'fs'; readFile('file.txt', 'utf8', () => {}); - 动态导入:
// 异步加载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 模块
- 默认导入:
// CommonJS 环境 const esModule = require('./es-module').default; // 必须访问.default esModule.someMethod(); - 具名导入:
// 解构具名导出 const { namedExport } = require('./es-module'); - 异步加载:
// 在支持top-level await的环境 const { default: esModule } = await import('./es-module');
互操作中的类型处理
- CommonJS 模块类型声明:
// 标准声明 declare module 'cjs-module' { function someFunction(): void; const someValue: number; export = { someFunction, someValue }; } // 使用 import cjs from 'cjs-module'; cjs.someFunction(); - 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(); // 访问具名导出 - 兼容类型声明:
// 同时支持两种导入方式 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; // ✅ 正确
工程最佳实践
- 统一模块格式:
// tsconfig.json { "compilerOptions": { "module": "ESNext", // 输出ESM "moduleResolution": "Node" // 支持Node解析规则 } } - 条件导出(package.json):
{ "exports": { "import": "./dist/esm/index.js", // ESM入口 "require": "./dist/cjs/index.js", // CommonJS入口 "default": "./dist/cjs/index.js" // 回退 } } - 双格式发布:
my-package/ ├── dist/ │ ├── esm/ // ES模块版本 │ │ └── index.js │ └── cjs/ // CommonJS版本 │ └── index.js ├── package.json - 互操作封装层:
// 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 环境互操作
- .mjs 与 .cjs 扩展名:
// ES模块 (module.mjs) export function hello() {} // CommonJS导入 (script.cjs) const { hello } = require('./module.mjs'); // ❌ Node不允许 const esm = import('./module.mjs'); // ✅ 必须使用动态导入 - package.json type 字段:
{ "type": "module", // 所有.js文件视为ES模块 "type": "commonjs" // 默认值 } - 文件扩展名规则:
文件扩展名 模块类型 导入方式 .js 由type字段决定 根据type字段处理 .mjs ES模块 只能import导入 .cjs CommonJS 只能require导入
浏览器环境互操作
- 脚本类型声明:
<!-- 加载ES模块 --> <script type="module" src="app.js"></script> <!-- 加载CommonJS模块(需打包转换) --> <script src="cjs-bundle.js"></script> - 动态导入兼容:
// 浏览器中的动态导入 if (typeof module === 'object') { // Node环境 const mod = require('./cjs-module'); } else { // 浏览器环境 import('./es-module.js').then(mod => {}); }
互操作类型安全策略
- 模块扩充声明:
// 扩充CommonJS模块类型 declare module 'fs' { export function customMethod(): void; } // 使用 import fs from 'fs'; fs.customMethod(); // ✅ 类型安全 - 条件类型处理:
// 区分模块类型 type ModuleType<T> = T extends { __esModule: true } ? { default: infer U } : T; function importCompat<T>(module: T): ModuleType<T> { // 实现互操作转换 } - 运行时类型守卫:
// 检测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文件 |
| 全局污染 | 可能污染全局命名空间 | 无全局污染 |
使用场景指南
命名空间适用场景:
- 浏览器环境下的旧项目维护
- 需要全局可访问的库类型声明(如window.myLib)
- 将大型库拆分为多个文件但仍需共享作用域
// 多文件命名空间 // 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) { /*...*/ } } }
现代模块适用场景:
- 新项目开发
- 需要代码分割的应用
- 组件化架构(React/Vue)
- Node.js 服务端应用
- 需要 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 { /*...*/ }
最佳实践指南
- 新项目强制使用模块:
// tsconfig.json { "compilerOptions": { "module": "ESNext", "moduleResolution": "NodeNext" } } - 旧项目渐进迁移:
- 新文件使用模块语法
- 旧文件保持命名空间
- 使用export as namespace提供全局访问
// legacy.d.ts export = MyLib; export as namespace myLib;
- 避免全局扩展污染:
// 不推荐:污染全局命名空间 declare global { interface Window { myGlobalVar: any; } } // 推荐:模块封装 // my-module.ts export function init() { window.myGlobalVar = { /*...*/ }; } - 类型声明策略:
// 现代模块类型声明 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;
}
工程最佳实践
- 基础路径配置:
{ "compilerOptions": { "baseUrl": "src", // 所有非绝对路径相对src解析 } } // 使用 import utils from 'utils'; // 解析为 src/utils.ts - 多路径映射模式:
{ "paths": { // 多路径回退 "@services/*": [ "services/v2/*", // 优先尝试v2 "services/v1/*" // 回退到v1 ] } } - 通配符扩展:
{ "paths": { "@assets/*": ["assets/*", "assets/images/*"] // 多目录映射 } } - 绝对路径映射:
{ "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'
解决方案:
- 重构提取公共模块
- 使用依赖注入
- 动态导入打破循环
// 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;
}
高级路径映射技术
- 条件路径映射:
{ "paths": { "config": [ "config/production", // 生产环境 "config/development" // 开发环境 ] } } - 多项目共享映射:
// 基座项目 tsconfig.base.json { "compilerOptions": { "paths": { "@shared/*": ["../shared/*"] } } } // 子项目 tsconfig.json { "extends": "../tsconfig.base.json", "compilerOptions": { "baseUrl": "src" } } - 路径映射重定向:
{ "paths": { "old-module": ["new-module"] // 重定向旧路径 } }
路径映射规范指南
统一前缀约定:
@app/ // 应用核心代码 @lib/ // 公共库 @assets/ // 静态资源 @test/ // 测试工具禁止深层相对路径:
// 禁用 import util from '../../../../utils'; // 启用 import util from '@core/utils';映射路径长度限制:
// 优先使用短路径 { "paths": { "@c": ["components"] // 不推荐:过于简短 } }文档化映射规则:
- 路径映射规范:
映射路径 实际路径 说明 @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)); // 类型安全访问
});
代码分割类型声明
- 模块声明文件:
// math.module.d.ts declare const mathModule: { add: (a: number, b: number) => number; PI: number; }; export default mathModule; - 动态模块类型:
// 使用泛型定义返回类型 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 动态组件类型安全
- React.lazy 基础用法:
const LazyComponent = React.lazy(() => import('./HeavyComponent')); function App() { return ( <React.Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </React.Suspense> ); } - 带属性的组件类型:
// 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 异步组件类型
- defineAsyncComponent:
import { defineAsyncComponent } from 'vue'; const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue') ); // 使用 <template> <AsyncComponent :count="5" /> </template> - 类型推断组件属性:
// 声明组件类型 interface AsyncComponentProps { count: number; } const AsyncComponent = defineAsyncComponent({ loader: () => import('./AsyncComponent.vue'), loadingComponent: LoadingSpinner, delay: 200, }) as DefineComponent<AsyncComponentProps>;
代码分割模式
- 路由级分割:
// React Router v6 const router = createBrowserRouter([ { path: '/dashboard', element: ( <React.Suspense fallback={<Spinner />}> {import('./Dashboard')} </React.Suspense> ) }, { path: '/reports', lazy: () => import('./Reports') // 自动处理Suspense } ]); - 功能级分割:
// 按需加载工具库 function processData(data: any[]) { if (data.length > 1000) { import('./heavy-utils').then(({ sortBigData }) => { sortBigData(data); }); } } - 条件分割:
// 根据设备加载不同模块 const loadEditor = () => { if (isMobileDevice()) { return import('./mobile-editor'); } return import('./desktop-editor'); };
类型安全策略
- 动态导入泛型封装:
// 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)); - 模块类型验证:
// 运行时类型验证 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; }; - 类型断言辅助:
// 创建类型断言函数 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 魔法注释
- 基础注释:
// 命名代码块 import(/* webpackChunkName: "math" */ './math'); // 预获取 import(/* webpackPrefetch: true */ './prefetch-module'); // 预加载 import(/* webpackPreload: true */ './critical-module'); - 组合使用:
// 生产环境启用预取 const isProd = process.env.NODE_ENV === 'production'; const comments = isProd ? 'webpackPrefetch: true' : ''; const mod = import(/* webpackChunkName: "utils", ${comments} */ './utils');
最佳实践指南
- 代码分割策略:
分割粒度 适合场景 示例 路由级 SPA应用 lazy(() => import('./page'))组件级 大型组件/三方组件 const Heavy = lazy(() => import(...))库级 第三方大体积库 import('lodash/debounce')功能级 非首屏关键功能 点击后加载编辑器模块 - 加载状态管理:
// 自定义加载状态 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) ); - 错误边界处理:
// 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>
性能优化技巧
- 预加载关键模块:
// 首屏渲染后预加载 useEffect(() => { import('./next-page').catch(console.error); }, []); - 批量加载策略:
// 同时加载多个模块 const loadDashboardModules = () => Promise.all([ import('./chart'), import('./stats'), import('./recent-activity') ]); // 使用 loadDashboardModules().then(([chart, stats, activity]) => { chart.render(); stats.update(); }); - 请求合并:
// 使用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 内置工具类型通过条件类型和映射类型实现
基础工具类型
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 }
Required<T>:将类型 T 的所有属性变为必填type Required<T> = { [P in keyof T]-?: T[P]; };- 实现原理:映射类型 + -? 移除可选修饰符
- 示例:
type RequiredUser = Required<PartialUser>; // { name: string; age: number }
Readonly<T>:将类型 T 的所有属性变为只读type Readonly<T> = { readonly [P in keyof T]: T[P]; };- 实现原理:映射类型 + readonly 修饰符
- 示例:
type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number }
结构操作工具类型
Pick<T, K extends keyof T>: 从类型 T 中选取指定属性 Ktype Pick<T, K extends keyof T> = { [P in K]: T[P]; };- 实现原理:映射类型 + 键名约束
- 示例:
type NameOnly = Pick<User, 'name'>; // { name: string }
Omit<T, K extends keyof any>: 从类型 T 中排除指定属性 Ktype 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 }
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 }
类型操作工具类型
Exclude<T, U>: 从 T 中排除可分配给 U 的类型type Exclude<T, U> = T extends U ? never : T;- 实现原理:条件类型 + never
- 示例:
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
Extract<T, U>: 从 T 中提取可分配给 U 的类型type Extract<T, U> = T extends U ? T : never;- 实现原理:条件类型反向操作
- 示例:
type T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
NonNullable<T>: 从 T 中排除 null 和 undefinedtype NonNullable<T> = T extends null | undefined ? never : T;- 实现原理:条件类型过滤
- 示例:
type T2 = NonNullable<string | null | undefined>; // string
函数操作工具类型
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]
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
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]
高级工具类型
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
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
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
源码设计模式分析
- 分布式条件类型
- 当泛型参数为联合类型时,条件类型会分布式应用:
// 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') - 类型递归模式
- 处理嵌套结构的关键技术:
// 内置工具类型中的递归 type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]; } - 类型推断优先级
- 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;
工程实践要点
- 避免过度嵌套:
// 危险:深层嵌套可能导致类型实例化过深错误 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; - 性能敏感场景优化:
// 使用内置工具类型替代自定义复杂类型 // 不推荐: type MyPartial<T> = { [P in keyof T]?: T[P] }; // 推荐:直接使用 Partial - 类型安全检查:
// 确保工具类型安全 type SafeExtract<T, U> = [T] extends [U] ? T : never; // 防止分布式行为 type DistributiveCheck<T> = [T] extends [never] ? true : false; - 源码调试技巧:
// 使用类型打印调试 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 } |
核心语法解析
- 添加修饰符(显式+):
// 显式添加只读属性 type AddReadonly<T> = { +readonly [P in keyof T]: T[P]; }; // 显式添加可选属性 type AddOptional<T> = { [P in keyof T]+?: T[P]; }; - 移除修饰符(-):
// 移除只读属性 type RemoveReadonly<T> = { -readonly [P in keyof T]: T[P]; }; // 移除可选属性 type RemoveOptional<T> = { [P in keyof T]-?: T[P]; };
实现原理分析
- 修饰符的编译转换:
// 源码: type Mutable<T> = { -readonly [P in keyof T]: T[P]; }; // 编译后等价于: type Mutable<T> = { [P in keyof T]: T[P]; }; - 类型系统内部处理:
+操作:添加属性描述符标志-操作:清除属性描述符标志- 映射过程本质是创建新属性描述符
内置工具类型应用
Required<T>实现:type Required<T> = { [P in keyof T]-?: T[P]; };Readonly<T>实现:type Readonly<T> = { +readonly [P in keyof T]: T[P]; };Mutable<T>(非内置但常用):type Mutable<T> = { -readonly [P in keyof T]: T[P]; };
组合修饰符技巧
- 同时添加/移除多个修饰符:
// 创建可变且必填的类型 type MutableRequired<T> = { -readonly [P in keyof T]-?: T[P]; }; // 等价于: type MutableRequired<T> = Mutable<Required<T>>; - 条件修饰符:
// 仅对函数属性添加只读 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]; };
工程实践指南
- 不可变状态管理:
// 创建深度只读状态 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"; // 🚫 编译错误 - 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 一定存在 } - 安全补丁策略:
// 创建部分更新类型 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" // ✅ };
性能优化技巧
- 避免深度递归:
// 限制递归深度 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; - 条件短路优化:
type OptimizedMutable<T> = T extends readonly any[] ? Array<OptimizedMutable<T[number]>> : T extends object ? { -readonly [P in keyof T]: OptimizedMutable<T[P]> } : T;
编译原理透视
- 修饰符的 AST 表示:
// TypeScript AST 节点示例 interface MappingModifier { +?: boolean; // 添加修饰符 -?: boolean; // 移除修饰符 readonly?: "+" | "-" | null; optional?: "+" | "-" | null; } - 类型实例化流程:
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)
分布式行为解析
- 基础示例:
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" - 非分布式对比:
// 包裹类型参数(非裸类型) type Wrapped<T> = [T] extends [string] ? "string" : "number"; // 非分布式计算 type Result2 = Wrapped<"a" | 1>; // 等价于: // ["a" | 1] extends [string] ? "string" : "number" // → "number"(因为 ["a" | 1] 不继承 [string])
分布式规则详解
- 空联合类型处理:
type Test<T> = T extends string ? true : false; type Result = Test<never>; // never(空联合类型返回never) - 联合类型包含never:
type Test<T> = T extends string ? true : false; type Result = Test<"a" | never>; // true(never被忽略) - 联合类型优先级:
type Test<T> = T extends any ? T : never; type Result = Test<string | number>; // string | number
内置工具类型应用
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"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"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:元组包裹 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; - 特殊类型约束:
// 使用never约束 type IsNever<T> = [T] extends [never] ? true : false; // 使用any约束 type IsAny<T> = 0 extends (1 & T) ? true : false;
高级应用场景
- 联合类型过滤:
// 过滤出函数类型 type FilterFunctions<T> = T extends (...args: any[]) => any ? T : never; type Functions = FilterFunctions<string | (() => void) | number>; // () => void - 类型差异检测:
// 检测类型差异 type Diff<T, U> = T extends U ? never : T; // 使用 type MissingProps = Diff<"name" | "age", keyof User>; // User中缺失的属性 - 递归类型操作:
// 联合类型转元组(简化版) type UnionToTuple<T> = T extends any ? (arg: T) => void : never extends (arg: infer U) => void ? U : never;
分布式与映射类型结合
- 分布式键名重映射:
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 } - 条件键名过滤:
// 仅保留字符串键 type StringKeys<T> = { [K in keyof T]: K extends string ? K : never }[keyof T]; // 使用 type Keys = StringKeys<{ 1: number; "name": string }>; // "name"
编译原理透视
- 分布式处理流程:
1. 解析条件类型:T extends U ? X : Y 2. 检查T是否为裸类型参数的联合类型 3. 若是,对联合类型每个成员进行独立计算 4. 将计算结果组合为新联合类型 - 类型实例化优化:
- TS编译器对分布式条件类型进行缓存优化
- 相同类型参数的计算结果会被复用
- 避免重复计算提升性能
工程实践指南
- 明确设计意图:
// 需要分发时 type Distributed<T> = T extends any ? T[] : never; // 不需要分发时 type NonDistributed<T> = [T] extends [any] ? T[] : never; - 文档注释规范:
/** * 提取数组元素类型(分发模式) * @template T - 联合类型数组 * @example * type Element = ElementType<Array<string> | Array<number>>; // string | number */ type ElementType<T> = T extends (infer U)[] ? U : never; - 性能监控策略:
// 复杂类型性能测试 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"); - 错误处理模式:
// 安全分发类型 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 子句中使用
- 只能出现在协变位置(返回值、属性值等)
基础提取模式
- 提取函数返回类型:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; // 使用 type FnReturn = ReturnType<() => number>; // number - 提取函数参数类型:
type FirstParam<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never; // 使用 type Param = FirstParam<(a: string, b: number) => void>; // string - 提取数组元素类型:
type ArrayElement<T> = T extends (infer U)[] ? U : never; // 使用 type Element = ArrayElement<string[]>; // string
高级提取技术
- 递归类型解包:
// 解包嵌套Promise type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T; // 使用 type Result = Awaited<Promise<Promise<string>>>; // string - 多个 infer 变量:
// 提取函数参数元组 type Params<T> = T extends (...args: infer P) => any ? P : never; // 使用 type FnParams = Params<(a: string, b: number) => void>; // [string, number] - 逆变位置提取:
// 提取函数this类型 type ThisType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown; // 使用 function fn(this: Window) {} type Context = ThisType<typeof fn>; // Window
模式匹配技术
- 字符串模板提取:
// 提取模板字符串变量 type ExtractVariable<T> = T extends `user_${infer Id}` ? Id : never; // 使用 type UserId = ExtractVariable<"user_123">; // "123" - 对象键值提取:
// 提取特定键的值类型 type ValueOfKey<T, K extends string> = T extends { [key in K]: infer V } ? V : never; // 使用 type NameType = ValueOfKey<{ name: string; age: number }, "name">; // string - 条件分支提取:
// 提取满足条件的子类型 type ExtractByType<T, U> = T extends U ? T : never; // 使用 type Strings = ExtractByType<"a" | 1 | true, string>; // "a"
内置工具类型解析
Parameters<T>实现:type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;ReturnType<T>实现:type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;ConstructorParameters<T>实现:type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
工程应用模式
- 组件 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 } - 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 } - 路由参数提取:
// 提取动态路由参数 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"
编译原理透视
- infer 实现机制:
- 类型系统实现模式匹配
- 在类型关系求解时绑定类型变量
- 基于约束的类型变量实例化
- 类型推导过程:
1. 匹配条件类型结构 2. 提取候选类型 3. 绑定 infer 变量 4. 验证类型约束 5. 实例化结果类型
最佳实践指南
- 类型变量命名规范:
// 推荐:描述性命名 type ExtractReturn<T> = T extends (...args: any) => infer ReturnType ? ReturnType : never; // 避免:单字母命名 type Bad<T> = T extends (...a: any) => infer R ? R : never; - 错误处理模式:
// 安全提取模式 type SafeExtract<T, Fallback = never> = T extends { [key: string]: infer V } ? V : Fallback; // 使用 type Value = SafeExtract<string>; // never - 复杂类型分解:
// 分解复杂提取逻辑 type Step1<T> = /* ... */; type Step2<T> = Step1<T> extends infer U ? /* ... */ : never; type Final<T> = Step2<T> extends infer V ? /* ... */ : never; - 类型测试策略:
// 使用类型断言验证 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)
);
面试级考点
- 类型收窄原理:
// 利用可辨识联合(discriminated union) if (action.type === 'INCREMENT') { // 此处 action 自动收窄为 { type: 'INCREMENT'; payload: number } } - 类型安全与性能平衡:
// ❌ 过度泛化 type Action<T = any> = { type: string; payload: T }; // ✅ 精确控制 type StrictAction = { type: 'A'; payload: number } | { type: 'B'; payload: string }; - Redux 中间件类型:
interface MiddlewareAPI<D extends Dispatch, S> { dispatch: D; getState(): S; } type Middleware = <S>(api: MiddlewareAPI<Dispatch, S>) => (next: Dispatch) => (action: any) => any; - 时间旅行调试类型:
type TimeTravelState = { past: State[]; present: State; future: State[]; }; - 终极挑战:实现类型安全的 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 路由参数自动类型推导
核心挑战
- 路径解析:将字符串路径(如 /user/:id/posts/:postId)转换为类型结构
- 参数提取:识别动态段(:param)和可选段(?)
- 类型转换:将参数名映射为对象键({ id: string; postId: string })
- 嵌套路由:支持多层路由参数合并
基础路径参数推导
场景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)
面试级考点
- 模板字面量类型操作:
type SplitPath<P> = P extends `/${infer Part}/${infer Rest}` ? [Part, ...SplitPath<`/${Rest}`>] : P extends `/${infer Part}` ? [Part] : []; - 类型递归限制:TS 4.5+ 支持尾递归优化
- React Router v6 类型实现:
// 官方ParamKey类型定义 type ParamKey<TPath> = TPath extends `${infer _}:${infer Param}/${infer Rest}` ? Param | ParamKey<Rest> : TPath extends `${infer _}:${infer Param}` ? Param : never; - Vue Router 对比:
// Vue Router的RouteRecordRaw类型 interface RouteRecordRaw { path: string; component: Component; children?: RouteRecordRaw[]; } - 终极挑战:实现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 |
配置详解与工程实践
- noImplicitAny
- 问题代码:
function log(message) { // 🚫 参数隐式 any console.log(message); } - 解决方案:
function log(message: string) { // ✅ 显式声明类型 console.log(message); }
- 问题代码:
- strictNullChecks
- 问题代码:
const element = document.getElementById("app"); element.innerHTML = "Hello"; // 🚫 可能为 null - 解决方案:
const element = document.getElementById("app"); if (element) { // ✅ 安全检查 element.innerHTML = "Hello"; }
- 问题代码:
- strictPropertyInitialization
- 问题代码:
class User { name: string; // 🚫 未初始化 } - 解决方案:
class User { name!: string; // ✅ 明确赋值断言 // 或 constructor(public name: string) {} }
- 问题代码:
- useUnknownInCatchVariables
- 问题代码:
try { /* ... */ } catch (err) { // any 类型 console.log(err.message); // 🚫 可能不存在 } - 解决方案:
try { /* ... */ } catch (err: unknown) { // ✅ TS 4.4+ if (err instanceof Error) { console.log(err.message); } }
- 问题代码:
四、严格模式配置策略
- 渐进启用策略:
{ "compilerOptions": { "strict": true, "noImplicitAny": false // 临时关闭最严格的规则 } } - 文件级覆盖:
// @ts-nocheck // 禁用整个文件检查 // @ts-ignore // 禁用下一行检查 function legacyCode() { // @ts-expect-error // 预期错误(TS 3.9+) const x: number = "string"; } - 目录差异化配置:
// 根目录 tsconfig.json { "extends": "./tsconfig.base.json", "references": [ { "path": "./src" }, // 应用严格模式 { "path": "./legacy" } // 非严格模式 ] } // legacy/tsconfig.json { "extends": "../tsconfig.base.json", "compilerOptions": { "strict": false } }
性能与严格模式
- 编译速度影响:
严格选项 性能影响 缓解措施 strictNullChecks 中 项目引用隔离修改范围 noImplicitAny 高 渐进启用 + @ts-ignore 临时方案 strictFunctionTypes 低 无需特别处理 - 内存占用优化:
{ "compilerOptions": { "strict": true, "incremental": true, // 增量编译 "tsBuildInfoFile": "./buildcache/.tsbuildinfo" // 缓存位置 } }
严格模式错误处理
- 常见错误类型:
错误代码 含义 解决方案 TS7006 参数隐式 any 添加类型注解 TS2532 对象可能为 undefined 添加空值检查 TS2564 属性未初始化 构造函数初始化或明确赋值断言 TS2345 参数类型不匹配 修正参数类型或使用类型断言 - 错误自动修复:
// .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’):
- 检查当前目录的 node_modules/module.ts/module.tsx/module.d.ts
- 检查 node_modules/module/package.json 的 types 或 main 字段
- 检查 node_modules/module/index.ts/index.d.ts
- 递归检查父目录的 node_modules(../node_modules/module)
文件扩展名解析顺序:
[
".ts", ".tsx", ".d.ts", // TypeScript 文件
".js", ".jsx", ".cjs", ".mjs", // JavaScript 文件
".json" // JSON 文件
]
Node16/NodeNext 策略
ESM 专属特性:
- 支持 package.json 的 exports 和 imports 字段
- 严格区分 .js (CommonJS) 和 .mjs (ESM) 文件
- 支持 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" // 开发环境
]
}
}
}
路径映射实战
- 别名配置:
{ "baseUrl": ".", "paths": { "@app/*": ["src/*"], "shared/*": ["../shared/*"] } } - 多路径回退:
{ "paths": { "legacy-utils": [ "src/utils/v2", // 优先使用 v2 "src/utils/v1" // 回退到 v1 ] } } - 通配符扩展:
{ "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);
构建工具集成
- Webpack 配置:
// webpack.config.js const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); module.exports = { resolve: { plugins: [new TsconfigPathsPlugin()], extensions: ['.ts', '.tsx', '.js'] } }; - 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 '第三方库名' {
// 扩展内容
}
常见扩展场景
- 添加缺失类型:
declare module 'untyped-lib' { export function calculate(a: number, b: number): number; export const version: string; } - 扩展现有接口:
import 'react'; declare module 'react' { interface HTMLAttributes<T> { // 添加自定义属性 customAttr?: string; } } // 使用 <div customAttr="value" />; // ✅ 类型安全 - 覆盖错误类型:
declare module 'flawed-lib' { // 修正错误返回类型 export function fetchData(): Promise<Data>; // 原声明为 any }
模块合并策略
- 接口合并:
// 原始声明 interface Config { timeout: number; } // 扩展声明 declare module 'configurable-lib' { interface Config { retries?: number; // 添加可选属性 } } - 函数重载扩展:
// 原始函数 declare function log(message: string): void; // 扩展重载 declare module 'logger-lib' { function log(data: object): void; // 添加对象支持 } - 命名空间合并:
// 原始命名空间 declare namespace Analytics { function track(event: string): void; } // 扩展命名空间 declare module 'analytics-lib' { namespace Analytics { function identify(userId: string): void; // 添加新方法 } }
类型扩展最佳实践
- 模块化扩展文件:
src/ types/ extensions/ react.d.ts # React扩展 lodash.d.ts # Lodash扩展 global.d.ts # 全局扩展 tsconfig.json - 条件扩展模式:
// 根据环境变量扩展 type Env = 'development' | 'production'; declare module 'configurable-lib' { interface Config { debug?: Env extends 'development' ? boolean : never; } } - 类型安全增强:
// 添加严格类型约束 declare module 'flexible-lib' { function parse(input: string): unknown; function parse<T>(input: string): T; // 泛型版本 }
高级扩展技巧
- 模板字面量类型扩展:
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>; } } - 泛型约束增强:
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; } } - 类型谓词扩展:
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 生态扩展
- 组件属性扩展:
declare module 'react-table' { export interface ColumnInstance<D extends object = {}> { align?: 'left' | 'center' | 'right'; // 添加对齐属性 } } - Hooks 返回值扩展:
declare module 'react-query' { interface QueryResult<T> { customMeta?: { timestamp: number }; // 添加元数据 } } - Context 类型增强:
import { ThemeContext } from 'theme-provider'; declare module 'theme-provider' { interface Theme { customColor: string; // 扩展主题对象 } } // 使用 const theme = useContext(ThemeContext); theme.customColor; // ✅ 类型安全
扩展测试策略
- 类型测试:
// 验证扩展是否生效 type Test = Expect< // 检查扩展属性是否存在 'customAttr' extends keyof React.HTMLAttributes<any> ? true : false >; - 运行时验证:
// 确保扩展与运行时兼容 if ('newMethod' in lib) { lib.newMethod(); } else { console.warn('Library extension not available'); }
工程化建议
- 扩展文档化:
## UI 库扩展说明 ### 新增属性 | 属性名 | 类型 | 说明 | |---------------|--------|--------------------| | customAttr | string | 自定义HTML属性 | | __extendedVariant | 'tertiary' | 特殊按钮类型 | - 版本管理策略:
// package.json { "name": "@types/custom-lib-extension", "version": "1.0.0", "peerDependencies": { "target-lib": "^2.0.0" } } - 扩展发布流程:
graph LR A[创建扩展声明] --> B[本地测试验证] B --> C[发布到内部npm] C --> D[项目安装使用]
扩展安全指南
- 避免全局污染:
// 错误:全局扩展基础类型 interface Array<T> { customMethod(): void; } // 正确:模块化扩展 declare module 'array-extension' { interface Array<T> { safeCustomMethod(): void; } } - 防御性类型检查:
// 安全扩展函数 declare module 'utils-lib' { function parse(input: unknown): unknown; function parse<T>(input: T): T extends string ? Data : never; } - 兼容性处理:
// 处理不同版本的库 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 // ✅ 合并属性
};
模块声明合并策略
- 接口合并:
// 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 // ✅ }; - 函数合并:
// 原始声明 declare module 'utils' { function parse(input: string): any; } // 扩展声明 declare module 'utils' { function parse<T>(input: string): T; // 泛型版本 } // 使用 const data = parse<{ id: string }>('{"id":"123"}'); // ✅ 类型安全 - 命名空间合并:
// 原始命名空间 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 } → 错误 |
| 函数重载 | 后声明优先 | 最后声明的签名最先匹配 |
| 命名空间 | 按声明顺序合并 | 后声明的成员覆盖先前的 |
| 类与接口 | 接口声明优先 | 类实现必须满足所有接口声明 |
模块合并最佳实践
- 分层声明结构:
types/ ├── lib/ │ ├── base.d.ts // 基础声明 │ └── extensions/ // 扩展声明 │ ├── v1.d.ts │ └── v2.d.ts └── global.d.ts // 全局合并 - 版本化合并策略:
// 条件合并 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; } } - 安全覆盖模式:
declare module 'sensitive-lib' { interface __OriginalConfig { // 保留原始定义 originalProp: string; } interface Config extends __OriginalConfig { // 安全扩展 customProp?: number; } }
React 组件合并案例
- 组件属性合并:
// 原始组件声明 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" />; // ✅ - 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;