typescript基础

TypeScript

一、TypeScript 简介

TypeScript (TS) 是 JavaScript (JS) 的超集,在 JS 基础上增加静态类型系统,最终编译成普通 JS 运行,可运行在任何 JS 环境。

简单说:JS 是动态类型,运行才报错;TS 静态类型,写代码阶段就能发现错误。

二、为何需要 TypeScript

  1. JS 弱类型,变量类型随意改变,大型项目容易出现隐蔽运行时错误

  2. TS 编译阶段做类型检查,提前捕获 bug

  3. 类型提示增强代码可读性,方便团队协作、重构代码

  4. 支持更新 ES 语法,编译向下兼容旧浏览器

示例:JS 隐患

JavaScript 复制代码
function add(a, b) {
  return a + b
}
console.log(add(1, '2')) // 得到"12",不是预期3,JS不会报错

TS 版本会直接报错:b 期望数字,传入字符串。

三、编译 TypeScript

1. 命令行编译

  • 安装 ts 编译器:npm install -g typescript

  • 新建 demo.ts 写 ts 代码

  • 终端执行:tsc demo.ts,生成 demo.js

  • 执行 js:node demo.js

2. 自动化编译

生成配置文件:tsc --init,产出 tsconfig.json

  • tsc:读取配置批量编译全部 ts 文件

  • tsc -w:监听文件改动,文件保存自动重新编译

3. tsconfig.json 关键配置

JSON 复制代码
{
  "compilerOptions": {
    "target": "ES2020",             // 编译目标 JS 版本
    "module": "commonjs",           // 模块系统
    "outDir": "./dist",             // 输出 js 存放文件夹
    "rootDir": "./src",             // ts 源码目录
    "strict": true,                 // 开启严格模式(推荐)
    "strictNullChecks": true,       // null/undefined 不能赋值给其他类型
    "noImplicitAny": true,          // 不允许隐式 any
    "esModuleInterop": true,        // 兼容 ES 模块和 CommonJS 互导
    "skipLibCheck": true,           // 跳过声明文件类型检查
    "experimentalDecorators": true, // 开启装饰器
    "emitDecoratorMetadata": true   // 装饰器元数据
  }
}

四、类型声明

显式指定变量类型,格式:let 变量: 类型 = 值

TypeScript 复制代码
let num: number = 10
let str: string = "hello"
let flag: boolean = true

num = 20
// num = "abc" // 报错,不能把字符串赋值给 number

五、类型推断

TS 可以自动识别变量类型,不需要每次手写类型。

TypeScript 复制代码
let a = 10 // 推断 a 是 number 类型
// a = "hi" // 报错

let b // 只声明不赋值,推断为 any 类型,可以随便赋值
b = 1
b = "test"

最佳实践: 能推断就不写类型;函数参数、返回值建议手动声明。

六、类型总览

分类 类型
基础类型 number、string、boolean、null、undefined、symbol、bigint
特殊类型 any、unknown、void、never
复合类型 object、数组、元组 tuple、枚举 enum
高级类型 联合类型、交叉类型、字面量类型
自定义类型 type、interface、泛型

七、常用类型与语法

1. any

任意类型,关闭 TS 类型检查,可以任意赋值、调用属性方法,尽量少用。

TypeScript 复制代码
let x: any = 1
x = "abc"
x = true
x.foo() // 不会报错,运行才会炸

2. unknown

未知类型,安全版 any。允许赋值任意值,但不能直接操作,必须做类型判断。

TypeScript 复制代码
let y: unknown = 100
y = "hello"
// y.toUpperCase() // 直接调用报错

if (typeof y === 'string') {
  y.toUpperCase() // 类型收窄后可以正常调用
}

3. never

永远不会发生的值。函数抛出异常、死循环返回值是 never。

TypeScript 复制代码
function error(): never {
  throw new Error("出错!")
}

never 的核心用途:穷尽性检查(exhaustive check)

TypeScript 复制代码
type Shape = 'circle' | 'square'
function area(s: Shape) {
  switch (s) {
    case 'circle': return Math.PI
    case 'square': return 4
    default:
      const _exhaustive: never = s // 如果 Shape 新增类型忘了处理,这里编译报错
      return _exhaustive
  }
}

