全栈手账 订阅 RSS

TypeScript 知识总结(基础 + 高级)

TypeScript 是 JavaScript 的超集,为其添加静态类型系统,编译后生成纯 JS。它解决了 JS 的类型不安全、重构困难、IDE 提示弱三大痛点,本文覆盖基础类型、泛型与高级类型技巧。

读了 54 分钟 更新于 8 月 10 日

第一部分:基础

一、TypeScript 简介

1.1 什么是 TypeScript

  • 微软开发的编程语言,2012 年发布
  • JavaScript 的超集——所有合法的 JS 代码都是合法的 TS 代码
  • 添加了静态类型系统,编译时检查类型错误
  • 编译后生成纯 JS,运行时没有类型信息(类型擦除)

1.2 TS vs JS

对比项 TypeScript JavaScript
类型系统 静态类型(编译时检查) 动态类型(运行时才知道)
错误发现 编译时(早) 运行时(晚)
IDE 支持 智能提示、跳转定义、重构 较弱
学习曲线 需要学类型语法 直接写
运行环境 需编译为 JS 直接运行
适合项目 中大型项目 小型项目、脚本

1.3 安装与编译

bash
# 全局安装npm install -g typescript# 初始化配置文件tsc --init# 编译单个文件tsc index.ts# 监听模式(文件变化自动编译)tsc -w# 验证版本tsc -v

1.4 第一个 TS 程序

typescript
// index.tsfunction greet(name: string): string {  return `Hello, ${name}!`;}const message: string = greet('World');console.log(message);
bash
tsc index.ts    # 生成 index.jsnode index.js   # Hello, World!

二、基础类型

2.1 常用类型一览

typescript
// 基本类型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

typescript
// 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 字面量类型

typescript
// 字符串字面量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 变量的类型:

typescript
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 会自动推断变量的类型,不需要每次都写注解:

typescript
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 什么时候需要手动标注

场景 是否需要标注
变量声明时有初始值 ❌ 自动推断
函数参数 ✅ 必须
函数返回值 可选(但推荐标注,便于理解)
变量声明时无初始值 ✅ 必须
复杂类型(联合、交叉) ✅ 推荐
typescript
// ✅ 有初始值,不需要标注let count = 0;// ❌ 无初始值,必须标注let count: number;count = 10;// ✅ 函数参数必须标注function greet(name: string) {}// ✅ 返回值推荐标注function getUser(): User {  return { name: 'Alice', age: 25 };}

四、接口与类型别名

4.1 接口(interface)

typescript
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 索引签名

typescript
interface StringArray {  [index: number]: string;  // 数组式访问}let arr: StringArray = ['a', 'b', 'c'];interface Config {  [key: string]: any;       // 任意字符串键  name: string;             // 具体属性也必须符合索引签名}

4.3 接口继承

typescript
interface Animal {  name: string;}interface Dog extends Animal {  bark(): void;}const dog: Dog = {  name: 'Rex',  bark() { console.log('Woof!'); },};

4.4 类型别名(type)

typescript
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 & 交叉
推荐 对象/类类型 联合/交叉/工具类型
typescript
// 声明合并(只有 interface 支持)interface Window {  myCustomProp: string;}interface Window {  myCustomMethod(): void;}// 两个同名 interface 自动合并

选择建议:对象形状用 interface,联合/交叉/工具类型用 type。


五、函数类型

5.1 函数声明

typescript
// 参数和返回值标注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 函数类型表达式

typescript
// 定义函数类型type MathFunc = (a: number, b: number) => number;// 使用const add: MathFunc = (a, b) => a + b;const subtract: MathFunc = (a, b) => a - b;

5.3 函数重载

同一个函数名,根据参数类型不同执行不同逻辑:

typescript
// 重载签名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,类型推断为 boolean

5.4 this 类型

typescript
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 类的基本语法

typescript
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 参数属性(简写)

typescript
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);  // ✅ public

6.3 访问修饰符

修饰符 类内 子类 类外
public ✅ ✅ ✅
private ✅ ❌ ❌
protected ✅ ✅ ❌
readonly ✅(读) ✅(读) ✅(读)

6.4 存取器(getter/setter)

