简介
装饰器(Decorator)是一种语法结构,用来在定义时修改类(class)的行为。
在语法上,装饰器有如下几个特征。
(1)第一个字符(或者说前缀)是@
,后面是一个表达式。
(2)@
后面的表达式,必须是一个函数(或者执行后可以得到一个函数)。
(3)这个函数接受所修饰对象的一些相关值作为参数。
(4)这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象。
举例来说,有一个函数Injectable()
当作装饰器使用,那么需要写成@Injectable
,然后放在某个类的前面。
@Injectable class A {
// ...
}
上面示例中,由于有了装饰器@Injectable
,类A
的行为在运行时就会发生改变。
下面就是一个最简单的装饰器。
function simpleDecorator() {
console.log('hi');
}
@simpleDecorator
class A {} // "hi"
上面示例中,函数simpleDecorator()
用作装饰器,附加在类A
之上,后者在代码解析时就会打印一行日志。
编译上面的代码会报错,提示没有用到装饰器的参数。现在就为装饰器加上参数,让它更像正式运行的代码。
function simpleDecorator(
target:any,
context:any
) {
console.log('hi, this is ' + target);
return target;
}
@simpleDecorator
class A {} // "hi, this is class A {}"
上面的代码就可以顺利通过编译了,代码含义这里先不解释。大家只要理解,类A
在执行前会先执行装饰器simpleDecorator()
,并且会向装饰器自动传入参数就可以了。
装饰器有多种形式,基本上只要在@
符号后面添加表达式都是可以的。下面都是合法的装饰器。
@myFunc
@myFuncFactory(arg1, arg2)
@libraryModule.prop
@someObj.method(123)
@(wrap(dict['prop']))
注意,@
后面的表达式,最终执行后得到的应该是一个函数。
相比使用子类改变父类,装饰器更加简洁优雅,缺点是不那么直观,功能也受到一些限制。所以,装饰器一般只用来为类添加某种特定行为。
@frozen class Foo {
@configurable(false)
@enumerable(true)
method() {}
@throttle(500)
expensiveMethod() {}
}
上面示例中,一共有四个装饰器,一个用在类本身(@frozen
),另外三个用在类的方法(@configurable
、@enumerable
、@throttle
)。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。
装饰器的版本
TypeScript 从早期开始,就支持装饰器。但是,装饰器的语法后来发生了变化。ECMAScript 标准委员会最终通过的语法标准,与 TypeScript 早期使用的语法有很大差异。
目前,TypeScript 5.0 同时支持两种装饰器语法。标准语法可以直接使用,传统语法需要打开--experimentalDecorators
编译参数。
$ tsc --target ES5 --experimentalDecorators
本章介绍装饰器的标准语法,下一章介绍传统语法。
装饰器的结构
装饰器函数的类型定义如下。
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;
上面代码中,Decorator
是装饰器的类型定义。它是一个函数,使用时会接收到value
和context
两个参数。
-
value
:所装饰的对象。 -
context
:上下文对象,TypeScript 提供一个原生接口ClassMethodDecoratorContext
,描述这个对象。
function decorator(
value:any,
context:ClassMethodDecoratorContext
) {
// ...
}
上面是一个装饰器函数,其中第二个参数context
的类型就可以写成ClassMethodDecoratorContext
。
context
对象的属性,根据所装饰对象的不同而不同,其中只有两个属性(kind
和name
)是必有的,其他都是可选的。
(1)kind
:字符串,表示所装饰对象的类型,可能取以下的值。
- 'class'
- 'method'
- 'getter'
- 'setter'
- 'field'
- 'accessor'
这表示一共有六种类型的装饰器。
(2)name
:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等。
(3)addInitializer()
:函数,用来添加类的初始化逻辑。以前,这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入addInitializer()
方法。注意,addInitializer()
没有返回值。
(4)private
:布尔值,表示所装饰的对象是否为类的私有成员。
(5)static
:布尔值,表示所装饰的对象是否为类的静态成员。
(6)access
:一个对象,包含了某个值的 get 和 set 方法。
类装饰器
类装饰器的类型描述如下。
type ClassDecorator = (
value: Function,
context: {
kind: 'class';
name: string | undefined;
addInitializer(initializer: () => void): void;
}
) => Function | void;
类装饰器接受两个参数:value
(当前类本身)和context
(上下文对象)。其中,context
对象的kind
属性固定为字符串class
。