4. void

函数没有返回值。

TypeScript 复制代码
function log(): void {
  console.log("输出")
}

void 和 undefined 的区别是,不允许用void的结果来干任何事情。

5. any vs unknown vs never 对比

类型 能赋值给它 它能赋值给 直接操作 使用场景
any 任意类型 任意类型 可以 旧代码迁移、临时绕过
unknown 任意类型 仅 any / unknown 不可以(需收窄) 不确定类型但要安全
never 无(没有值是 never) 任意类型 不可以 抛异常、死循环、穷尽检查

6. object

对象类型,描述非原始值。

TypeScript 复制代码
let obj: object = { name: "张三" }

// 推荐:直接写对象字面量类型
let user: { name: string, age: number } = { name: "张三", age: 18 }

7. 数组

两种写法等价:

TypeScript 复制代码
let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]

// 联合类型数组
let mixed: (string | number)[] = [1, "a", 2]

8. tuple 元组

固定长度、固定每个位置类型的数组。

TypeScript 复制代码
let t: [string, number] = ["李四", 20]
t[0].length
// t[2] = 10 // 超出长度报错

// 可选元素
let coord: [number, number, number?] = [1, 2]

// 剩余元素
type Rest = [string, ...number[]] // 第一个 string,后面任意个 number

9. enum 枚举

给一组常量命名。

TypeScript 复制代码
enum Color {
  Red,
  Green,
  Blue
}
console.log(Color.Red)  // 0
console.log(Color[0])   // Red

// 手动赋值
enum Status {
  Success = 200,
  Error = 500
}

// 字符串枚举
enum Direction {
  Up = "UP",
  Down = "DOWN"
}

// const enum(编译后完全移除,性能更好,不能反向映射)
const enum Week { Mon, Tue }
let w = Week.Mon // 编译后直接变成 0

10. null 和 undefined

strict 模式下(strictNullChecks: true),null 和 undefined 不能直接赋值给其他类型,可以联合使用。

TypeScript 复制代码
let n: number | null = null
n = 99

可选链 ?. 和空值合并 ****??(配合严格空检查使用):

TypeScript 复制代码
let obj2 = { name: "a", info: null }
console.log(obj2.info?.age) // undefined,不会报错
let val = obj2.info ?? "默认值" // 左侧为 null/undefined 时取右侧

11. 类型断言

告诉 TS "我比你更清楚这个类型",两种写法:

TypeScript 复制代码
let someValue: unknown = "hello"
let strLen: number = (someValue as string).length

// 或尖括号写法(JSX 中不可用):
let strLen2: number = (<string>someValue).length

as const****:断言为字面量只读类型

TypeScript 复制代码
let arr = [1, 2, 3] as const        // readonly [1, 2, 3]
let obj = { a: 1 } as const         // { readonly a: 1 }

双重断言(不推荐,慎用):

TypeScript 复制代码
let x: string = "hi"
let y: number = x as unknown as number

八、联合类型 & 交叉类型

1. 联合类型(Union Types)|

表示"或"的关系,值可以是多种类型中的一种。

TypeScript 复制代码
type Id = number | string
let id: Id = 123
id = "abc123"

// 联合类型只能访问共有的属性/方法
function print(val: string | number) {
  // val.toFixed() // 报错,string 没有 toFixed
  val.toString() // 都有,没问题
}

2. 交叉类型(Intersection Types)&

表示"且"的关系,同时具有多个类型的所有属性。

TypeScript 复制代码
type A = { name: string }
type B = { age: number }
type Person = A & B // 同时有 name 和 age
let p: Person = { name: "张三", age: 18 }

interface 用 extends 实现类似效果,但交叉可以用于任意类型。

九、字面量类型 & 模板字面量类型

1. 字面量类型

把值本身作为类型,比基本类型更精确。

TypeScript 复制代码
type Direction = "up" | "down" | "left" | "right"
let d: Direction = "up"
// d = "north" // 报错

