TypeScript
一、TypeScript 简介
TypeScript (TS) 是 JavaScript (JS) 的超集,在 JS 基础上增加静态类型系统,最终编译成普通 JS 运行,可运行在任何 JS 环境。
简单说:JS 是动态类型,运行才报错;TS 静态类型,写代码阶段就能发现错误。
二、为何需要 TypeScript
-
JS 弱类型,变量类型随意改变,大型项目容易出现隐蔽运行时错误
-
TS 编译阶段做类型检查,提前捕获 bug
-
类型提示增强代码可读性,方便团队协作、重构代码
-
支持更新 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 类型系统做"计算",常见于面试或库开发。
常用套路:
-
模式匹配 :用
infer+ 条件类型提取信息 -
递归:用条件类型递归处理数组/字符串
-
数组长度做计数:利用元组长度进行数值运算
-
联合类型转交叉:通过函数参数逆变
例 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 装饰结果
不同类型装饰器在同一个类中的执行顺序(从内到外):
-
参数装饰器(每个方法上的)
-
方法 / 访问器 / 属性装饰器(按声明顺序,静态 > 实例)
-
构造函数参数装饰器
-
类装饰器