TypeScript 知识总结(基础 + 高级)
TypeScript 是 JavaScript 的超集,为其添加静态类型系统,编译后生成纯 JS。它解决了 JS 的类型不安全、重构困难、IDE 提示弱三大痛点,本文覆盖基础类型、泛型与高级类型技巧。
第一部分:基础
一、TypeScript 简介
1.1 什么是 TypeScript
- 微软开发的编程语言,2012 年发布
- JavaScript 的超集——所有合法的 JS 代码都是合法的 TS 代码
- 添加了静态类型系统,编译时检查类型错误
- 编译后生成纯 JS,运行时没有类型信息(类型擦除)
1.2 TS vs JS
| 对比项 | TypeScript | JavaScript |
|---|---|---|
| 类型系统 | 静态类型(编译时检查) | 动态类型(运行时才知道) |
| 错误发现 | 编译时(早) | 运行时(晚) |
| IDE 支持 | 智能提示、跳转定义、重构 | 较弱 |
| 学习曲线 | 需要学类型语法 | 直接写 |
| 运行环境 | 需编译为 JS | 直接运行 |
| 适合项目 | 中大型项目 | 小型项目、脚本 |
1.3 安装与编译
# 全局安装npm install -g typescript# 初始化配置文件tsc --init# 编译单个文件tsc index.ts# 监听模式(文件变化自动编译)tsc -w# 验证版本tsc -v1.4 第一个 TS 程序
// index.tsfunction greet(name: string): string { return `Hello, ${name}!`;}const message: string = greet('World');console.log(message);tsc index.ts # 生成 index.jsnode index.js # Hello, World!二、基础类型
2.1 常用类型一览
// 基本类型let str: string = 'hello';let num: number = 42;let bool: boolean = true;// 数组let arr: number[] = [1, 2, 3];let arr2: Array<number> = [1, 2, 3];let mixed: (string | number)[] = [1, 'a', 2, 'b'];// 元组(Tuple)—— 固定长度和类型的数组let tuple: [string, number] = ['Alice', 25];// 枚举enum Color { Red, Green, Blue }let c: Color = Color.Green; // 1enum Direction { Up = 'UP', Down = 'DOWN' }let d: Direction = Direction.Up; // 'UP'2.2 特殊类型
| 类型 | 说明 | 示例 |
|---|---|---|
any |
任意类型,跳过类型检查 | let a: any = 1; a = 'x'; |
unknown |
安全的 any,使用前必须断言 | let a: unknown = 1; (a as number).toFixed() |
void |
函数无返回值 | function log(): void {} |
never |
永不返回(抛异常/死循环) | function error(): never { throw new Error(); } |
null |
空值 | let n: null = null; |
undefined |
未定义 | let u: undefined = undefined; |
2.3 any vs unknown vs never
// any:放弃类型检查(不推荐)let a: any = 1;a.foo(); // 编译通过,运行时报错// unknown:安全的 any,必须先检查类型才能用let b: unknown = 'hello';// b.toUpperCase(); // ❌ 报错if (typeof b === 'string') { b.toUpperCase(); // ✅ 正确}// never:不可能到达的类型function throwError(msg: string): never { throw new Error(msg); // 永远不会正常返回}// never 的实际用途:穷尽检查type Shape = 'circle' | 'square';function getArea(shape: Shape) { switch (shape) { case 'circle': return Math.PI; case 'square': return 4; default: const _exhaustive: never = shape; // 如果漏了某个 case,这里会报错 break; }}2.4 字面量类型
// 字符串字面量let direction: 'up' | 'down' | 'left' | 'right';direction = 'up'; // ✅// direction = 'top'; // ❌// 数字字面量let dice: 1 | 2 | 3 | 4 | 5 | 6;dice = 3; // ✅// 布尔字面量let alwaysTrue: true = true;三、类型注解与推断
3.1 类型注解
手动告诉 TS 变量的类型:
let name: string = 'Alice';let age: number = 25;let callback: (x: number) => number = (x) => x * 2;// 函数参数和返回值function add(a: number, b: number): number { return a + b;}3.2 类型推断
TS 会自动推断变量的类型,不需要每次都写注解:
let name = 'Alice'; // 推断为 stringlet age = 25; // 推断为 numberlet arr = [1, 2, 3]; // 推断为 number[]// 函数返回值推断function add(a: number, b: number) { return a + b; // 推断返回值为 number}// 对象推断const user = { name: 'Alice', age: 25,};// 推断为 { name: string; age: number }3.3 什么时候需要手动标注
| 场景 | 是否需要标注 |
|---|---|
| 变量声明时有初始值 | ❌ 自动推断 |
| 函数参数 | ✅ 必须 |
| 函数返回值 | 可选(但推荐标注,便于理解) |
| 变量声明时无初始值 | ✅ 必须 |
| 复杂类型(联合、交叉) | ✅ 推荐 |
// ✅ 有初始值,不需要标注let count = 0;// ❌ 无初始值,必须标注let count: number;count = 10;// ✅ 函数参数必须标注function greet(name: string) {}// ✅ 返回值推荐标注function getUser(): User { return { name: 'Alice', age: 25 };}四、接口与类型别名
4.1 接口(interface)
interface User { name: string; age: number; email?: string; // 可选属性 readonly id: number; // 只读属性}const user: User = { id: 1, name: 'Alice', age: 25, // email 可以不写};// user.id = 2; // ❌ 只读属性不能修改4.2 索引签名
interface StringArray { [index: number]: string; // 数组式访问}let arr: StringArray = ['a', 'b', 'c'];interface Config { [key: string]: any; // 任意字符串键 name: string; // 具体属性也必须符合索引签名}4.3 接口继承
interface Animal { name: string;}interface Dog extends Animal { bark(): void;}const dog: Dog = { name: 'Rex', bark() { console.log('Woof!'); },};4.4 类型别名(type)
type User = { name: string; age: number;};type ID = string | number;type Callback = (data: any) => void;type Point = [number, number]; // 元组4.5 interface vs type
| 对比项 | interface | type |
|---|---|---|
| 语法 | interface X {} |
type X = {} |
| 对象类型 | ✅ | ✅ |
| 联合类型 | ❌ | ✅ type A = B | C |
| 交叉类型 | ❌(用继承模拟) | ✅ type A = B & C |
| 元组 | ❌ | ✅ |
| 基本类型别名 | ❌ | ✅ type ID = string |
| 声明合并 | ✅ 同名接口自动合并 | ❌ 重复定义报错 |
| 继承 | extends |
& 交叉 |
| 推荐 | 对象/类类型 | 联合/交叉/工具类型 |
// 声明合并(只有 interface 支持)interface Window { myCustomProp: string;}interface Window { myCustomMethod(): void;}// 两个同名 interface 自动合并选择建议:对象形状用 interface,联合/交叉/工具类型用 type。
五、函数类型
5.1 函数声明
// 参数和返回值标注function add(a: number, b: number): number { return a + b;}// 可选参数(? 标记,必须在必选参数后面)function greet(name: string, greeting?: string): string { return `${greeting || 'Hello'}, ${name}!`;}// 默认值(有默认值的参数自动可选)function greet2(name: string, greeting: string = 'Hello'): string { return `${greeting}, ${name}!`;}// 剩余参数function sum(...numbers: number[]): number { return numbers.reduce((a, b) => a + b, 0);}5.2 函数类型表达式
// 定义函数类型type MathFunc = (a: number, b: number) => number;// 使用const add: MathFunc = (a, b) => a + b;const subtract: MathFunc = (a, b) => a - b;5.3 函数重载
同一个函数名,根据参数类型不同执行不同逻辑:
// 重载签名function process(input: string): string;function process(input: number): number;function process(input: boolean): boolean;// 实现签名(不对外暴露)function process(input: string | number | boolean): string | number | boolean { if (typeof input === 'string') { return input.toUpperCase(); } else if (typeof input === 'number') { return input * 2; } else { return !input; }}const r1 = process('hello'); // string,类型推断为 stringconst r2 = process(42); // number,类型推断为 numberconst r3 = process(true); // boolean,类型推断为 boolean5.4 this 类型
interface Button { text: string; onClick(this: Button, event: Event): void;}const button: Button = { text: 'Click', onClick(event) { console.log(this.text); // this 被正确推断为 Button },};六、类
6.1 类的基本语法
class Person { // 属性声明 name: string; private age: number; // 私有 readonly id: number; // 只读 // 构造函数 constructor(name: string, age: number, id: number) { this.name = name; this.age = age; this.id = id; } // 方法 greet(): string { return `Hello, I'm ${this.name}`; }}const p = new Person('Alice', 25, 1);console.log(p.greet());// p.age; // ❌ private 不可外部访问6.2 参数属性(简写)
class Person { constructor( public name: string, // 自动创建 public 属性 private age: number, // 自动创建 private 属性 readonly id: number, // 自动创建 readonly 属性 ) {}}const p = new Person('Alice', 25, 1);console.log(p.name); // ✅ public6.3 访问修饰符
| 修饰符 | 类内 | 子类 | 类外 |
|---|---|---|---|
public |
✅ | ✅ | ✅ |
private |
✅ | ❌ | ❌ |
protected |
✅ | ✅ | ❌ |
readonly |
✅(读) | ✅(读) | ✅(读) |
6.4 存取器(getter/setter)
class Temperature { private _celsius: number = 0; get celsius(): number { return this._celsius; } set celsius(value: number) { if (value < -273.15) { throw new Error('温度不能低于绝对零度'); } this._celsius = value; } get fahrenheit(): number { return this._celsius * 9 / 5 + 32; }}const temp = new Temperature();temp.celsius = 25;console.log(temp.fahrenheit); // 776.5 抽象类
abstract class Animal { abstract makeSound(): void; // 抽象方法,子类必须实现 move(): void { // 普通方法 console.log('Moving...'); }}class Dog extends Animal { makeSound(): void { console.log('Woof!'); }}// const a = new Animal(); // ❌ 抽象类不能实例化const dog = new Dog();dog.makeSound(); // Woof!dog.move(); // Moving...6.6 类实现接口
interface Serializable { serialize(): string;}interface Comparable { compareTo(other: this): number;}class User implements Serializable, Comparable { constructor(public name: string, public age: number) {} serialize(): string { return JSON.stringify({ name: this.name, age: this.age }); } compareTo(other: User): number { return this.age - other.age; }}七、泛型
7.1 为什么需要泛型
// 没有 泛型:要么用 any(丢失类型),要么写多个函数function identityAny(arg: any): any { return arg;}const result = identityAny('hello'); // result 是 any,没有提示// 有 泛型:保留类型信息function identity<T>(arg: T): T { return arg;}const result = identity('hello'); // result 是 stringconst result2 = identity(42); // result2 是 number7.2 泛型函数
function first<T>(arr: T[]): T { return arr[0];}first([1, 2, 3]); // 返回 numberfirst(['a', 'b', 'c']); // 返回 string// 多个泛型参数function pair<K, V>(key: K, value: V): [K, V] { return [key, value];}pair('name', 'Alice'); // [string, string]pair('age', 25); // [string, number]7.3 泛型接口
interface Box<T> { value: T;}const stringBox: Box<string> = { value: 'hello' };const numberBox: Box<number> = { value: 42 };// API 响应通用格式interface ApiResponse<T> { code: number; message: string; data: T;}const userResp: ApiResponse<{ name: string; age: number }> = { code: 200, message: 'success', data: { name: 'Alice', age: 25 },};7.4 泛型类
class Stack<T> { private items: T[] = []; push(item: T): void { this.items.push(item); } pop(): T | undefined { return this.items.pop(); }}const numStack = new Stack<number>();numStack.push(1);numStack.push(2);const top = numStack.pop(); // number | undefined7.5 泛型约束
限制泛型必须符合某种结构:
// 约束 T 必须有 length 属性function getLength<T extends { length: number }>(arg: T): number { return arg.length;}getLength('hello'); // 5getLength([1, 2, 3]); // 3// getLength(42); // ❌ number 没有 length// 使用接口约束interface HasId { id: number;}function findById<T extends HasId>(items: T[], id: number): T | undefined { return items.find(item => item.id === id);}const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];findById(users, 1); // { id: 1, name: 'Alice' }7.6 keyof 操作符
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}const user = { name: 'Alice', age: 25 };getProperty(user, 'name'); // stringgetProperty(user, 'age'); // number// getProperty(user, 'email'); // ❌ 'email' 不是 user 的 key第二部分:高级
八、联合类型与交叉类型
8.1 联合类型(Union)
// 一个值可以是多种类型之一let id: string | number;id = 'abc';id = 123;// 联合字面量类型type Status = 'pending' | 'success' | 'error';let status: Status = 'pending';8.2 交叉类型(Intersection)
// 多个类型合并为一个type Person = { name: string };type Employee = { employeeId: number };type EmployeePerson = Person & Employee;const ep: EmployeePerson = { name: 'Alice', employeeId: 1001,};8.3 可辨识联合
// 用一个公共属性区分不同类型interface Circle { kind: 'circle'; // 可辨识属性 radius: number;}interface Square { kind: 'square'; sideLength: number;}type Shape = Circle | Square;function getArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2; // shape 被收窄为 Circle case 'square': return shape.sideLength ** 2; // shape 被收窄为 Square }}8.4 类型守卫
// typeof —— 基本类型判断function pad(value: string | number): string { if (typeof value === 'number') { return value.toFixed(2); // value 被收窄为 number } return value.toUpperCase(); // value 被收窄为 string}// instanceof —— 类实例判断class Cat { meow(): void {} }class Dog { bark(): void {} }function speak(animal: Cat | Dog): void { if (animal instanceof Cat) { animal.meow(); // 收窄为 Cat } else { animal.bark(); // 收窄为 Dog }}// in —— 属性存在判断interface Fish { swim(): void }interface Bird { fly(): void }function move(animal: Fish | Bird): void { if ('swim' in animal) { animal.swim(); // 收窄为 Fish } else { animal.fly(); // 收窄为 Bird }}// 自定义类型谓词function isError(x: any): x is Error { return x instanceof Error;}try { // ...} catch (e) { if (isError(e)) { console.log(e.message); // e 被收窄为 Error }}九、类型断言与类型收窄
9.1 类型断言
// 方式 1:as 语法(推荐)let someValue: unknown = 'hello';let strLength: number = (someValue as string).length;// 方式 2:尖括号语法(JSX 中不可用)let strLength2: number = (<string>someValue).length;// 断言为更具体的类型const myCanvas = document.getElementById('main') as HTMLCanvasElement;const input = document.querySelector('input') as HTMLInputElement;input.value = 'test';9.2 非空断言
// ! 告诉 TS "这个值一定不是 null/undefined"function processValue(value: string | null) { // value.toUpperCase(); // ❌ 可能是 null value!.toUpperCase(); // ✅ 非空断言}// 可选链 + 非空断言const element = document.querySelector('.btn')!;element.addEventListener('click', () => {});警告:非空断言是"我保证不是 null",如果运行时真的是 null,会报错。尽量用可选链代替。
9.3 类型收窄的常见方式
function process(value: string | number | null) { // 1. 条件判断 if (value === null) { return; // value 被收窄为 null } // 2. typeof if (typeof value === 'string') { value.toUpperCase(); // string } else { value.toFixed(2); // number } // 3. 真值检查 if (value) { // value: string | number(排除了 null 和 空字符串和 0) }}9.4 satisfies 操作符(TS 4.9+)
// 问题:用 as 断言会丢失类型检查const config = { port: 3000,} as { port: number };// config.port = 'abc'; // 被错误允许// 解决:satisfies 保留精确类型,同时验证是否符合目标类型const config2 = { port: 3000,} satisfies { port: number };// config2.port 被推断为 3000(字面量类型),更精确十、映射类型与条件类型
10.1 keyof 操作符
interface User { name: string; age: number; email: string;}type UserKeys = keyof User; // 'name' | 'age' | 'email'10.2 映射类型
// 把所有属性变成只读type Readonly<T> = { readonly [P in keyof T]: T[P];};// 把所有属性变成可选type Partial<T> = { [P in keyof T]?: T[P];};// 实际使用type ReadonlyUser = Readonly<User>;type PartialUser = Partial<User>;// 修饰符映射:- 表示移除修饰符type Mutable<T> = { -readonly [P in keyof T]: T[P]; // 移除 readonly};10.3 条件类型
// 语法:T extends U ? X : Ytype IsString<T> = T extends string ? 'yes' : 'no';type A = IsString<string>; // 'yes'type B = IsString<number>; // 'no'type C = IsString<any>; // 'yes' | 'no'(分配律)10.4 infer 关键字
在条件类型中提取类型:
// 提取函数返回值类型type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;type R1 = ReturnType<() => string>; // stringtype R2 = ReturnType<(x: number) => boolean>; // boolean// 提取 Promise 的值类型type Awaited<T> = T extends Promise<infer U> ? U : T;type R3 = Awaited<Promise<number>>; // numbertype R4 = Awaited<Promise<string>>; // string// 提取数组元素类型type ElementOf<T> = T extends (infer E)[] ? E : never;type R5 = ElementOf<string[]>; // stringtype R6 = ElementOf<number[]>; // number// 提取构造函数参数类型type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;10.5 条件类型的分配律
// 裸类型参数会分配type ToArray<T> = T extends any ? T[] : never;type R = ToArray<string | number>;// 等价于 ToArray<string> | ToArray<number>// 结果:string[] | number[]// 阻止分配:用方括号包裹type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;type R2 = ToArrayNonDist<string | number>;// 结果:(string | number)[]十一、内置工具类型
11.1 属性操作类
interface User { name: string; age: number; email?: string;}// Partial —— 所有属性变可选type PartialUser = Partial<User>;// { name?: string; age?: number; email?: string; }// Required —— 所有属性变必选type RequiredUser = Required<User>;// { name: string; age: number; email: string; }// Readonly —— 所有属性变只读type ReadonlyUser = Readonly<User>;// { readonly name: string; readonly age: number; readonly email?: string; }// Pick —— 选取部分属性type UserName = Pick<User, 'name' | 'age'>;// { name: string; age: number; }// Omit —— 排除部分属性type UserWithoutAge = Omit<User, 'age'>;// { name: string; email?: string; }11.2 类型转换类
// Record —— 构造键值对类型type UserMap = Record<string, User>;const users: UserMap = { alice: { name: 'Alice', age: 25 },};// ReturnType —— 获取函数返回值类型function getUser() { return { name: 'Alice', age: 25 }; }type User = ReturnType<typeof getUser>;// { name: string; age: number; }// Parameters —— 获取函数参数类型(元组)function add(a: number, b: string) {}type AddParams = Parameters<typeof add>;// [number, string]// ConstructorParameters —— 构造函数参数类型// InstanceType —— 获取构造函数实例类型class Person { constructor(public name: string) {} }type P = InstanceType<typeof Person>; // Person// Awaited —— 递归解包 Promisetype R = Awaited<Promise<Promise<string>>>; // string11.3 字符串操作类(TS 4.1+)
// Uppercase / Lowercasetype Upper = Uppercase<'hello'>; // 'HELLO'type Lower = Lowercase<'WORLD'>; // 'world'// Capitalize / Uncapitalizetype Cap = Capitalize<'hello'>; // 'Hello'type Uncap = Uncapitalize<'Hello'>; // 'hello'11.4 工具类型速查表
| 工具类型 | 作用 |
|---|---|
Partial<T> |
所有属性可选 |
Required<T> |
所有属性必选 |
Readonly<T> |
所有属性只读 |
Pick<T, K> |
选取 K 指定的属性 |
Omit<T, K> |
排除 K 指定的属性 |
Record<K, V> |
构造 { K: V } 键值对 |
ReturnType<F> |
函数返回值类型 |
Parameters<F> |
函数参数类型(元组) |
Awaited<P> |
解包 Promise |
InstanceType<C> |
构造函数实例类型 |
NonNullable<T> |
排除 null 和 undefined |
keyof T |
获取所有键的联合类型 |
typeof X |
获取变量的类型 |
十二、自定义工具类型
实战中常用的自定义类型工具:
// 1. 获取对象某个属性的类型type PropType<T, K extends keyof T> = T[K];interface User { name: string; age: number; }type NameType = PropType<User, 'name'>; // string// 2. 将指定键变为可选type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;type OptionalAge = OptionalKeys<User, 'age'>;// { name: string; age?: number; }// 3. 将指定键变为必选type RequiredKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;// 4. 深层 Partialtype DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];};interface Config { server: { host: string; port: number }; db: { url: string; timeout: number };}type DeepPartialConfig = DeepPartial<Config>;// 所有层级属性都变可选// 5. 深层 Readonlytype DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];};// 6. 获取函数的所有重载type Overloads<T> = T extends { (...args: infer A): any; (...args: infer B): any;} ? A | B : T;// 7. 联合类型转交叉类型type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;// 8. 排除 null 和 undefined 的简写type NonNull<T> = NonNullable<T>;// 9. 获取 Promise 链的最终类型type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;// 10. 严格的对象类型(不允许多余属性)type Strict<T> = { [P in keyof T]: T[P];} & { [P in Exclude<keyof any, keyof T>]: never;};十三、装饰器
13.1 类装饰器
// 类装饰器:接收构造函数,返回新的构造函数function Logged<T extends { new(...args: any[]): {} }>(constructor: T) { return class extends constructor { created_at = new Date().toISOString(); };}@Loggedclass User { constructor(public name: string) {}}const user = new User('Alice');console.log((user as any).created_at); // 2024-01-01T00:00:00.000Z13.2 方法装饰器
// 方法装饰器:接收 (target, propertyKey, descriptor)function Log(target: any, key: string, desc: PropertyDescriptor) { const original = desc.value; desc.value = function(...args: any[]) { console.log(`调用 ${key},参数: ${args}`); const result = original.apply(this, args); console.log(`返回: ${result}`); return result; };}class Calculator { @Log add(a: number, b: number): number { return a + b; }}new Calculator().add(1, 2);// 调用 add,参数: 1,2// 返回: 313.3 装饰器工厂
// 工厂函数:返回装饰器,可以传参function Route(path: string) { return function (target: any, key: string, desc: PropertyDescriptor) { // 把路由信息存到元数据 Reflect.defineMetadata('path', path, target, key); };}class UserController { @Route('/users/:id') getUser() {} @Route('/users') getUsers() {}}13.4 属性装饰器与参数装饰器
// 属性装饰器function Column(name: string) { return function (target: any, propertyKey: string) { // 标记数据库列名 Reflect.defineMetadata('column', name, target, propertyKey); };}// 参数装饰器function Inject(token: string) { return function (target: any, key: string, index: number) { Reflect.defineMetadata('inject', token, target, key, index); };}class User { @Column('user_name') name: string; constructor(@Inject('DB') db: any) {}}注意:装饰器是实验性特性,需要在 tsconfig 中启用
"experimentalDecorators": true。
十四、模块与声明文件
14.1 模块导出导入
// math.tsexport function add(a: number, b: number): number { return a + b;}export const PI = 3.14159;// 默认导出(一个模块只能有一个)export default function multiply(a: number, b: number): number { return a * b;}// main.tsimport multiply, { add, PI } from './math';import * as Math from './math';// 类型导出export interface User { name: string; }export type ID = string | number;14.2 声明文件(.d.ts)
为 JS 库提供类型声明:
// express.d.ts —— 为没有类型的 JS 库补声明declare module 'express' { interface Request { user?: { id: number; name: string }; } interface Response { json(data: any): void; } const express: () => any; export default express;}// 全局变量声明declare const APP_VERSION: string;declare const API_BASE_URL: string;// 全局函数declare function gtag(...args: any[]): void;14.3 三斜线指令
// 引用其他声明文件(现代项目用 import 代替)/// <reference types="node" />/// <reference path="./custom.d.ts" />14.4 命名空间(namespace)
// 早期 TS 的模块化方案,现代项目推荐用 ES Modulenamespace Utils { export function formatDate(date: Date): string { return date.toISOString(); } export const VERSION = '1.0.0';}Utils.formatDate(new Date());十五、tsconfig.json 配置
15.1 常用配置
{ "compilerOptions": { /* 基础 */ "target": "ES2020", // 编译目标 JS 版本 "module": "commonjs", // 模块系统 "lib": ["ES2020", "DOM"], // 可用的 API(DOM 表示浏览器 API) "outDir": "./dist", // 输出目录 "rootDir": "./src", // 源码目录 /* 严格检查 */ "strict": true, // 开启所有严格检查(推荐) "noImplicitAny": true, // 禁止隐式 any "strictNullChecks": true, // null/undefined 检查 "strictFunctionTypes": true, // 函数类型严格检查 "noUnusedLocals": true, // 未使用的变量报错 "noUnusedParameters": true, // 未使用的参数报错 "noImplicitReturns": true, // 函数所有路径必须返回 /* 模块解析 */ "moduleResolution": "node", // 模块解析策略 "esModuleInterop": true, // 允许 import express from 'express' "allowSyntheticDefaultImports": true, "resolveJsonModule": true, // 允许 import json /* 路径别名 */ "baseUrl": ".", // 路径解析基准 "paths": { "@/*": ["src/*"], // @/xxx → src/xxx "@components/*": ["src/components/*"] }, /* 实验性 */ "experimentalDecorators": true, // 装饰器 "emitDecoratorMetadata": true, // 装饰器元数据 /* Source Map */ "sourceMap": true, // 生成 source map /* 跳过类型检查 */ "skipLibCheck": true, // 跳过 .d.ts 检查(加速) "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]}15.2 strict 模式包含的检查
| 选项 | 说明 |
|---|---|
strictNullChecks |
null/undefined 不能赋值给其他类型 |
noImplicitAny |
禁止隐式 any |
strictFunctionTypes |
函数参数双向检查 → 逆变检查 |
strictBindCallApply |
bind/call/apply 严格类型 |
strictPropertyInitialization |
类属性必须初始化 |
noImplicitThis |
this 必须有明确类型 |
alwaysStrict |
编译输出加 'use strict' |
十六、常见面试题
Q1:any 和 unknown 的区别?
any:放弃类型检查,可以任意操作unknown:安全的 any,使用前必须先检查类型或断言- 推荐:尽量用
unknown代替any
Q2:interface 和 type 的区别?
interface只能描述对象类型,type可以描述任何类型interface支持声明合并,type不支持interface用extends继承,type用&交叉- 对象形状优先用
interface,联合/交叉/工具类型用type
Q3:never 类型有什么用?
- 表示永远不会出现的类型
- 用于穷尽检查:
switch的default分支赋值给never,漏写 case 会报错 - 用于过滤:
Exclude<T, never>会移除 never
Q4:什么是泛型?为什么要用?
- 泛型是参数化类型,让类型像变量一样传递
- 作用:复用代码同时保留类型安全
- 示例:
function identity<T>(arg: T): T
Q5:keyof 和 typeof 的区别?
keyof T:获取类型 T 的所有键的联合类型typeof x:获取变量 x 的类型
const config = { port: 3000 };type Config = typeof config; // { port: number }type Keys = keyof Config; // 'port'Q6:什么是类型守卫?
- 一种运行时检查,TS 据此收窄类型
typeof、instanceof、in、自定义类型谓词(x is T)
Q7:as 和 satisfies 的区别?
as:类型断言,改变类型但不检查satisfies:验证是否符合目标类型,同时保留原始精确类型
Q8:什么是条件类型的分配律?
- 裸类型参数在条件类型中会分配
T extends U ? X : Y中 T 是联合类型时,会分别判断每个成员- 用
[T] extends [U]阻止分配
Q9:Partial<T> 的实现原理?
type Partial<T> = { [P in keyof T]?: T[P];};keyof T获取所有键in遍历每个键?把每个属性变可选
Q10:infer 关键字的作用?
- 在条件类型中提取类型变量
- 只能在
extends条件类型的右侧使用 - 示例:
T extends Promise<infer R> ? R : T
Q11:什么是声明文件?
.d.ts文件,为 JS 代码提供类型信息- 不包含实现代码,只有类型声明
declare module 'xxx'为第三方库补充类型
Q12:枚举的潜在问题?
- 数字枚举会生成反向映射代码,增加体积
const enum会被内联,不生成额外代码- 字符串枚举没有反向映射,更安全
Q13:什么是结构类型系统?
- TS 采用结构类型(鸭子类型):只要结构匹配就是同一类型
- 与名义类型(Java/C#)不同,不关心类型名称
interface Point { x: number; y: number }interface Coordinate { x: number; y: number }const p: Point = { x: 1, y: 2 }; // ✅const c: Coordinate = p; // ✅ 结构相同就行Q14:abstract 和 interface 的区别?
abstract class可以有实现代码,interface不能abstract class用extends,interface用implements- 一个类只能继承一个抽象类,但可以实现多个接口
Q15:如何在运行时获取 TS 类型?
- 不能。TS 类型在编译后会被擦除(类型擦除)
- 运行时只有 JS,没有类型信息
- 如果需要运行时验证,用
class-validator、zod等库
十七、最佳实践
- 开启 strict 模式:
"strict": true,尽早发现类型问题 - 禁止
any:用unknown代替,或定义明确的类型 - 优先用
interface:对象形状用 interface,联合/交叉用 type - 善用工具类型:
Partial、Pick、Omit减少重复定义 - 函数返回值标注:便于理解,也便于 IDE 提示
- 用
unknown处理外部数据:API 响应、用户输入先当unknown,再验证 - 路径别名:
@/代替../../../,配置paths - 避免类型断言滥用:
as是告诉编译器"我比你懂",用错了会掩盖 bug - 用
satisfies代替as(TS 4.9+):既验证类型又保留精确推断 - 运行时验证用专门库:
zod、class-validator、joi,不要只用 TS 类型
总结
TypeScript 的核心可以归纳为两层:
- 基础层:类型注解 + 接口 + 泛型 —— 日常编码 80% 的场景
- 高级层:条件类型 + 映射类型 + infer + 工具类型 —— 写类型工具和库时用
掌握基础层就能写出类型安全的业务代码;掌握高级层才能写出可复用的类型工具,理解框架源码中的类型魔法。
TypeScript 的本质是在编译时捕获错误——越早发现问题,修复成本越低。 类型系统不是束缚,而是和编译器的对话:你告诉它你的意图,它帮你检查是否符合。