// 数字字面量
type StatusCode = 200 | 404 | 500

// 布尔字面量
type True = true

2. 模板字面量类型

用模板字符串语法组合字符串类型。

TypeScript 复制代码
type EventName = `on${Capitalize<string>}Change`
// "onFooChange"、"onBarChange" 都符合

type Greeting = `hello_${string}`
let g: Greeting = "hello_world"

十、类型收窄(Type Narrowing)/ 类型守卫

通过判断把宽类型收窄成更精确的类型。

1. typeof 收窄(原始类型)

TypeScript 复制代码
function fn(val: string | number) {
  if (typeof val === "string") {
    val.toUpperCase() // string
  } else {
    val.toFixed() // number
  }
}

2. instanceof 收窄(类)

TypeScript 复制代码
class Dog { bark() {} }
class Cat { meow() {} }

function sound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark()
  } else {
    animal.meow()
  }
}

3. in 操作符收窄(对象属性)

TypeScript 复制代码
type Fish = { swim: () => void }
type Bird = { fly: () => void }

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim()
  } else {
    animal.fly()
  }
}

4. 字面量相等收窄

TypeScript 复制代码
type Circle = { kind: "circle", r: number }
type Square = { kind: "square", side: number }

function area(shape: Circle | Square) {
  if (shape.kind === "circle") { // 可辨识联合(Discriminated Union)
    return Math.PI * shape.r ** 2
  }
  return shape.side ** 2
}

可辨识联合 :每个类型都有一个共同的字面量属性(如 kind)用来区分。

5. 自定义类型谓词(is 关键字)

函数返回值用 参数 is 类型 形式:

TypeScript 复制代码
function isString(val: unknown): val is string {
  return typeof val === "string"
}

let arr: unknown[] = [1, "a", 2, "b"]
let strs = arr.filter(isString) // 自动推断为 string[]

6. 非空断言 !

确定值一定不是 null/undefined 时用。

TypeScript 复制代码
let el = document.getElementById("app")
el!.innerHTML = "hi" // 告诉 TS el 一定存在

十一、type 类型别名 & interface 接口

1. type 类型别名

给类型起别名,复用类型。

TypeScript 复制代码
type User = {
  name: string
  age: number
}
let u: User = { name: "小明", age: 16 }

type Id = number | string
type Callback = (a: number, b: number) => number

2. interface 接口

定义对象结构,支持继承,多用于描述对象。

TypeScript 复制代码
interface IPerson {
  name: string
  age: number
  gender?: string        // 可选属性
  readonly id: number    // 只读属性
}

let ip: IPerson = { name: "小红", age: 22, id: 1 }

索引签名:任意字符串 key

TypeScript 复制代码
interface Dict {
  [key: string]: string
}
let dict: Dict = { a: "1", b: "2" }

接口继承:

TypeScript 复制代码
interface IStudent extends IPerson {
  stuId: number
}

// 接口多继承
interface IWorker extends IPerson, IStudent {
  salary: number
}

3. type 与 interface 的区别

特性 type interface
描述对象 可以 可以
联合/交叉类型 可以 不可以
描述原始类型别名 可以 不可以
描述函数类型 可以 可以
描述元组 可以 不可以
声明合并 不可以 可以
继承方式 交叉类型 & extends
报错信息 展开属性 显示接口名
映射类型 可以用 in 不可以

4. 声明合并(Declaration Merging)

interface 可以多次声明,自动合并。

TypeScript 复制代码
interface Box {
  height: number
}
interface Box {
  width: number
}
// 合并后 Box 同时有 height 和 width
let box: Box = { height: 10, width: 20 }

常见用途:扩展第三方库类型、扩展全局 Window 等

TypeScript 复制代码
declare global {
  interface Window {
    myGlobal: string
  }
}

十二、类(Class)

1. 类基础

TypeScript 复制代码
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
  sayHi(): void {
    console.log(`hi ${this.name}`)
  }
}
const p = new Person("小王")
p.sayHi()

2. 属性修饰符

