Article

TypeScript类型系统

更新于:2026-07-11

一、JavaScript vs TypeScript 详细对比表

特性 / 维度JavaScriptTypeScript
类型系统动态类型(运行时检查)静态类型(编译时检查)
类型注解支持 : type 语法
类型推断有限强大,可自动推导类型
接口(Interface)支持
泛型(Generics)支持
枚举(Enum)无(需用对象模拟)原生支持
类继承增强基础继承支持 public / private / protected 等修饰符
编译步骤直接运行需要编译为 JS
错误发现运行时编译时
工具支持一般极佳(智能提示、重构)
学习曲线中等(需学类型系统)
适用场景小型项目、脚本、原型中大型项目、团队协作

二、TypeScript 类型系统

1. 类型基础

基本类型: stringnumberbooleannullundefinedsymbolbigint

类型作用代码示例注意事项
string表示文本类型,用于字符串值let name: string = "Alice";区分大小写;可使用单引号、双引号或模板字符串;不可赋值为 nullundefined(除非启用 strictNullChecks: false
number表示所有数字类型(浮点、整数、NaN、Infinity)let age: number = 25; let price: number = 9.99;TypeScript 中所有数字都是 number 类型(无 int/float 区分);支持二进制、八进制、十六进制字面量
boolean表示布尔值:truefalselet isActive: boolean = true;不能将 01"true" 等非布尔值赋给 boolean 类型变量
null表示”无值”,是一个明确的空值let data: null = null;默认情况下 null 是所有类型的子类型(除非启用 strictNullChecks: true);通常用于显式清空变量
undefined表示”未定义”,变量声明但未初始化let name: undefined = undefined; let x; // x 的值为 undefined函数无返回值时返回 undefined;启用 strictNullChecks 后,不能赋给其他类型(如 string
symbol表示唯一且不可变的值,常用于对象属性键const key1 = Symbol("id"); const key2 = Symbol("id"); console.log(key1 === key2); // false每个 Symbol() 调用返回唯一值;不能与其他类型进行运算或隐式转换;用作对象属性时可避免命名冲突
bigint表示任意精度的整数,用于超出 Number.MAX_SAFE_INTEGER 的大整数let bigNum: bigint = 123n; let another = BigInt(100);必须以 n 结尾或使用 BigInt() 构造;不能与 number 类型混合运算;不支持小数

类型注解与类型推断

概念作用代码示例注意事项
类型注解 (Type Annotation)显式指定变量、函数参数或返回值的类型let count: number = 10; function greet(name: string): string { return "Hello, " + name; }提高代码可读性和类型安全;推荐在函数参数和返回值上使用;不要过度标注,让类型推断发挥作用
类型推断 (Type Inference)TypeScript 自动根据赋值推断变量类型let message = "Hello"; // 推断为 string let count = 42; // 推断为 number let items = [1, 2, 3]; // 推断为 number[]初始赋值决定类型;数组混合类型会推断为联合类型(如 [1, "a"](number | string)[]);对于空数组或复杂对象,建议手动标注
联合类型推断当值可能为多种类型时,TS 推断为联合类型let val = Math.random() > 0.5 ? "yes" : 42; // val: string | number后续使用需进行类型收窄(如 typeof 判断);否则无法调用特定类型的方法

anyunknownnever 的区别与使用场景

类型作用代码示例注意事项
any禁用类型检查,允许任意操作let data: any = "hello"; data = 100; data.toFixed(); // 不报错,但可能运行时报错尽量避免使用,破坏类型安全;适用于迁移旧 JS 项目或第三方库无类型定义时;可赋值给任何类型,也可被任何类型赋值
unknown表示未知类型,比 any 更安全let input: unknown; input = "hello"; input = 100; // OK // input.toFixed(); // ❌ 编译错误,必须先类型收窄 if (typeof input === "number") { console.log(input.toFixed(2)); }any 的类型安全替代方案;不能直接操作,必须通过类型守卫(type guard)收窄后使用;推荐用于函数参数、API 返回值等不确定类型场景
never表示永远不会发生的类型,常用于抛出异常或无限循环的函数返回值function throwError(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) {} }never 是所有类型的子类型(可赋值给任何类型);但没有类型是 never 的子类型(反之不行);用于类型收窄的穷尽性检查(exhaustive check)

使用建议:

  • 优先使用 unknown 替代 any
  • never 多用于工具类型和控制流分析

类型断言:as<Type> 语法

语法作用代码示例注意事项
as Type(推荐)将值断言为某个特定类型const input = document.getElementById("name") as HTMLInputElement; input.value = "Alice";JSX 中只能使用 as 语法(<Type> 会被误认为 JSX 标签);不进行运行时检查,仅告诉编译器”我确定它是这个类型”;错误断言可能导致运行时错误
<Type>(旧语法)同上,泛型风格语法const input = <HTMLInputElement>document.getElementById("name"); input.value = "Bob";.ts 文件中可用,但在 .tsx 中不支持;建议统一使用 as 语法以保持一致性
非空断言 !断言某个值不为 nullundefinedlet el: HTMLElement | null = document.getElementById("app"); el!.innerHTML = "Hello"; // 不检查是否为 null仅在确定值存在时使用;否则可能导致 Cannot read property 'xxx' of null
双重断言(不推荐)强制转换类型(先转 any / unknown,再转目标类型)let a = "hello" as any as number; // 危险!绕过类型系统,极易出错;仅在极端兼容场景下使用,应避免

⚠️ 类型断言本质是”类型欺骗”,应谨慎使用,优先考虑类型守卫(如 if (x instanceof Date))或联合类型处理。

总结建议:

最佳实践说明
✅ 使用 unknown 替代 any提高类型安全性
✅ 启用 strictNullChecks避免 null / undefined 意外赋值
✅ 优先使用 as Type 断言语法兼容 JSX,代码更清晰
✅ 避免过度使用类型断言会削弱类型系统的保护作用
✅ 利用类型推断减少冗余注解保持代码简洁

2. 复杂类型

数组与元组(Tuple)