typescript
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);  // 77

6.5 抽象类

typescript
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 类实现接口

typescript
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 为什么需要泛型

typescript
// 没有 泛型:要么用 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 是 number

7.2 泛型函数

typescript
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 泛型接口

typescript
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 泛型类

typescript
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 | undefined

7.5 泛型约束

限制泛型必须符合某种结构:

typescript
// 约束 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
typescript
// 使用接口约束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 操作符

typescript
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)

typescript
// 一个值可以是多种类型之一let id: string | number;id = 'abc';id = 123;// 联合字面量类型type Status = 'pending' | 'success' | 'error';let status: Status = 'pending';

8.2 交叉类型(Intersection)

typescript
// 多个类型合并为一个type Person = { name: string };type Employee = { employeeId: number };type EmployeePerson = Person & Employee;const ep: EmployeePerson = {  name: 'Alice',  employeeId: 1001,};

8.3 可辨识联合

typescript
// 用一个公共属性区分不同类型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 类型守卫

typescript
// 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 类型断言

typescript
// 方式 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 非空断言

typescript
// ! 告诉 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 类型收窄的常见方式

typescript
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+)

typescript
// 问题:用 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 操作符

typescript
interface User {  name: string;  age: number;  email: string;}type UserKeys = keyof User;  // 'name' | 'age' | 'email'

10.2 映射类型

typescript
// 把所有属性变成只读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 条件类型

typescript
// 语法: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 关键字

在条件类型中提取类型:

typescript
// 提取函数返回值类型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 条件类型的分配律

typescript
// 裸类型参数会分配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 属性操作类

typescript
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 类型转换类

typescript
// 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>>>;  // string

11.3 字符串操作类(TS 4.1+)

typescript
// 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 获取变量的类型

十二、自定义工具类型

实战中常用的自定义类型工具:

typescript
// 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 类装饰器

typescript
// 类装饰器:接收构造函数,返回新的构造函数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.000Z

13.2 方法装饰器

typescript
// 方法装饰器:接收 (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// 返回: 3

13.3 装饰器工厂

typescript
// 工厂函数:返回装饰器,可以传参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 属性装饰器与参数装饰器

typescript
// 属性装饰器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 模块导出导入

typescript
// 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 库提供类型声明:

typescript
// 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 三斜线指令

typescript
// 引用其他声明文件(现代项目用 import 代替)/// <reference types="node" />/// <reference path="./custom.d.ts" />

14.4 命名空间(namespace)

typescript
// 早期 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 常用配置

json
{  "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 的类型
typescript
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> 的实现原理?

typescript
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#)不同,不关心类型名称
typescript
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 等库

十七、最佳实践

  1. 开启 strict 模式:"strict": true,尽早发现类型问题
  2. 禁止 any:用 unknown 代替,或定义明确的类型
  3. 优先用 interface:对象形状用 interface,联合/交叉用 type
  4. 善用工具类型:Partial、Pick、Omit 减少重复定义
  5. 函数返回值标注:便于理解,也便于 IDE 提示
  6. 用 unknown 处理外部数据:API 响应、用户输入先当 unknown,再验证
  7. 路径别名:@/ 代替 ../../../,配置 paths
  8. 避免类型断言滥用:as 是告诉编译器"我比你懂",用错了会掩盖 bug
  9. 用 satisfies 代替 as(TS 4.9+):既验证类型又保留精确推断
  10. 运行时验证用专门库:zod、class-validator、joi,不要只用 TS 类型

总结

TypeScript 的核心可以归纳为两层:

  • 基础层:类型注解 + 接口 + 泛型 —— 日常编码 80% 的场景
  • 高级层:条件类型 + 映射类型 + infer + 工具类型 —— 写类型工具和库时用

掌握基础层就能写出类型安全的业务代码;掌握高级层才能写出可复用的类型工具,理解框架源码中的类型魔法。

TypeScript 的本质是在编译时捕获错误——越早发现问题,修复成本越低。 类型系统不是束缚,而是和编译器的对话:你告诉它你的意图,它帮你检查是否符合。

前端、后端、部署、客服都是我一个人。写这里的东西都是真踩过的坑,不是抄来的教程。