修饰符 说明
public 公开,默认,内外都可访问
private 私有,仅类内部访问(子类也不行)
protected 受保护,类内部、子类可以访问
readonly 只读,不能修改
static 静态属性/方法,属于类本身,不属于实例
TypeScript 复制代码
class A {
  public a: number
  private b: number
  protected c: number
  readonly d: number
  static version: string = "1.0"

  constructor() {
    this.a = 1
    this.b = 2
    this.c = 3
    this.d = 4
  }
}
// A.version 直接访问

3. 抽象类 abstract

不能 new 实例,用来被继承,可以有抽象方法(子类必须实现)。

TypeScript 复制代码
abstract class Animal {
  abstract run(): void      // 抽象方法,没有实现
  eat(): void {
    console.log("吃")       // 普通方法可以有实现
  }
}

class Dog extends Animal {
  run() {
    console.log("狗跑")
  }
}

// const ani = new Animal() // 报错不能实例化
const dog = new Dog()
dog.run()

4. 类实现接口(implements)

类必须实现接口定义的所有属性和方法。

TypeScript 复制代码
interface Flyable {
  fly(): void
}

class Bird implements Flyable {
  fly() {
    console.log("飞")
  }
}

// 一个类可以实现多个接口
class Plane implements Flyable, Runnable { /* ... */ }

5. 类的 this 类型

在方法中返回 this 可以实现链式调用,子类继承后类型自动匹配。

TypeScript 复制代码
class Builder {
  setName(name: string): this {
    // ...
    return this
  }
}

十三、函数类型

1. 函数类型标注

TypeScript 复制代码
// 参数类型 + 返回值类型
function add(a: number, b: number): number {
  return a + b
}

// 函数表达式
const multiply: (a: number, b: number) => number = (a, b) => a * b

// 可选参数(必须在必选参数后面)
function greet(name: string, greeting?: string): string {
  return `${greeting || "hello"} ${name}`
}

// 默认参数
function pow(base: number, exp: number = 2): number {
  return Math.pow(base, exp)
}

// 剩余参数
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0)
}

2. 函数重载

同一个函数名,不同参数类型/数量的类型签名。

TypeScript 复制代码
// 先写重载签名(类型定义)
function reverse(str: string): string
function reverse(arr: number[]): number[]
// 再写实现签名(实际函数,兼容所有重载)
function reverse(x: string | number[]): string | number[] {
  if (typeof x === "string") {
    return x.split("").reverse().join("")
  }
  return [...x].reverse()
}

reverse("abc")      // 返回 string,有字符串方法提示
reverse([1, 2, 3])  // 返回 number[]

3. this 参数

在函数第一个参数位置声明 this 的类型(编译后会移除,不影响调用)。

TypeScript 复制代码
interface User {
  name: string
  sayHi: () => void
}

function say(this: User) {
  console.log(this.name)
}

十四、泛型(Generics)

泛型是 TS 最重要的进阶概念之一,用于编写"类型可变、可复用"的代码。

1. 基本使用

用尖括号 <T> 声明类型变量,调用时再确定具体类型。

TypeScript 复制代码
// 泛型函数
function identity<T>(arg: T): T {
  return arg
}

identity<string>("hello")  // 显式指定
identity(123)              // 类型推断,自动识别 T 为 number

// 多个类型参数
function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b]
}

2. 泛型约束(extends)

限制泛型必须满足某些条件。

TypeScript 复制代码
interface Lengthwise {
  length: number
}

function logLen<T extends Lengthwise>(arg: T): T {
  console.log(arg.length) // 确定有 length 属性
  return arg
}

logLen("abc")    // string 有 length,可以
logLen([1, 2])   // 数组有 length,可以
// logLen(123)   // number 没有 length,报错

3. 泛型接口

TypeScript 复制代码
interface Container<T> {
  value: T
  getValue(): T
}

let box: Container<number> = {
  value: 10,
  getValue() { return this.value }
}

4. 泛型类

TypeScript 复制代码
class Stack<T> {
  private items: T[] = []
  push(item: T) { this.items.push(item) }
  pop(): T | undefined { return this.items.pop() }
}

const numStack = new Stack<number>()