类型作用代码示例注意事项
数组(Array)表示相同类型元素的有序集合let numbers: number[] = [1, 2, 3]; let words: Array<string> = ["a", "b"]; // 泛型写法所有元素必须是同一类型(或联合类型);可使用 pushmap 等方法;Array<T>T[] 等价
元组(Tuple)表示固定长度、类型有序的数组,每个位置可有不同类型let user: [string, number, boolean] = ["Alice", 25, true]; // [name, age, isActive]长度和类型顺序固定;访问越界索引可能返回 undefined 或报错(取决于配置);支持只读元组:readonly [string, number]
越界与可变性元组在 JS 中仍是数组,可 push,但 TS 不检查运行时行为user.push("admin"); // 合法(JS 层面) // user[3] 类型为 string | number | boolean user[1] = 30; // OK user[3] = "extra"; // ❌ 编译错误(若严格模式)TS 仅在编译时检查元组类型;push 后访问新元素会推断为联合类型;推荐配合 readonly 使用防止修改
命名元组(可读性增强)为元组元素添加标签,提升可读性let range: [min: number, max: number] = [0, 100];标签仅用于文档和 IDE 提示,不影响类型检查;仍按索引访问(如 range[0]

对象类型与可选 / 只读属性

概念作用代码示例注意事项
对象类型(匿名)定义对象的结构:属性名与类型let user: { name: string; age: number } = { name: "Bob", age: 30 };属性必须完全匹配(多余或缺少属性会报错);支持嵌套对象类型
可选属性(?表示某个属性可以不存在let config: { url: string; timeout?: number; // 可选 } = { url: "api.example.com" };访问可选属性前应检查是否存在;类型为 T | undefined
只读属性(readonly属性只能在初始化时赋值,之后不可修改let point: { readonly x: number; readonly y: number; } = { x: 10, y: 20 }; // point.x = 30; // ❌ 错误用于定义常量对象、配置项等;与 const 不同:const 限制变量绑定,readonly 限制属性修改
只读数组与元组防止数组被修改let list: readonly string[] = ["a", "b"]; // list.push("c"); // ❌ 错误 let tuple: readonly [number, string] = [1, "a"];ReadonlyArray<T> 等价于 readonly T[];方法如 pushpop 被禁用,但 concatslice 可用

联合类型(Union)与交叉类型(Intersection)

类型作用代码示例注意事项
联合类型(|表示一个值可以是多种类型之一function printId(id: number | string) { console.log("ID: " + id); } printId(101); // OK printId("abc"); // OK只能访问所有类型的共有成员(如 toString());必须使用类型守卫(typeofininstanceof)进行收窄才能调用特定方法
字面量联合类型限制值为几个特定字面量之一type Direction = "left" | "right" | "up" | "down"; let dir: Direction = "left"; // dir = "forward"; // ❌ 错误实现类似枚举的效果;常用于函数参数、状态机等
交叉类型(&将多个类型合并为一个新类型(取所有类型的交集)type Person = { name: string; age: number }; type Employee = { id: number; department: string }; type Staff = Person & Employee; let staff: Staff = { name: "Alice", age: 28, id: 1001, department: "Engineering" };属性冲突时,类型为 never(如 { x: string } & { x: number });用于混入(mixin)或组合多个接口
联合 vs 交叉联合是”或”,交叉是”且”type A = { a: string }; type B = { b: number }; type U = A | B; // 有 a 或 b type I = A & B; // 同时有 a 和 bU 类型变量必须通过类型守卫判断具体类型;I 类型变量必须包含所有属性

类型别名(Type Alias)

概念作用代码示例注意事项
类型别名(type为类型创建一个新名称,提高可读性和复用性type Point = { x: number; y: number }; type ID = string | number; type Callback = (result: boolean) => void; let p: Point = { x: 1, y: 2 };可用于对象、联合、元组、函数等任意类型;不创建新类型,只是别名(等价替换)
泛型类型别名支持泛型参数,增强复用性type Box<T> = { value: T }; let stringBox: Box<string> = { value: "hello" }; let numberBox: Box<number> = { value: 42 };与泛型接口类似,但更轻量;不能被 extendsimplements
联合/交叉组合用于构建复杂类型结构type Status = "loading" | "success" | "error"; type ApiResponse<T> = { status: "loading" };-

与接口(interface)对比:

  • 类型别名更灵活,接口更适合扩展
  • 类型别名可表示原始类型、联合等;接口支持自动合并(declaration merging)
  • 一般建议:对象结构用 interface,复杂组合用 type

总结建议:

最佳实践说明
✅ 使用元组时考虑 readonly防止意外修改
✅ 对象类型优先使用 interfacetype 命名提高可维护性
✅ 联合类型必须配合类型守卫使用避免访问不存在的属性
✅ 交叉类型慎用于属性冲突场景否则可能导致 never
✅ 类型别名用于简化复杂类型如联合、泛型、映射类型等

3. 接口(Interface)

定义对象结构

概念作用代码示例注意事项
基本接口定义描述对象的形状(shape),规定属性名和类型interface User { name: string; age: number; isActive: boolean; } let user: User = { name: "Alice", age: 28, isActive: true };接口是”契约”,变量必须满足所有必需属性;多余属性会报错(除非使用索引签名或类型断言)
可选属性(?指定某些属性可以不存在interface Config { apiUrl: string; timeout?: number; // 可选 retries?: number; } let config: Config = { apiUrl: "https://api.example.com" }; // OK访问可选属性前应检查是否为 undefined;类型为 T | undefined
只读属性(readonly属性只能在创建时赋值,之后不可修改interface Point { readonly x: number; readonly y: number; } let p: Point = { x: 10, y: 20 }; // p.x = 30; // ❌ 编译错误用于定义常量数据结构;与 const 不同:const 控制变量绑定,readonly 控制属性修改

函数类型接口

概念作用代码示例注意事项
接口描述函数使用接口定义函数的参数和返回值类型interface GreetFunction { (name: string): string; } let greet: GreetFunction = function(name: string): string { return "Hello, " + name; };接口没有函数名,只关注调用签名;实现该接口的函数必须匹配参数类型和返回类型
多调用签名一个接口支持多种函数重载形式interface Format { (value: string): string; (value: number, decimals?: number): string; }实现函数需兼容所有签名;常用于库函数的重载设计
与类型别名对比type 也可定义函数类型,语法更灵活type GreetType = (name: string) => string; interface GreetInterface { (name: string): string; }功能几乎等价;接口支持声明合并,类型别名不支持;一般建议:函数类型可用 type 更简洁

可索引类型

概念作用代码示例注意事项
字符串索引签名定义对象可通过字符串键动态访问属性interface StringArray { [index: number]: string; // 数字索引 → 字符串值 } let arr: StringArray = ["a", "b"]; console.log(arr[0]); // "a" interface Dictionary { [key: string]: string; } let dict: Dictionary = { name: "Bob", role: "admin" };支持两种索引类型:stringnumber;若同时存在 stringnumber 索引,number 索引的返回类型必须是 string 索引的子类型
数字索引签名主要用于类数组结构interface NumberDictionary { [index: string]: number; length: number; // OK, length 是 number // name: string; // ❌ 错误!string 不是 number 的子类型 }JS 中数字索引会被转为字符串(obj[0]obj["0"]);因此 number 索引签名实际上也适用字符串键
索引签名限制提高安全性,防止任意属性写入interface SafeObject { [key: string]: string; id: string; // OK // tags: string[]; // ❌ 错误,string[] 不是 string 子类型 }所有明确属性必须符合索引签名的类型;否则会导致类型冲突

接口继承与实现

概念作用代码示例注意事项
接口继承(extends一个接口可以继承一个或多个其他接口interface Person { name: string; age: number; } interface Employee extends Person { employeeId: number; department: string; } let emp: Employee = { name: "Alice", age: 28, employeeId: 1001, department: "Engineering" };支持多重继承:interface Admin extends Person, Permissions;继承后包含所有父接口的成员
类实现接口(implements类必须实现接口中定义的所有成员interface Drawable { draw(): void; } class Circle implements Drawable { draw() { console.log("Drawing a circle"); } }一个类可实现多个接口:class A implements X, Y;实现是”承诺”,确保类具有指定结构
混合继承与实现类可继承父类并实现接口class Animal { constructor(public name: string) {} } interface Flyable { fly(): void; } class Bird extends Animal implements Flyable { fly() { console.log(\${this.name} is flying`); } }`extends 用于类继承(获取父类逻辑);implements 用于接口实现(满足契约);可同时使用
接口合并(Declaration Merging)同名接口会自动合并interface Box { value: string; } interface Box { count: number; } // 等价于: interface Box { value: string; count: number; } let b: Box = { value: "test", count: 1 };是接口独有的特性,type 不支持;常用于扩展第三方库的类型定义

总结建议:

最佳实践说明
✅ 优先使用 interface 描述对象结构更符合面向对象设计
✅ 使用可选和只读属性提高类型安全性明确意图,防止误改
✅ 函数类型接口适用于重载场景支持多调用签名
✅ 利用接口继承构建类型层级实现”is-a”关系
implements 强制类遵循契约提高代码可维护性
✅ 利用声明合并扩展类型特别适合库的类型增强

💡 接口 vs 类型别名(interface vs type)速查表:

场景推荐
定义对象结构interface
需要声明合并interface
定义联合/交叉/原始类型type
泛型高级类型(如映射类型)type
函数类型typeinterface 均可
类实现契约interface

4. 类(Class)增强

访问修饰符:public / private / protected

修饰符作用代码示例注意事项
public默认值,属性或方法可在任何地方访问class Person { public name: string; constructor(name: string) { this.name = name; } public greet() { return "Hello, " + this.name; } } const p = new Person("Alice"); console.log(p.name); // OK p.greet(); // OK所有成员默认为 public;可在类内部、子类、实例上访问
private仅在当前类内部可访问,子类和实例不可访问class BankAccount { private balance: number = 0; private updateLog(amount: number) { console.log(\Balance changed by ${amount}`); } deposit(amount: number) { this.balance += amount; this.updateLog(amount); } } const account = new BankAccount(); // account.balance; // ❌ 编译错误 // account.updateLog(100); // ❌ 错误`TypeScript 层面限制,编译为 JS 后仍可通过 ['balance'] 访问(无运行时保护);用于封装敏感数据或内部逻辑
protected在当前类和子类中可访问,实例不可访问class Vehicle { protected speed: number = 0; protected startEngine() { console.log("Engine started"); } } class Car extends Vehicle { accelerate() { this.speed += 10; // OK this.startEngine(); // OK } } const car = new Car(); // car.speed; // ❌ 错误 // car.startEngine(); // ❌ 错误支持继承链中的受控访问;适合设计框架或基类时使用

⚠️ TypeScript 的访问修饰符仅在编译时检查,JS 运行时无访问控制。如需真正私有,应使用 JS 的 # 私有字段。

只读属性与参数属性

概念作用代码示例注意事项
readonly 属性属性只能在声明或构造函数中赋值,之后不可修改class Person { readonly id: number; readonly name: string = "Unknown"; constructor(id: number, name: string) { this.id = id; this.name = name; // 覆盖默认值 } // someMethod() { // this.id = 100; // ❌ 错误 // } }类似 const,但用于类属性;常用于 ID、配置项等不可变数据
参数属性(Parameter Properties)在构造函数参数前加修饰符,自动生成类属性并赋值class Person { constructor( public name: string, private age: number, readonly id: number ) { // 自动创建并赋值:this.name, this.age, this.id } getInfo() { return \${this.name}, ${this.age}, ID: ${this.id}`; } } const p = new Person(“Alice”, 25, 1001); console.log(p.name); // OK // console.log(p.age); // ❌ private // p.id = 1002; // ❌ readonly`减少样板代码;支持 publicprivateprotectedreadonly 及其组合(如 readonly private);不能重复声明同名属性

静态成员

概念作用代码示例注意事项
static 属性属于类本身,不依赖实例,所有实例共享class Counter { static count: number = 0; static MAX_COUNT = 100; constructor() { Counter.count++; } static isMax() { return Counter.count >= Counter.MAX_COUNT; } } console.log(Counter.count); // 0 new Counter(); new Counter(); console.log(Counter.count); // 2通过类名访问,不能通过 this 或实例访问;常用于计数器、常量、工具方法
static 方法静态函数,通常作为工具函数或工厂方法class MathUtils { static add(a: number, b: number): number { return a + b; } static createZeroPoint() { return { x: 0, y: 0 }; } } const sum = MathUtils.add(2, 3); // 5不能访问实例属性或方法(无 this 上下文);适合无状态的工具函数
静态块(TypeScript 4.4+)在类加载时执行初始化逻辑--

抽象类与方法

概念作用代码示例注意事项
abstract不能被实例化,只能被继承abstract class Shape { abstract area(): number; abstract perimeter(): number; logInfo() { console.log(\Area: ${this.area()}`); } } // const s = new Shape(); // ❌ 错误`用于定义”模板”或”契约”;强制子类实现特定行为
abstract 方法仅定义方法签名,无实现,必须在子类中实现class Circle extends Shape { constructor(private radius: number) { super(); } area(): number { return Math.PI * this.radius ** 2; } perimeter(): number { return 2 * Math.PI * this.radius; } }抽象方法不能有方法体;子类必须实现所有抽象方法,否则也必须声明为 abstract
抽象类 vs 接口抽象类可包含实现和状态,接口只能定义结构接口更轻量,支持多实现;抽象类适合共享代码和状态;可结合使用:类实现接口并继承抽象类-

扩展:JS 原生私有字段(#

TypeScript 也支持 JavaScript 的原生私有字段 #,提供运行时私有性:

class BankAccount {
  #balance: number = 0;

  deposit(amount: number) {
    this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}
  • #balance 在 JS 运行时真正私有,无法通过 obj.#balance 或反射访问
  • private 不兼容,不能互相访问
  • 推荐用于需要强封装的场景

总结建议:

最佳实践说明
✅ 使用 private 封装内部状态防止外部误用
✅ 用 protected 设计可扩展的基类允许子类访问关键逻辑
readonly + 参数属性减少冗余代码提高开发效率
✅ 静态成员用于工具函数和共享数据避免实例化开销
✅ 抽象类用于定义框架或组件模板强制实现关键逻辑
✅ 抽象类和接口可结合使用实现”is-a”关系并共享代码

5. 泛型(Generics)

泛型函数与泛型类

概念作用代码示例注意事项
泛型函数函数的操作不依赖具体类型,通过类型参数保持灵活性function identity<T>(arg: T): T { return arg; } // 使用 let output1 = identity<string>("hello"); // 推断为 string let output2 = identity(42); // 类型推断:T = number // 泛型数组 function getFirst<T>(arr: T[]): T | undefined { return arr[0]; } getFirst([1, 2, 3]); // 返回 number | undefined类型参数 T 在调用时确定;可使用多个类型参数:<T, U>;尽量让 TS 自动推断类型,避免冗余标注
泛型类类的某些属性或方法依赖未定类型class Box<T> { value: T; constructor(value: T) { this.value = value; } getValue(): T { return this.value; } setValue(value: T): void { this.value = value; } } let stringBox = new Box("hello"); // T = string let numberBox = new Box(100); // T = number类名后声明泛型参数 <T>;所有实例共享类逻辑,但类型独立;构造函数不能是泛型的(但类可以是)

泛型约束

概念作用代码示例注意事项
extends 约束限制泛型参数必须满足某个类型结构interface Lengthwise { length: number; } function logLength<T extends Lengthwise>(arg: T): T { console.log(arg.length); // OK,因为保证有 length 属性 return arg; } logLength("hello"); // string 有 length logLength([1, 2, 3]); // 数组有 length // logLength(42); // ❌ 错误:number 没有 length避免对泛型做非法操作;约束越具体,可用属性越多;可结合交叉类型增强约束:T extends A & B
多类型参数约束多个泛型参数之间建立关系function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; // 安全访问 } let user = { name: "Alice", age: 28 }; getProperty(user, "name"); // 返回 string // getProperty(user, "email"); // ❌ 编译错误(若 email 不存在)keyof T 表示 T 的所有键的联合类型;K extends keyof T 确保 keyobj 的有效属性;这是泛型高级用法的核心模式之一

泛型默认值

概念作用代码示例注意事项
默认类型参数为泛型提供默认类型,提升易用性class Component<T = HTMLElement> { element: T; constructor(el: T) { this.element = el; } } // 使用默认类型 let div = new Component(document.getElementById("app")); // T = HTMLElement // 显式指定类型 let input = new Component<HTMLInputElement>(document.querySelector("input"));语法:<T = DefaultType>;提高 API 友好性,减少重复标注;常用于配置对象、容器类等场景
函数泛型默认值同样适用于函数function createArray<T = string>(length: number, value: T): T[] { return Array(length).fill(value); } createArray(3, "hi"); // string[] createArray<number>(3, 42); // number[] createArray(2, true); // boolean[],自动推断 T = boolean若能推断出类型,则忽略默认值;默认值仅在无法推断时生效

泛型工具类型:PartialRequiredReadonly

工具类型作用代码示例注意事项
Partial<T>T 的所有属性变为可选interface User { id: number; name: string; email: string; } function updateUser(id: number, changes: Partial<User>) { // 模拟更新用户信息 } updateUser(1, { name: "Bob" }); // OK,只改 name常用于”补丁”对象(如 PATCH 请求);实现原理:type Partial<T> = { [P in keyof T]?: T[P]; }
Required<T>T 的所有属性变为必选interface Config { apiUrl?: string; timeout?: number; } const defaultConfig: Required<Config> = { apiUrl: "https://api.example.com", timeout: 5000 };Partial 相反;用于确保配置项完整
Readonly<T>T 的所有属性变为只读interface Point { x: number; y: number; } let p: Readonly<Point> = { x: 10, y: 20 }; // p.x = 30; // ❌ 错误创建不可变对象;适合用于状态管理、常量数据
Pick<T, K>T 中选出一组属性 K,构成新类型interface Todo { id: number; title: string; description: string; completed: boolean; } type TodoPreview = Pick<Todo, "id" | "title">; const todo: TodoPreview = { id: 1, title: "Learn TS" }; // OKK 必须是 keyof T 的子集;用于提取关注字段
Omit<T, K>T 中排除一组属性 Ktype TodoInfo = Omit<Todo, "completed">; // 包含 id, title, description const info: TodoInfo = { id: 1, title: "Learn TS", description: "Master TypeScript generics" };Pick 互补;常用于 DTO、表单数据转换
Record<K, T>构建一个对象类型,其键属于 K,值类型为 Ttype Pages = "home" | "about" | "contact"; const nav: Record<Pages, string> = { home: "/", about: "/about", contact: "/contact" };用于动态键的对象;替代 { [key: string]: T } 更精确
ReturnType<T>获取函数类型的返回值类型function getUser() { return { name: "Alice", age: 28 }; } type User = ReturnType<typeof getUser>; // { name: string; age: number }结合 typeof 使用;用于类型推导,避免重复定义

自定义工具类型

// 将所有属性转为字符串类型
type Stringify<T> = {
  [P in keyof T]: string;
};

interface User { id: number; name: string; }
type StringUser = Stringify<User>;
// { id: string; name: string; }

总结建议:

最佳实践说明
✅ 优先使用泛型替代 any保持类型安全
✅ 使用泛型约束提升类型精度避免运行时错误
✅ 善用 keyof 和索引类型实现安全访问getProperty 模式
✅ 内置工具类型大幅简化类型操作推荐熟练掌握 PartialPickOmit
✅ 泛型默认值提升 API 友好性减少使用者负担
✅ 泛型类适合构建通用容器或组件Box<T>Store<T>

6. 高级类型技巧

条件类型

概念作用代码示例注意事项
基本语法 T extends U ? X : Y根据类型关系选择不同的类型分支type IsString<T> = T extends string ? true : false; type A = IsString<"hello">; // true type B = IsString<42>; // false类似三元运算符,但作用于类型层面;在编译时计算,不影响运行时
分布式条件类型T 是联合类型时,条件类型会自动”分发”到每个成员type ToArray<T> = T extends any ? T[] : never; type Result = ToArray<string | number>; // string[] | number[] // 等价于: (string extends any ? string[] : never) | (number extends any ? number[] : never)只对”裸类型参数”(naked type parameter)生效;用于构建灵活的类型转换工具
实际应用:ExcludeExtract内置工具类型基于条件类型实现// Exclude<T, U>: 从 T 中排除可分配给 U 的类型 type WithoutString = Exclude<string | number | boolean, string>; // number | boolean // Extract<T, U>: 从 T 中提取可分配给 U 的类型 type OnlyString = Extract<string | number, string>; // stringExclude<T, U> = T extends U ? never : TExtract<T, U> = T extends U ? T : never

映射类型

概念作用代码示例注意事项
基本映射类型基于已有类型,通过遍历其属性构建新类型interface Todo { title: string; completed: boolean; } type ReadOnlyTodo = { [K in keyof Todo]: Todo[K]; }; // 等价于 { title: string; completed: boolean; } // 但可添加修饰符 type Readonly<T> = { readonly [K in keyof T]: T[K]; }; type LockedTodo = Readonly<Todo>; // 所有属性只读keyof T 获取 T 的所有键的联合类型;[K in Keys] 遍历每个键;T[K] 获取对应属性类型
可选与只读映射使用 ?readonly 修饰符type Partial<T> = { [K in keyof T]?: T[K]; // 可选 }; type Mutable<T> = { -readonly [K in keyof T]: T[K]; // 移除只读 }; type Required<T> = { [K in keyof T]-?: T[K]; // 移除可选 };-? 表示移除可选性;-readonly 表示移除只读性;TypeScript 内置了这些工具类型
映射修饰符 as(重映射键)使用 as 动态修改生成的属性名(TS 4.1+)type EventNames<T> = { [K in keyof T as \on${Capitalize<string & K>}Change`]: (v: T[K]) => void; }; interface Form { name: string; age: number; } type FormEvents = EventNames
; // { onNameChange: (v: string) => void; onAgeChange: (v: number) => void; }`
as 后可使用模板字面量类型;需将 K 转为 string & K 才能使用 Capitalize;强大但复杂,适合高级场景

类型守卫(Type Guard)

概念作用代码示例注意事项
typeof 守卫用于原始类型(stringnumber 等)的类型收窄function padLeft(value: string | number, padding: string | number) { if (typeof padding === "number") { return " ".repeat(padding) + value; // padding: number } if (typeof value === "string") { return padding + value; // value: string } return padding.toString() + value.toString(); }仅适用于 stringnumberbooleansymbol;不能用于对象、数组、null;类似 Python 的 type(obj) == Class,仅能用于基础类型
instanceof 守卫用于类或构造函数的类型收窄class Dog { bark() { console.log("Woof!"); } } class Cat { meow() { console.log("Meow!"); } } function makeSound(animal: Dog | Cat) { if (animal instanceof Dog) { animal.bark(); // animal: Dog } else { animal.meow(); // animal: Cat } }依赖原型链,适用于 class 实例;不适用于接口(无运行时值);类似 Python 的 isinstance(obj, class),可以处理继承关系
in 操作符守卫检查对象是否具有某属性interface Admin { role: string; permissions: string[]; } interface User { name: string; email: string; } function printInfo(entity: Admin | User) { if ("role" in entity) { console.log(entity.role); // entity: Admin } else { console.log(entity.email); // entity: User } }适用于接口或对象类型的区分;比 typeof 更灵活;类似 Python 的 hasattr
自定义类型谓词(is创建可复用的类型判断函数function isString(value: any): value is string { return typeof value === "string"; } function processInput(input: string | number) { if (isString(input)) { return input.toUpperCase(); // input: string } return input.toFixed(2); // input: number }返回类型为 value is Type;函数返回 true 时,TS 推断 valueType;适合复杂判断逻辑的封装

类型推断(infer

概念作用代码示例注意事项
infer 关键字在条件类型中”捕获”待推断的类型// 提取函数返回值类型 type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type Func = () => string; type R = ReturnType<Func>; // string // 提取数组元素类型 type ElementType<T> = T extends (infer U)[] ? U : never; type Item = ElementType<number[]>; // numberinfer 只能在 extends 子句中使用;infer 声明的类型变量在 true 分支中可用
多重 infer从复杂结构中提取多个类型type FunctionArgs<T> = T extends (...args: infer A) => any ? A : never; type Args = FunctionArgs<(name: string, age: number) => void>; // [string, number]可提取参数类型、this 类型等;用于构建高级类型工具
实际应用:Promise<value> 解包常用于处理异步类型type Unpacked<T> = T extends Promise<infer U> ? U extends Promise<any> ? Unpacked<U> // 递归解包 : U : T; type A = Unpacked<Promise<string>>; // string type B = Unpacked<Promise<Promise<number>>>; // number结合递归实现深度解包;类似 awaited 类型的行为

扩展:实用高级类型模式

// 递归 Partial(深度可选)
type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

// 获取类的实例类型
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;

总结建议:

最佳实践说明
✅ 条件类型用于”类型逻辑判断”ExcludeNonNullable
✅ 映射类型用于”批量转换属性”PartialReadonly、自定义事件类型等
✅ 类型守卫是联合类型安全使用的基石必须在使用前进行收窄
infer 是高级类型的核心工具用于提取、解构复杂类型
✅ 组合使用这些技巧构建强大类型系统ReturnType<infer R> 模式非常常见

7. 模块与类型管理

ES 模块与 CommonJS 互操作

概念作用代码示例注意事项
ES 模块(ESM)语法使用 import / export 的现代模块标准// mathUtils.ts export const add = (a: number, b: number): number => a + b; export function multiply(a: number, b: number): number { return a * b; } export default function divide(a: number, b: number): number { return a / b; } // main.ts import divide, { add, multiply } from './mathUtils'; console.log(add(2, 3)); // 5推荐使用现代 ESM 语法;支持命名导出和默认导出
CommonJS 语法Node.js 传统模块系统,使用 require / module.exports// mathUtils.cjs (或 .js) module.exports = { add: (a: number, b: number) => a + b, multiply: (a: number, b: number) => a * b, }; // main.ts const math = require('./mathUtils'); // 或使用 import import * as math from './mathUtils';在 Node.js 环境中广泛使用;TypeScript 可通过配置支持混合使用
esModuleInterop: true允许更自然地从 CommonJS 模块导入默认导出// 假设 lodash 是 CommonJS 模块 // tsconfig.json { "compilerOptions": { "esModuleInterop": true } } import _ from 'lodash'; // OK,即使 lodash 没有 __esModule 标记解决 default 导出兼容性问题;推荐在新项目中开启
allowSyntheticDefaultImports允许对没有默认导出的模块使用 import X from 'Y'// 即使 moment 是 CommonJS 模块 import moment from 'moment'; // OK(若 allowSyntheticDefaultImports: true)仅影响类型检查,不修改运行时行为;需与打包工具(如 Webpack)配合使用

类型声明文件(.d.ts

概念作用代码示例注意事项
声明文件作用为 JavaScript 文件提供类型信息,使 TS 能进行类型检查// jquery.d.ts declare const $: { (selector: string): any; ajax(url: string, options?: any): void; }; declare function jQuery(selector: string): any; export default jQuery;文件以 .d.ts 结尾;不生成 JS 代码,仅用于类型检查;可为第三方库或全局变量提供类型
为本地 JS 文件添加类型让 TS 理解 .js 文件的结构// utils.js exports.formatDate = (date) => { /* ... */ }; // utils.d.ts declare module './utils' { export function formatDate(date: Date): string; }文件名需与 JS 文件对应;使用 declare module 'path' 语法
全局声明文件定义全局变量或函数的类型// globals.d.ts declare const VERSION: string; declare function log(message: string): void;放入 includetypeRoots 中;避免污染全局命名空间,推荐使用模块化

@types 社区类型库

概念作用代码示例注意事项
@types/*DefinitelyTyped 社区维护的第三方库类型定义npm install --save-dev @types/lodash @types/express @types/react import _ from 'lodash'; import express from 'express'; const app = express(); app.get('/', (req, res) => { res.send('Hello'); });大多数流行库都有对应的 @types 包;命名规则:@types/<npm-package-name>
类型自动加载TypeScript 自动查找 node_modules/@types 中的类型// 无需手动引入 .d.ts 文件 import * as fs from 'fs'; // 内置类型 // import express from 'express'; // @types/express 提供类型优先级:项目内 .d.ts > node_modules/@types > 内置类型
自定义类型覆盖项目内类型优先于 @types// src/types/express/index.d.ts declare module 'express' { interface Request { user?: { id: number; name: string }; } }用于扩展第三方库类型(如添加 req.user);文件结构需匹配模块路径

三斜线指令

⚠️ 三斜线指令是旧版 TypeScript 特性,现代项目推荐使用模块导入。仅在特定场景下使用。

指令作用代码示例注意事项
/// <reference path="..." />引入本地 .d.ts 文件// utils.d.ts declare function formatCurrency(amount: number): string; // main.ts /// <reference path="./utils.d.ts" /> console.log(formatCurrency(100));用于文件间依赖;已被 import 取代,仅用于全局声明文件
/// <reference types="..." />引入 @types 包中的类型/// <reference types="jquery" /> // 等价于:import 'jquery'; // 但不导入值,仅引入类型用于不导入值但需要类型的场景(如 JSX);等价于 import 类型引用
/// <reference lib="..." />引入 TypeScript 内置类型库/// <reference lib="dom" /> /// <reference lib="es2021" />lib 未在 tsconfig.json 中配置时使用;如 domes5webworker

配置建议(tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "typeRoots": ["node_modules/@types", "src/types"]
  },
  "include": ["src/**/*"]
}

总结建议:

最佳实践说明
✅ 优先使用 ES 模块语法更现代、可 tree-shaking
✅ 开启 esModuleInteropallowSyntheticDefaultImports提高模块互操作性
✅ 为第三方 JS 库使用 @types/*获得开箱即用的类型支持
✅ 本地 JS 文件可通过 .d.ts 添加类型逐步迁移 JS 项目到 TS
✅ 扩展第三方类型时在 src/types 下创建声明文件如 Express 的 Request 扩展
✅ 避免使用三斜线指令优先使用 import / export 模块语法

8. 编译配置(tsconfig.json)

常用编译选项:targetmoduleoutDirstrict

选项作用配置示例注意事项
target指定编译后的 JavaScript 版本{ "compilerOptions": { "target": "ES2020" } }决定生成代码的语法兼容性(如 letconst、箭头函数);推荐使用 ES2015 或更高版本;若需支持旧浏览器,可设为 ES5,但会引入更多 polyfill 和降级代码
module指定模块系统格式{ "compilerOptions": { "module": "ESNext" } }ESNext:支持最新 ES 模块语法(推荐用于现代项目);CommonJS:Node.js 默认模块系统;需与运行环境或打包工具(如 Webpack、Vite)匹配
outDir指定编译后文件的输出目录{ "compilerOptions": { "outDir": "./dist" } }避免源码与编译后文件混杂;建议设置为 distbuild 等独立目录;配合 .gitignore 忽略输出目录
rootDir指定源码根目录{ "compilerOptions": { "rootDir": "./src", "outDir": "./dist" } }控制哪些文件参与编译;与 outDir 配合保持目录结构一致
strict启用所有严格类型检查选项的总开关{ "compilerOptions": { "strict": true } }等价于启用 strictNullChecksstrictFunctionTypesstrictBindCallApplystrictPropertyInitializationnoImplicitAnynoImplicitThisalwaysStrict;强烈建议在新项目中开启
strictNullChecks禁止 nullundefined 赋值给非 any 类型let name: string = null; // ❌ 错误(若 strictNullChecks: true) let optionalName: string | null = null; // OK防止空值错误;需显式声明联合类型 T | null | undefined
noImplicitAny禁止隐式 any 类型推断function log(value) { // ❌ 参数隐式为 any console.log(value); }强制显式标注类型,避免类型失控;提高代码可维护性
esModuleInterop改善 ES 模块与 CommonJS 的互操作性{ "compilerOptions": { "esModuleInterop": true } }允许 import _ from 'lodash' 正确导入 CommonJS 模块;推荐开启
allowSyntheticDefaultImports允许对无默认导出的模块使用默认导入import React from 'react'; // 即使 react 是 CommonJS仅影响类型检查,不改变运行时;通常与 esModuleInterop 一起使用
types / typeRoots控制类型声明文件的查找范围{ "compilerOptions": { "types": ["node", "jest"], "typeRoots": ["./typings", "node_modules/@types"] } }types:白名单,只包含指定包的类型;typeRoots:自定义类型声明目录优先级

增量编译与构建优化

概念作用配置/示例注意事项
增量编译(Incremental)只重新编译修改过的文件及其依赖,大幅提升构建速度{ "compilerOptions": { "incremental": true, "composite": true } }首次编译仍较慢,后续编译显著加快;TS 会生成 .tsbuildinfo 文件记录编译状态;适合大型项目
tsbuildinfo 文件存储上次编译的元信息(如时间戳、依赖图)自动生成于 outDir 或指定路径可提交到版本控制(避免 CI 重复全量编译);删除后下次编译变为全量
构建模式(--build使用 tsc --build 支持项目引用和增量构建tsc --build # 构建全部 tsc --build --clean # 清理构建状态 tsc --build --watch # 监听并增量编译替代 tsc 命令用于复杂项目;支持多项目配置(Project References)
项目引用(Project References)将大项目拆分为多个子项目,实现按需编译// tsconfig.base.json { "compilerOptions": { /* 公共配置 */ } } // packages/utils/tsconfig.json { "extends": "../tsconfig.base.json", "references": [ { "path": "../shared" } ] }实现逻辑分离与独立构建;修改一个包只触发相关联的增量编译;适合 monorepo 架构
declaration: true生成 .d.ts 类型声明文件{ "compilerOptions": { "declaration": true, "declarationMap": true } }发布 npm 包时必须开启,提供类型支持;生成的 .d.ts 文件可被其他 TS 项目使用
skipLibCheck: true跳过对 .d.ts 文件的类型检查{ "compilerOptions": { "skipLibCheck": true } }加快编译速度,尤其当使用大量 @types 包时;安全:因第三方类型通常已验证;仅跳过库文件,不影响业务代码检查

推荐基础 tsconfig.json 示例

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": true,
    "declaration": true,
    "declarationMap": true
  },
  "include": ["src"]
}

总结建议:

最佳实践说明
✅ 新项目务必开启 "strict": true最大程度利用类型系统优势
✅ 设置合理的 outDirrootDir保持项目结构清晰
✅ 开启 incremental: true显著提升大型项目构建效率
✅ 发布库时启用 declaration: true提供完整的类型支持
✅ 使用 tsc --build 管理复杂项目支持增量、清理、监听等高级功能
✅ 合理使用 skipLibCheck 加速编译不影响业务代码安全性
✅ 配置 .gitignore 忽略 dist/.tsbuildinfo避免提交编译产物(除非必要)

9. 工程化与工具链

与 ESLint、Prettier 集成

工具作用配置示例注意事项
ESLint(代码质量检查)静态分析代码,检测潜在错误、风格问题和类型安全漏洞npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin // .eslintrc.json { "root": true, "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "parserOptions": { "project": "./tsconfig.json" }, "rules": { "@typescript-eslint/no-unused-vars": "error" } }@typescript-eslint/parser:让 ESLint 能解析 TS 语法;@typescript-eslint/eslint-plugin:提供 TS 特有规则;project 选项启用类型检查规则;推荐使用 eslint --ext .ts,.tsx src 检查
Prettier(代码格式化)统一代码风格,自动格式化(缩进、引号、换行等)npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier // .prettierrc { "semi": true, "singleQuote": true, "arrowParens": "avoid", "trailingComma": "es5" } // .eslintrc.json(续) { "extends": [ "...", "plugin:prettier/recommended" ] }eslint-config-prettier:禁用与 Prettier 冲突的 ESLint 规则;eslint-plugin-prettier:将 Prettier 作为 ESLint 规则运行;推荐在编辑器中启用”保存时自动格式化”
VS Code 集成实现编辑器内实时 lint 和格式化// .vscode/settings.json { "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "eslint.validate": ["typescript", "typescriptreact"] }安装 VS Code 插件:ESLint、Prettier - Code formatter;确保项目根目录有 .eslintrc.prettierrc

与构建工具(Webpack、Vite)配合

构建工具集成方式配置要点注意事项
Webpack使用 ts-loaderbabel-loader 编译 TypeScriptnpm install --save-dev ts-loader // webpack.config.js module.exports = { entry: './src/index.ts', module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js'], }, output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, };ts-loader:直接调用 tsc 编译,支持类型检查;babel-loader + @babel/preset-typescript:仅做语法转换,不进行类型检查,需配合 fork-ts-checker-webpack-pluginextensions 需包含 .ts.tsx
Vite原生支持 TypeScript,零配置启动// src/main.ts import { createApp } from 'vue'; import App from './App.vue'; createApp(App).mount('#app'); // vite.config.ts import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], });Vite 使用 esbuild 预构建 TS,极快;开发阶段不进行类型检查,需单独运行 tsc --noEmit 或使用 vite-plugin-checker;生产构建使用 Rollup
类型检查插件在构建过程中加入类型检查# Webpack npm install --save-dev fork-ts-checker-webpack-plugin # Vite npm install --save-dev vite-plugin-checker // vite.config.ts import checker from 'vite-plugin-checker'; export default defineConfig({ plugins: [ vue(), checker({ typescript: true }) ], });避免”类型错误但构建成功”的问题;推荐在 CI/CD 中运行 tsc --noEmit 作为最终检查

开发热重载(ts-node、tsx)

工具作用使用方式注意事项
ts-node直接运行 .ts 文件,无需预编译npm install --save-dev ts-node # 运行 TypeScript 文件 npx ts-node src/index.ts # 结合 nodemon 实现热重载 npx nodemon --exec "ts-node" src/index.ts适合后端 Node.js 服务开发;首次启动较慢(需编译);不适用于前端浏览器环境
tsx(推荐)基于 esbuild 的极速 TS/TSX 运行器,支持 ESM 和热重载npm install --global tsx # 运行脚本 npx tsx src/index.ts # 支持 JSX 和 ESM npx tsx src/app.tsx # 监听文件变化(热重载) npx tsx watch src/index.tsts-node 快 10-100 倍;原生支持 importexport、JSX;内置 watch 模式,无需 nodemon;推荐用于现代 TS 项目开发
vite-nodeVite 生态的运行时,支持 Vite 插件和 HMRnpm install --save-dev vite-node # 通常用于测试框架(如 Vitest) npx vitest与 Vite 配置一致,适合全栈项目;主要用于测试环境

推荐开发脚本(package.json

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "lint": "eslint src --ext .ts,.tsx",
    "format": "prettier --write src",
    "type-check": "tsc --noEmit",
    "preview": "node dist/index.js"
  }
}

工程化最佳实践总结:

实践说明
✅ 统一代码风格ESLint + Prettier + Editor Integration,确保团队一致性
✅ 构建工具选择前端项目:Vite(开发快,配置少);复杂项目:Webpack(灵活性高)
✅ 开发体验优化使用 tsxts-node + nodemon 实现快速启动与热重载
✅ 类型检查不遗漏即使使用 Vite 或 babel,也需在 CI 中运行 tsc --noEmit
✅ 配置文件共用将 ESLint、Prettier 配置提取为独立包(如 @myorg/eslint-config),便于多项目复用
✅ 性能监控使用 --diagnostics--extendedDiagnostics 查看 tsc 性能瓶颈

10. 最佳实践

避免滥用 any

原则说明错误示例正确做法注意事项
any 是类型安全的”后门”使用 any 会关闭类型检查,失去 TypeScript 的核心优势function processData(data: any) { data.randomMethod(); // ❌ 运行时可能报错 return data.value * 2; // ❌ 可能 NaN } // 调用 processData({ name: "Alice" }); // 无编译错误,但运行失败interface Data { value: number; } function processData(data: Data) { return data.value * 2; // 类型安全 } // processData({ name: "Alice" }); // ❌ 编译时报错仅在临时调试或无法避免的第三方库交互中使用;优先使用更精确的类型(如 unknown、泛型、接口)
替代方案:使用泛型保持类型灵活性的同时保证安全function identity<T>(arg: T): T { return arg; } const result = identity({ name: "Bob" }); // 类型为 { name: string }-适用于函数、组件等通用逻辑;避免”类型擦除”
替代方案:联合类型 + 类型守卫处理多种可能类型function handleInput(input: string | number) { if (typeof input === "string") { return input.toUpperCase(); } return input.toFixed(2); }-结合 typeofinstanceofin 安全收窄类型

合理使用 unknown

原则说明代码示例注意事项
unknownany 的安全替代unknown 表示”未知类型”,不能直接操作,必须先进行类型检查function safeParse(json: string): unknown { return JSON.parse(json); } const data = safeParse('{ "name": "Alice" }'); // ❌ 错误:不能直接访问 // console.log(data.name); // 必须先类型守卫 if (typeof data === "object" && data !== null && "name" in data) { console.log((data as { name: string }).name); } // 或使用类型谓词 function isUser(obj: any): obj is { name: string } { return !!obj && typeof obj === "object" && "name" in obj; } if (isUser(data)) { console.log(data.name); // OK,类型收窄 }适用于:API 响应、JSON 解析、用户输入等不可信数据;比 any 安全,强制类型验证;在函数返回未知结构数据时,优先返回 unknown;接收第三方数据时,参数类型设为 unknown

any vs unknown 对比:

  • any:可随意读写、调用方法(失去类型安全)
  • unknown:必须通过类型守卫才能使用

类型驱动开发(TDD)

原则说明开发流程示例注意事项
先定义类型,再实现逻辑通过类型系统设计 API 接口,驱动代码实现// Step 1: 定义类型 interface User { id: number; name: string; email: string; } type ApiResponse<T> = { success: true; data: T; } | { success: false; error: string; }; function fetchUser(id: number): Promise<ApiResponse<User>> { // Step 2: 实现逻辑(类型已知) return fetch(\/api/users/${id}`) .then(res => res.json()) .then(data => { if (data.success) { return { success: true, data: data.data as User }; } return { success: false, error: data.error }; }); } // Step 3: 使用(类型安全) fetchUser(1).then(result => { if (result.success) { console.log(result.data.name); // 自动提示 } else { console.error(result.error); } });`类型即文档,提升可维护性;减少运行时错误;适合设计 API、状态管理、组件 props
工具支持编辑器基于类型提供智能提示、自动补全、重构支持使用 VS Code 可实时看到类型推断;类型错误在编码阶段暴露-

渐进式迁移 JS 项目到 TS

阶段策略操作步骤注意事项
1. 初始化配置引入 TypeScript,但不强制检查npm install --save-dev typescript // tsconfig.json { "compilerOptions": { "target": "ES2020", "module": "ESNext", "allowJs": true, "checkJs": false, "outDir": "./dist", "rootDir": "./src" }, "include": ["src"] }allowJs: true:允许 .js 文件存在;checkJs: false:暂不检查 JS 文件类型
2. 重命名与增量添加类型将部分 .js 文件改为 .ts,逐步添加类型mv src/utils.js src/utils.ts // src/utils.ts export function formatDate(date: Date): string { return date.toLocaleDateString(); }优先迁移工具函数、类型稳定模块;使用 // @ts-ignore 临时忽略错误(需标注原因);仍可混合使用 JS 和 TS
3. 启用严格检查分阶段开启严格模式{ "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true } }不要一次性开启所有 strict 选项;使用 tsc --noEmit --watch 实时查看错误
4. 补充类型声明为第三方库或全局变量添加 .d.ts// src/types/global.d.ts declare const VERSION: string; // src/types/lodash.d.ts declare module 'lodash' { export function debounce<T extends (...args: any[]) => any>(func: T, wait?: number): T; }使用 @types/* 优先;自定义声明避免过度泛化
5. 全量迁移与维护所有文件使用 .ts,持续维护类型质量// 最终 tsconfig.json { "compilerOptions": { "strict": true, "allowJs": false } }建立 CI 流程:tsc --noEmit 作为构建步骤;团队培训:避免新增 any

推荐 ESLint 规则防止 any 滥用

{
  "@typescript-eslint/no-explicit-any": "error",
  "@typescript-eslint/no-unsafe-argument": "error",
  "@typescript-eslint/no-unsafe-assignment": "error",
  "@typescript-eslint/no-unsafe-call": "error",
  "@typescript-eslint/no-unsafe-member-access": "error"
}

最佳实践总结表:

实践推荐做法避免做法
类型安全使用 unknown + 类型守卫直接使用 any
函数参数明确接口或泛型anyObject
第三方数据unknown → 类型验证 → 类型断言直接 as MyType
项目迁移渐进式:.js.ts → 添加类型 → 严格模式一次性重写全部代码
团队协作统一 ESLint + Prettier + no-any 规则放任 any 泛滥

11. TypeScript 与 JavaScript 编程习惯对比表

操作名称JavaScript 实现TypeScript 实现两者区别说明
变量声明(带类型)let age = 25; const name = "Alice";let age: number = 25; const name: string = "Alice";TS 显式声明类型,JS 依赖运行时推断。TS 可防止类型错误(如 age = "30" 会报错)
函数参数与返回值function add(a, b) { return a + b; }function add(a: number, b: number): number { return a + b; }TS 要求参数和返回值类型明确,调用时传错类型会编译报错。JS 只在运行时暴露问题
对象结构定义const user = { name: "Bob", age: 30 };interface User { name: string; age: number; } const user: User = { name: "Bob", age: 30 };TS 使用 interfacetype 定义结构,确保对象符合预期,避免拼写错误或缺失字段
数组类型定义const list = [1, 2, 3];const list: number[] = [1, 2, 3]; // 或 const list: Array<number> = [1, 2, 3];TS 明确数组元素类型,防止插入非法类型(如 list.push("4") 会报错)
可选属性function logUser(user) { console.log(user.name); if (user.email) { console.log(user.email); } }interface User { name: string; email?: string; } function logUser(user: User) { console.log(user.name); if (user.email) { console.log(user.email); } }TS 使用 ? 明确表示属性可选,调用时必须检查是否存在,提升代码健壮性
联合类型function formatStatus(status) { return status === 1 ? "Active" : "Inactive"; }type Status = 1 | 0; function formatStatus(status: Status): string { return status === 1 ? "Active" : "Inactive"; }TS 使用 1 | 0 限制值范围,传入 2 会报错。JS 无法限制
枚举类型const STATUS_ACTIVE = 1; const STATUS_INACTIVE = 0; if (user.status === STATUS_ACTIVE) { ... }enum Status { Active = 1, Inactive = 0 } if (user.status === Status.Active) { ... }TS 枚举提供命名空间和类型安全,避免魔法数字。JS 需手动定义常量
函数重载(模拟)function handleInput(value) { if (typeof value === "string") { return value.toUpperCase(); } else { return value * 2; } }function handleInput(value: string): string; function handleInput(value: number): number; function handleInput(value: string | number): string | number { if (typeof value === "string") { return value.toUpperCase(); } else { return value * 2; } }TS 支持函数重载签名,提供更精确的类型提示。JS 仅靠运行时判断
类与访问修饰符class Person { constructor(name) { this.name = name; } }class Person { private name: string; public age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } }TS 支持 privatepublicprotected,控制属性访问权限,增强封装性
接口实现(implements无原生支持interface Drawable { draw(): void; } class Circle implements Drawable { draw() { console.log("Drawing a circle"); } }TS 支持类实现接口,强制类包含指定方法,提升代码规范性和可扩展性
泛型使用function identity(value) { return value; }function identity<T>(value: T): T { return value; } const result = identity<string>("hello");TS 泛型保持类型信息,调用时能推断返回类型。JS 无法保留类型
类型断言(强制转型)无对应机制const input = document.getElementById("name") as HTMLInputElement; input.value = "Hello";TS 允许开发者”断言”某个值的类型,绕过类型检查(需谨慎使用)
空值检查if (user && user.profile) { ... }if (user?.profile) { ... } // 可选链 // 或使用非空断言(需确保安全) user!.profileTS 支持可选链 ?. 和非空断言 !,语法更简洁,但 ! 有风险
模块导入import React from 'react'; import { useState } from 'react';import React from 'react'; import { useState } from 'react'; // 类型导入(可省略) import type { User } from './types';导入语法一致,但 TS 支持 import type 仅导入类型,不参与运行时
基本箭头函数const add = (a, b) => { return a + b; };const add = (a: number, b: number): number => { return a + b; };TS 明确参数和返回值类型,防止传入字符串等非法类型。JS 无类型约束
单表达式隐式返回const square = x => x * x;const square = (x: number): number => x * x;TS 仍需标注参数类型,返回类型可自动推断。JS 无类型信息
无参数箭头函数const greet = () => { console.log("Hello"); };const greet = (): void => { console.log("Hello"); };TS 可显式声明返回类型为 void(无返回值),增强语义清晰度
返回对象字面量const createUser = (name, age) => ({ name: name, age: age });interface User { name: string; age: number; } const createUser = (name: string, age: number): User => ({ name, age });TS 使用接口定义返回对象结构,确保字段正确。JS 无法验证对象形状
回调函数(数组方法)const numbers = [1, 2, 3]; const doubled = numbers.map(n => n * 2);const numbers: number[] = [1, 2, 3]; const doubled: number[] = numbers.map((n: number): number => n * 2);TS 可推断类型,但显式标注提升可读性。TS 能防止传错类型到 map
事件处理函数const button = document.getElementById("btn"); button.addEventListener("click", (e) => { console.log(e.target); });const button = document.getElementById("btn"); if (button) { button.addEventListener("click", (e: MouseEvent) => { console.log((e.target as HTMLElement).innerText); }); }TS 需断言 e.target 类型,且事件对象类型更精确(如 MouseEvent
异步箭头函数const fetchData = async (url) => { const res = await fetch(url); return await res.json(); };interface ApiResponse { data: any; } const fetchData = async (url: string): Promise<ApiResponse> => { const res = await fetch(url); return await res.json(); };TS 使用 Promise<T> 明确异步函数返回的是 Promise 且解析后类型为 T
高阶函数返回箭头函数const createMultiplier = (factor) => { return (num) => num * factor; };const createMultiplier = (factor: number): ((num: number) => number) => { return (num: number): number => num * factor; };TS 显式声明返回的是一个函数类型,提升类型安全性与可读性