Appearance
装饰器的发展历程
- 2014-04-10: Yehuda Katz 向 TC39 提出装饰器提案,进入 Stage 0。
- 2015-03-05: Angular 团队和 TypeScript 团队宣布 Angular 将从 AtScript 切换到 TypeScript,并采用装饰器。
- 2015-03-24: 装饰器提案达到 Stage 1。
- 2016-07-28: 提案达到 Stage 2。
- 2022-03-28: 装饰器提案达到 Stage 3。
装饰器有两种主要的提案:第二提案(Stage 2)和第三提案(Stage 3)。二者虽然都旨在增强代码的可读性和可维护性,但在语法和功能上存在一些差异,在新版本的ts5 可以直接使用装饰器但是默认 stage3 提案的
新的装饰器不支持装饰参数TypeScript 5+装饰器变更的影响
另一个改动在装饰器函数定义上,之前装饰函数定义入参可以理解 target
key
descriptor
, 并且在第二提案的时候 使用描述符对象(descriptor),需要显式操作描述符,但是在提案 3 使用上下文对象(context),提供更详细和结构化的元数据。
看一下新版的装饰器函数定义
好的,我会将这些参数解释为一个表格:
参数名称 | 类型 | 说明 |
---|---|---|
value | DecoratedValue | 被装饰的值。 |
context | object | 上下文对象,包含装饰器的上下文信息。 |
context.kind | string | 装饰器应用的类型,例如属性、方法、访问器等。 |
context.name | `string | symbol` |
context.addInitializer | function | 可选函数,用于添加初始化逻辑。 |
context.static | boolean | 可选属性,指示是否是静态成员。 |
context.private | boolean | 可选属性,指示是否是私有成员。 |
context.access | object | 访问方法对象,包含获取和设置方法。 |
context.access.get | function | 可选属性,返回值的获取方法。 |
context.access.set | function | 可选属性,设置值的方法。 |
返回值 | `void | ReplacementValue` |
解释部分:
value
:这是被装饰的原始值,类型因装饰器作用的目标不同而异。context
:这是一个包含上下文信息的对象,用于提供装饰器的环境信息。context.kind
:标识装饰器应用的对象类型,例如class、method、getter、setter、field、accessor。context.name
:被装饰的对象的名称,可以是字符串或符号。context.addInitializer
:一个可选的函数,以前,这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入addInitializer()方法。注意,addInitializer()没有返回值。可以用于添加初始化逻辑,该函数在装饰器应用后会被调用。context.static
:一个可选布尔值,指示被装饰的对象是否是静态成员。context.private
:一个可选布尔值,指示被装饰的对象是否是私有成员。context.access
:一个对象,包含访问方法,用于获取和设置被装饰对象的值。context.access.get
:一个可选的函数,用于获取被装饰对象的值。context.access.set
:一个可选的函数,用于设置被装饰对象的值。返回值
:装饰器的返回值,可以是void
或者是一个替换值,如果返回替换值,它将替代原始值。
ts
type Decorator = (
value: DecoratedValue,
context: {
kind: string;
name: string | symbol;
addInitializer?(initializer: () => void): void;
static?: boolean;
private?: boolean;
access: {
get?(): unknown;
set?(value: unknown): void;
};
}
) => void | ReplacementValue;
- ClassDecoratorContext
属性 | 类型 | 说明 |
---|---|---|
kind | "class" | 被装饰的元素类型,类装饰器上下文中总是 "class" |
name | string | undefined | 被装饰的类的名称 |
addInitializer | (initializer: (this: Class) => void) => void | 添加类定义完成后的回调函数 |
metadata | DecoratorMetadata | 装饰器元数据 |
- ClassMethodDecoratorContext
属性 | 类型 | 说明 |
---|---|---|
kind | "method" | 被装饰的元素类型,方法装饰器上下文中总是 "method" |
name | string | symbol | 被装饰的类元素的名称 |
static | boolean | 表示类元素是静态(true )还是实例(false ) |
private | boolean | 表示类元素名字是否是私有的 |
access | Object | 可以用于在运行时访问类元素当前值的对象,上下文会有所不同 |
access.has | (object: This) => boolean | 检查对象是否有与装饰的元素相同名称的属性 |
access.get | (object: This) => Value | 从提供的对象获取当前方法的值 |
addInitializer | (initializer: (this: This) => void) => void | 添加回调函数,在静态初始化器或实例初始化器运行前调用 |
metadata | DecoratorMetadata | 装饰器元数据 |
- ClassGetterDecoratorContext
属性 | 类型 | 说明 |
---|---|---|
kind | "getter" | 被装饰的元素类型,getter装饰器上下文中总是 "getter" |
name | string | symbol | 被装饰的类元素的名称 |
static | boolean | 表示类元素是静态(true )还是实例(false ) |
private | boolean | 表示类元素名字是否是私有的 |
access | Object | 可以用于在运行时访问类元素当前值的对象,上下文会有所不同 |
access.has | (object: This) => boolean | 检查对象是否有与装饰的元素相同名称的属性 |
access.get | (object: This) => Value | 调用getter方法从提供的对象上获取值 |
addInitializer | (initializer: (this: This) => void) => void | 添加回调函数,在静态初始化器或实例初始化器运行前调用 |
metadata | DecoratorMetadata | 装饰器元数据 |
- ClassSetterDecoratorContext
属性 | 类型 | 说明 |
---|---|---|
kind | "setter" | 被装饰的元素类型,setter装饰器上下文中总是 "setter" |
name | string | symbol | 被装饰的类元素的名称 |
static | boolean | 表示类元素是静态(true )还是实例(false ) |
private | boolean | 表示类元素名字是否是私有的 |
access | Object | 可以用于在运行时访问类元素当前值的对象,上下文会有所不同 |
access.has | (object: This) => boolean | 检查对象是否有与装饰的元素相同名称的属性 |
access.set | (object: This, value: Value) => void | 调用setter从提供的对象上设置值 |
addInitializer | (initializer: (this: This) => void) => void | 添加回调函数,在静态初始化器或实例初始化器运行前调用 |
metadata | DecoratorMetadata | 装饰器元数据 |
- ClassAccessorDecoratorContext
属性 | 类型 | 说明 |
---|---|---|
kind | "accessor" | 被装饰的元素类型,accessor装饰器上下文中总是 "accessor" |
name | string | symbol | 被装饰的类元素的名称 |
static | boolean | 表示类元素是静态(true )还是实例(false ) |
private | boolean | 表示类元素名字是否是私有的 |
access | Object | 可以用于在运行时访问类元素当前值的对象,上下文会有所不同 |
access.has | (object: This) => boolean | 检查对象是否有与装饰的元素相同名称的属性 |
access.get | (object: This) => Value | 调用getter方法从提供的对象上获取值 |
access.set | (object: This, value: Value) => void | 调用setter从提供的对象上设置值 |
addInitializer | (initializer: (this: This) => void) => void | 添加回调函数,在静态初始化器或实例初始化器运行前调用 |
metadata | DecoratorMetadata | 装饰器元数据 |
- ClassFieldDecoratorContext
属性 | 类型 | 说明 |
---|---|---|
kind | "field" | 被装饰的元素类型,字段装饰器上下文中总是 "field" |
name | string | symbol | 被装饰的类元素的名称 |
static | boolean | 表示类元素是静态(true )还是实例(false ) |
private | boolean | 表示类元素名字是否是私有的 |
access | Object | 可以用于在运行时访问类元素当前值的对象,上下文会有所不同 |
access.has | (object: This) => boolean | 检查对象是否有与装饰的元素相同名称的属性 |
access.get | (object: This) => Value | 从提供的对象获取字段的值 |
access.set | (object: This, value: Value) => void | 设置提供的对象的字段 |
addInitializer | (initializer: (this: This) => void) => void | 添加回调函数,在静态初始化器或实例初始化器运行前调用 |
metadata | DecoratorMetadata | 装饰器元数据 |
类装饰器
类装饰器参数描述
ts
type ClassDecorator = (
value: Function,
context: {
kind: 'class';
name: string | undefined;
addInitializer(initializer: () => void): void;
}
) => Function | void;
下面是一个简单的类装饰器案例,执行后打印就可以发现 value 其实就是 class 本身 这一点和提案二是一样的
ts
// 执行需要 tsc 执行 ts-node v10.9.2 会报错
type constructor<T> = new (...args: any[]) => T;
let o: constructor<A>;
function classDeclaration(v: string) {
return function <T>(value: constructor<T>, context: ClassDecoratorContext) {
// 如果是多个不同种类装饰器用这个判断可以进行分类
if (context.kind === "class") {
// 还是指向构造函数
o = value;
console.log(`class decorator${v}`);
}
};
}
@classDeclaration("类装饰器")
class A {}
console.log(A === o); // true
案例 -- 在原型链上添加
ts
// 执行需要 tsc 执行 ts-node v10.9.2 会报错
type constructor<T> = new (...args: any[]) => T;
function classDeclaration(v: string) {
return function <T>(value: constructor<T>, context: ClassDecoratorContext) {
// 如果是多个不同种类装饰器用这个判断可以进行分类
if (context.kind === "class") {
value.prototype.getName = function () {
console.log(`class decorator${v}`);
};
}
};
}
@classDeclaration("类装饰器")
class A {}
const a = new A();
(a as any).getName(); // class decorator类装饰器
案例 -- 继承修改修饰类
ts
// 执行需要 tsc 执行 ts-node v10.9.2 会报错
type constructor<T> = new (...args: any[]) => T;
function classDeclaration(v: string) {
return function <T extends constructor<A>>(
value: T,
context: ClassDecoratorContext
) {
// 如果是多个不同种类装饰器用这个判断可以进行分类
if (context.kind === "class") {
return class extends value {
age = 18;
};
}
};
}
@classDeclaration("类装饰器")
class A {}
const a = new A();
console.log(a.constructor); // [Function: class_1] 指向的是子类 此时A 变成了父类
console.log((a as any).age); // 19
覆盖当前类
ts
// 执行需要 tsc 执行 ts-node v10.9.2 会报错
type constructor<T> = new (...args: any[]) => T;
function classDeclaration<T>(v: string) {
return function (value: constructor<T>, context: ClassDecoratorContext) {
// 如果是多个不同种类装饰器用这个判断可以进行分类
if (context.kind === "class") {
// 这里不能出现 A 中没有的属性否则会 报错
return class {
name = "18";
age = 20;
};
}
};
}
@classDeclaration("类装饰器")
class A {
name = "w";
}
const a = new A();
console.log(a instanceof A); // true da
console.log((a as any).age); // 19
不准通过new 创建
ts
function functionCallable(
value:any, {kind}:any
):any {
if (kind === 'class') {
return function (...args:any) {
if (new.target !== undefined) {
throw new TypeError('This function can’t be new-invoked');
}
return new value(...args);
}
}
}
@functionCallable
class Person {
name:string;
constructor(name:string) {
this.name = name;
}
}
// @ts-ignore
const robin = Person('Robin');
robin.name // 'Robin'
使用 addInitializer
context的addInitializer()方法,用来定义一个类的初始化函数,在类完全定义结束后执行。类定义完成后,会自动执行类装饰器
ts
// 执行需要 tsc 执行 ts-node v10.9.2 会报错
type constructor<T> = new (...args: any[]) => T;
// 创建一个收集注册定义 class Map
const mapCls = new Map();
function classDeclaration<T>(v: string) {
return function (value: constructor<T>, context: ClassDecoratorContext) {
context.addInitializer(function () {
if (!mapCls.get(value)) mapCls.set(v, value);
});
};
}
@classDeclaration("A")
class A {
name = "w";
}
@classDeclaration("B")
class B {
name = "ww";
}
// 策略模式分发
function creatClsByKey(k: string) {
return new (mapCls.get(k))();
}
const a = creatClsByKey("A");
console.log(mapCls); // true
console.log(a.name); // w
- html 组件class 案例
ts
function customElement(name: string) {
return <Input extends new (...args: any) => any>(
value: Input,
context: ClassDecoratorContext
) => {
context.addInitializer(function () {
customElements.define(name, value);
});
};
}
@customElement("hello-world")
class MyComponent extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `<h1>Hello World</h1>`;
}
}
方法装饰器
ts
type ClassMethodDecorator = (
value: Function,
context: {
kind: 'method';
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown };
addInitializer(initializer: () => void): void;
}
) => Function | void;
- static:布尔值,表示是否为静态方法。该属性为只读属性。
- private:布尔值,表示是否为私有方法。该属性为只读属性。
- access:对象,包含了方法的存取器,但是只有get()方法用来取值,没有set()方法进行赋值。
替代所装饰原始函数
ts
function replaceMethod(value: Function, context: ClassMethodDecoratorContext) {
return function () {
return `How are you, ${this.name}?`;
};
}
function log(value: Function, context: ClassMethodDecoratorContext) {
console.log(context.static); // 是否是静态方法
console.log(context.private); // 方法是否为私有
function replacementMethod(this: any, ...args: any[]) {
console.log("开始执行");
const result = value.call(this, ...args);
return result;
}
// 替换原来的函数
return replacementMethod;
}
class Person {
constructor(public name: string) {
this.name = name;
}
@replaceMethod
hello() {
return `Hi ${this.name}!`;
}
@log
getName() {
return this.name;
}
}
const robin = new Person("Robin");
console.log(robin.hello()); // 输出: "How are you, Robin?"
console.log(robin.getName()); // 输出: 开始执行 Robin
防抖装饰器
ts
function delay(milliseconds: number = 0) {
return function (value, context) {
if (context.kind === "method") {
return function (...args: any[]) {
setTimeout(() => {
value.apply(this, args);
}, milliseconds);
};
}
};
}
class Logger {
@delay(1000)
log(msg: string) {
console.log(`${msg}`);
}
}
let logger = new Logger();
logger.log("Hello World");
收集
ts
function collect(
value,
{name, addInitializer}
) {
addInitializer(function () {
if (!this.collectedMethodKeys) {
this.collectedMethodKeys = new Set();
}
this.collectedMethodKeys.add(name);
});
}
class C {
@collect
toString() {}
@collect
[Symbol.iterator]() {}
}
const inst = new C();
inst.collectedMethodKeys // new Set(['toString', Symbol.iterator])
属性装饰器
属性装饰器用来装饰定义在类顶部的属性(field)
ts
type ClassFieldDecorator = (
value: undefined,
context: {
kind: 'field';
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown, set: (value: unknown) => void };
addInitializer(initializer: () => void): void;
}
) => (initialValue: unknown) => unknown | void;
装饰器的第一个参数value的类型是undefined,这意味着这个参数实际上没用的,装饰器不能从value获取所装饰属性的值。另外,第二个参数 context
对象的kind属性的值为字符串 field
属性装饰器要么不返回值,要么返回一个函数,该函数会自动执行,用来对所装饰属性进行初始化。该函数的参数是所装饰属性的初始值,该函数的返回值是该属性的最终值。
初始化参数值
返回一个函数,该函数会自动执行,用来对所装饰属性进行初始化
ts
// function twice() {
// return initialValue => initialValue * 2;
// }
// class C {
// @twice
// field = 3;
// }
// const inst = new C();
// inst.field // 6
function initParams(v) {
return function (value, context: ClassFieldDecoratorContext) {
const { kind, name } = context;
if (kind === "field") {
// console.log(context.access.get());
return function (initialValue) {
console.log(
`initializing ${name as string} with value ${initialValue}`
);
return v;
};
}
};
}
class Color {
@initParams("green")
name;
}
const color = new Color();
// "initializing name with value green"
console.log(color.name); // "green"
color.name = "red";
console.log(color.name); // red
用来触发指定对象 get set
ts
const a = { name: "w" };
function initParams(v) {
return function (value, context: ClassFieldDecoratorContext) {
const { kind, name } = context;
if (kind === "field") {
// console.log(context.access.get());
// 利用 context.access get set 方法设置触发传入的对象的属性值
context.access.set(a, v);
console.log(context.access.get(a), 2); // green 2
}
};
}
class Color {
@initParams("green")
name;
}
const color = new Color();
// "initializing name with value green"
console.log(color.name); // "undefined"
color.name = "red";
console.log(color.name); // red
console.log(a.name); // green
console.log(color.name); // red
- 提取存储器
ts
let acc;
function exposeAccess(
value, {access}
) {
acc = access;
}
class Color {
@exposeAccess
name = 'green'
}
const green = new Color();
green.name // 'green'
acc.get(green) // 'green'
acc.set(green, 'red');
green.name // 'red'
getter 装饰器,setter 装饰器
ts
type ClassGetterDecorator = (
value: Function,
context: {
kind: 'getter';
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown };
addInitializer(initializer: () => void): void;
}
) => Function | void;
type ClassSetterDecorator = (
value: Function,
context: {
kind: 'setter';
name: string | symbol;
static: boolean;
private: boolean;
access: { set: (value: unknown) => void };
addInitializer(initializer: () => void): void;
}
) => Function | void;
这两个装饰器要么不返回值,要么返回一个函数,取代原来的取值器或存值器。
案例
下面的例子是将取值器的结果,保存为一个属性,加快后面的读取,第一次读取inst.value,会进行计算,然后装饰器@lazy将结果存入只读属性value,后面再读取这个属性,就不会进行计算了。
ts
class C {
@lazy
get value() {
console.log('正在计算……');
return '开销大的计算结果';
}
}
function lazy(
value:any,
{kind, name}:any
) {
if (kind === 'getter') {
return function (this:any) {
const result = value.call(this);
Object.defineProperty(
this, name,
{
value: result,
writable: false,
}
);
return result;
};
}
return;
}
const inst = new C();
inst.value
// 正在计算……
// '开销大的计算结果'
inst.value
// '开销大的计算结果'
参考
TypeScript 5+装饰器变更的影响网道 / WangDoc.com2ality – JavaScript and more