5. 泛型默认类型

TypeScript 复制代码
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value)
}

createArray(3, "a") // T 默认为 string

6. 条件类型约束 + infer

见第十六章条件类型。

十五、索引类型 & 映射类型

1. keyof 操作符

获取对象类型所有 key 的联合类型。

TypeScript 复制代码
interface Person {
  name: string
  age: number
}
type PersonKeys = keyof Person // "name" | "age"

经典用法:约束第二个参数是第一个参数的合法 key

TypeScript 复制代码
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

getProp({ name: "a", age: 1 }, "name") // 提示 key,返回正确类型

2. 索引访问类型 T[K]

通过 key 的类型获取对应 value 的类型。

TypeScript 复制代码
type NameType = Person["name"]              // string
type PersonValue = Person[keyof Person]     // string | number

3. 索引签名

用 [key: 类型]: 值类型 描述任意 key 的对象。

TypeScript 复制代码
interface StringMap {
  [key: string]: string
}

// 同时有索引签名和具名属性时,具名属性类型必须兼容索引签名
interface ArrLike {
  [index: number]: string
  length: number
}

4. 映射类型(Mapped Types)

用 in 遍历联合类型,批量生成属性。

TypeScript 复制代码
// 基础语法:{ [K in 联合类型]: 值类型 }
type Keys = "a" | "b" | "c"
type Obj = { [K in Keys]: number } // { a: number; b: number; c: number }

常见映射模式(遍历已有类型的 key):

TypeScript 复制代码
type MyPartial<T> = {
  [K in keyof T]?: T[K] // 所有属性变可选
}

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K] // 所有属性变只读
}

// 可以用 -? 去掉可选,-readonly 去掉只读
type MyRequired<T> = {
  [K in keyof T]-?: T[K]
}

as** 子句(键重映射)**,TS 4.1+:

TypeScript 复制代码
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
// { getName: () => string; getAge: () => number }

十六、条件类型(Conditional Types)& infer

1. 条件类型基础

三元表达式形式:T extends U ? X : Y

如果 T 是 U 的子类型,就取 X,否则取 Y。

TypeScript 复制代码
type IsString<T> = T extends string ? true : false
type A = IsString<"hello"> // true
type B = IsString<123>     // false

2. infer 类型推导

在条件类型中用 infer 声明一个待推导的类型变量,从类型中"提取"信息。

TypeScript 复制代码
// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type Fn = (a: number) => string
type R = MyReturnType<Fn> // string

// 提取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never
type P = MyParameters<Fn> // [number]

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never
type E = ElementOf<number[]> // number

// 提取 Promise 的结果类型
type Awaited<T> = T extends Promise<infer U> ? U : T

3. 分布式条件类型

当条件类型左边 T 是裸类型参数且是联合类型时,会分布式展开到每个成员。

TypeScript 复制代码
type ToArray<T> = T extends any ? T[] : never
type U = ToArray<string | number> // string[] | number[]
// 等价于:ToArray<string> | ToArray<number>

典型应用:Exclude、Extract

TypeScript 复制代码
type MyExclude<T, U> = T extends U ? never : T
type MyExtract<T, U> = T extends U ? T : never

小技巧 :用 [T] extends [U] 阻止分布式

TypeScript 复制代码
type NoDistribute<T> = [T] extends [any] ? T[] : never
type V = NoDistribute<string | number> // (string | number)[]

十七、工具类型(Utility Types)

TS 内置的常用泛型工具,开箱即用。

1. 修饰类

工具类型 说明
Partial<T> 所有属性变可选
Required<T> 所有属性变必选
Readonly<T> 所有属性变只读

2. 结构裁剪类

工具类型 说明
Pick<T, K> 从 T 中挑选 K 属性
Omit<T, K> 从 T 中排除 K 属性
TypeScript 复制代码
interface Todo {
  title: string
  desc: string
  done: boolean
}

type TodoPreview = Pick<Todo, "title" | "done">   // { title: string; done: boolean }
type TodoWithoutDesc = Omit<Todo, "desc">         // 排除 desc

3. 联合类型操作

工具类型 说明
Exclude<T, U> 从联合 T 中排除属于 U 的部分
Extract<T, U> 从联合 T 中提取属于 U 的部分
NonNullable<T> 排除 null 和 undefined
TypeScript 复制代码
type T0 = Exclude<"a" | "b" | "c", "a">          // "b" | "c"
type T1 = Extract<"a" | "b" | "c", "a" | "b">    // "a" | "b"
type T2 = NonNullable<string | null>             // string

4. 函数相关

工具类型 说明
ReturnType<T> 获取函数返回值类型
Parameters<T> 获取函数参数类型(元组)
ConstructorParameters<T> 获取构造函数参数类型
InstanceType<T> 获取构造函数实例类型
TypeScript 复制代码
type R0 = ReturnType<() => string>                          // string
type P0 = Parameters<(a: number, b: string) => void>       // [number, string]

5. 其他

工具类型 说明
Record<K, V> 构造 key 为 K、value 为 V 的对象类型
Awaited<T> 递归解包 Promise
TypeScript 复制代码
type Dict = Record<string, number>    // { [key: string]: number }
type Res = Awaited<Promise<string>>   // string

6. 手写实现(面试常考)

TypeScript 复制代码
type Record<K extends keyof any, T> = { [P in K]: T }
type Pick<T, K extends keyof T> = { [P in K]: T[P] }
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
type Partial<T> = { [P in keyof T]?: T[P] }
type Required<T> = { [P in keyof T]-?: T[P] }
type Readonly<T> = { readonly [P in keyof T]: T[P] }
type Exclude<T, U> = T extends U ? never : T
type Extract<T, U> = T extends U ? T : never
type NonNullable<T> = T extends null | undefined ? never : T
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

十八、类型体操入门

类型体操 = 用 TS 类型系统做"计算",常见于面试或库开发。

常用套路:

  1. 模式匹配 :用 infer + 条件类型提取信息

  2. 递归:用条件类型递归处理数组/字符串

  3. 数组长度做计数:利用元组长度进行数值运算

  4. 联合类型转交叉:通过函数参数逆变

例 1:字符串首字母大写

TypeScript 复制代码
type Capitalize<S extends string> = S extends `${infer F}${infer Rest}`
  ? `${Uppercase<F>}${Rest}`
  : S

例 2:数组翻转

TypeScript 复制代码
type Reverse<T extends any[]> = T extends [infer F, ...infer Rest]
  ? [...Reverse<Rest>, F]
  : []

例 3:深度只读

TypeScript 复制代码
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function ? T[K] : DeepReadonly<T[K]>
    : T[K]
}

十九、类型声明文件(.d.ts)

1. 什么是 .d.ts

专门用来写类型声明的文件,编译后不会生成 JS 代码,只用来提供类型信息。

2. declare 关键字

用来告诉 TS "这个东西存在",常见用法:

TypeScript 复制代码
declare var jQuery: (selector: string) => any
declare function jQuery(selector: string): any

declare class Animal {
  name: string
}

declare enum Direction { Up, Down }

declare module "some-lib" { // 为第三方模块声明类型
  export function foo(): void
}

3. 第三方库类型

  • 很多库自带类型声明(package.json 中的 types 字段)

  • 没有自带的,去 DefinitelyTyped 找:npm install @types/xxx

  • 实在没有,自己写 xxx.d.ts 声明

4. 三斜线指令(老式引用,现在多用 import)

TypeScript 复制代码
/// <reference path="./other.d.ts" />
/// <reference types="node" />

二十、命名空间(Namespace)

TS 早期的模块化方案,现在逐步被 ES Module 取代,但老代码中常见。

TypeScript 复制代码
namespace MyApp {
  export const name = "app"  // 用 export 暴露
  export function run() {}
}

MyApp.run()

命名空间也可以声明合并:

TypeScript 复制代码
namespace MyApp {
  export const version = "1.0"
}

二十一、装饰器(Decorators)

装饰器属于实验特性,tsconfig 打开 "experimentalDecorators": true。

装饰器是函数,用 @ 语法,对类 / 方法 / 属性做增强,AOP 思想,不修改原代码扩展功能。

1. 类装饰器

接收目标类构造函数作为参数。

TypeScript 复制代码
function logClass(target: Function) {
  console.log("类被装饰了", target)
}

@logClass
class Test {}

装饰器工厂(可以传参):

TypeScript 复制代码
function factory(msg: string) {
  return function(target: Function) {
    console.log(msg)
  }
}

@factory("我是工厂传入参数")
class MyClass {}

类装饰器可以返回新类,替换原来的类:

TypeScript 复制代码
function replace(target: any) {
  return class extends target {
    name = "替换后的名字"
  }
}

@replace
class User {
  name: string
  constructor() {
    this.name = "原始名字"
  }
}

2. 属性装饰器

参数:类原型、属性名

TypeScript 复制代码
function propDec(target: any, propName: string) {
  console.log("属性装饰", propName)
}

class Cls {
  @propDec
  title: string = "标题"
}

3. 方法装饰器

参数:类原型、方法名、属性描述符 descriptor,可以修改方法逻辑

TypeScript 复制代码
function methodDec(target: any, methodName: string, descriptor: PropertyDescriptor) {
  const oldFn = descriptor.value
  descriptor.value = function(...args: any[]) {
    console.log("方法调用前")
    return oldFn.apply(this, args)
  }
}

class Calc {
  @methodDec
  add(a: number, b: number) {
    return a + b
  }
}

4. 访问器装饰器

装饰 get/set 访问器,参数同方法装饰器。

TypeScript 复制代码
function accDec(target: any, name: string, desc: PropertyDescriptor) {
  console.log("访问器")
}

class Person {
  private _age: number = 0
  @accDec
  get age() {
    return this._age
  }
}

5. 参数装饰器

装饰函数参数,参数:原型、方法名、参数索引。

TypeScript 复制代码
function paramDec(target: any, method: string, index: number) {
  console.log(`方法${method}第${index}个参数`)
}

class Test {
  fn(@paramDec x: number) {}
}

6. 装饰器组合与执行顺序

多个装饰器同时使用时:

  • 工厂函数:由上到下执行(先执行上层工厂,返回装饰器函数)

  • 装饰器函数:由下往上执行(先应用下层装饰器)

TypeScript 复制代码
@f1()  // 1. f1 工厂执行
@f2()  // 2. f2 工厂执行
class C {}
// 装饰器执行顺序:先 f2 装饰 C,再 f1 装饰结果

不同类型装饰器在同一个类中的执行顺序(从内到外):

  1. 参数装饰器(每个方法上的)

  2. 方法 / 访问器 / 属性装饰器(按声明顺序,静态 > 实例)

  3. 构造函数参数装饰器

  4. 类装饰器

相关推荐
濮水大叔8 小时前
CabloyJS 的 AI 时刻:从 TypeScript 全栈框架到 AI 规范驱动开发
typescript·ai编程·全栈
程序员Sunday17 小时前
TypeScript 的 any、unknown、never 有什么区别?
前端·面试·typescript
三十而立洋1 天前
TypeScript 7.0 深度解析:新特性、Go 原生编译器及与 TypeScript 6.0 的区别
前端·typescript·前端工程化
VimorphTech1 天前
React 自动保存怎么设计?防抖、串行请求与导出前等待
后端·typescript
yuanlaile1 天前
做了 3 年后端才明白:学 NestJS 千万不要只学 API 语法,完整电商项目才是分水岭
typescript·nestjs·node.js 后端·nestjs实战
ZoeLandia1 天前
TypeScript 07:工程化——import type、声明文件与 tsconfig
开发语言·前端·typescript
nyaomaru1 天前
如何用 Jev 从 PR 中提取 “Why” 并写入 CHANGELOG?🤔
前端·typescript
damoluomu1 天前
SSG 的后台怎么补:Node.js 生态里的三条路(番外二)
javascript·css·typescript·node.js·html
苏渡苇1 天前
从零搭建 Vue 3 工程化项目:Vite + TypeScript + Pinia + Element Plus 实战
前端·vue.js·typescript