什么是装饰器
装饰器本质上一个函数
类的装饰器
- 对类进行装饰
开启装饰器语法:
在tsconfig.json
文件中,将这两项开启:
/* Experimental Options */
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
- 装饰器的基本语法,使用@进行使用
function TestDecorator(constructor:any){
console.log('test decorator')
}
@TestDecorator
class Test{
}
const test = new Test()
// 输出 test decorator
- 类的装饰器是针对类而言的,不是针对实例而言的,所以只会执行一次
function TestDecorator(constructor:any){
console.log('test decorator')
}
@TestDecorator
class Test{
}
const test = new Test()
const test2 = new Test()
// test decorator
- 可以使用工厂模式对装饰器进行包装
使用场景:按条件添加装饰器
function TestDecoratorFactory(flag:boolean){
if(flag){
return function(constructor:any){
console.log('test decorator')
}
}else{
return function(constructor:any){}
}
}
@TestDecoratorFactory(true)
class Test{
}
const test = new Test()
// test decorator
使用装饰器更改类的构造函数
使用装饰器,为类增加一个getName
的方法
function TestDecoratorFactory(flag:boolean){
if(flag){
return function(constructor:any){
//直接绑到prototype之上
constructor.prototype.getName = () =>{
return 'yyc'
}
}
}else{
return function(constructor:any){}
}
}
@TestDecoratorFactory(true)
class Test{
}
// 先暂时any处理,后面会提到
const test:any = new Test()
console.log('name is',test.getName())
//yyc
虽然完成了,但是ts并不知道对类挂载了getName这个方法,所以在编译时会提示错误,需要先指出构造函数的类型。
- 有一点需要强调,最后返回出来的还是一个函数(继承了构造函数
// T是一个被实例化出来的构造函数
function TestDecorator any>(constructor:T){
//对原有装饰器的补充
return class extends constructor{
name = 'yyc'
getName(){
return this.name
}
}
}
@TestDecorator
class Test{
name:string;
constructor(name:string){
this.name = name
}
}
const test:any = new Test('cyy')
console.log('name is',test.getName())
// yyc
这样还是不能让ts推断出实例化的类的类型,使用工厂模式来解决这一问题
- 使用工厂模式返回一个装饰器
- 装饰器对基础类装饰后返回一个新的类
function TestDecoratorFactory(){
return function any>(constructor:T){
return class extends constructor{
name = 'yyc'
getName(){
return this.name
}
}
}
}
const Test = TestDecoratorFactory()(
class {
name:string;
constructor(name:string){
this.name = name
}
}
)
const test = new Test('cyy')
console.log('name is',test.getName())
//name is yyc
类中方法的装饰器
方法的装饰器,语法上和类的装饰器大致相同,但是接受参数有些区别
- target:原型
- key:方法名
- description:对方法的定义,与define
function getNameDecorator(target:any, key:string){
console.log('target is',target,'key',key)
}
class Test{
name:string;
constructor(name:string){
this.name = name
}
@getNameDecorator
getName(){
return this.name
}
}
const test = new Test('cyy')
//out put:
// target is Test { getName: [Function] } key getName
- 如果装饰的是静态方法:
function getNameDecorator(target:any,key:string){
console.log('target is',target,'key',key)
}
class Test{
name:string;
constructor(name:string){
this.name = name
}
@getNameDecorator
static getName(){
return name
}
}
//output
target is [Function: Test] { getName: [Function] } key getName
- 第三个参数
description
,与Object.defineProperty()
基本一致,可以对方法是否重写,以及方法默认值进行定义:
其余的属性查阅文档- description.writable = false 不可重写
function getNameDecorator(target:any,key:string,description:PropertyDescriptor){
console.log('target is',target,'key',key)
//不可重写
description.writable = false
}
class Test{
name:string;
constructor(name:string){
this.name = name
}
@getNameDecorator
getName(){
return this.name
}
}
const test = new Test('cyy')
test.getName = () => {
return 'aaa'
}
console.log(test.getName())
//会抛出错误:Cannot assign to read only property 'getName' of object '#'
访问器的装饰器
- 访问器的装饰器和方法的装饰器大致相同
-
getter
和setter
不能同时具备装饰器 - 通过改写装饰器,可以使访问器失效,比如
description.writable = false
function nameDecorator(target:any,key:string,description:PropertyDescriptor){
}
class Test{
private _name:string;
constructor(name:string){
this._name = name
}
get name(){
return this._name
}
@nameDecorator
set name(name:string){
this._name = name
}
}
const test = new Test('yyc')
test.name = 'yyc again'
console.log(test.name)
属性的装饰器
类的属性同样也可以配备装饰器
- 只接受两个参数:
-
target
:原型 -
key
:属性名
-
- 可以通过返回一个decriptor对属性进行设置
function NameDecorator(target:any,key:string):any{
const decorator:PropertyDescriptor = {
writable:false
}
return decorator
}
class Test{
@NameDecorator
name = 'yyc'
}
const test = new Test()
test.name = 'yyc2'
//output:Cannot assign to read only property 'name' of object '#'
- 不能通过装饰器对属性值进行更改,因为更改的是原型上的而不是实例上的。
function NameDecorator(target:any,key:string):any{
target[key] = 'yyc2'
}
class Test{
@NameDecorator
name = 'yyc'
}
const test = new Test()
console.log('test',test.name,(test as any).__proto__.name)
//test yyc yyc2
参数的装饰器
- 参数的装饰器一共接受三个参数:
- 原型:target
- 方法名
- 装饰器装饰的参数的位置
function ParamDecorator(target:any,key:string,paramIndex:number){
console.log('target',target,'key',key,'paramIndex',paramIndex)
//target Test { getInfo: [Function] } key getInfo paramIndex 0
}
class Test{
getInfo(@ParamDecorator name:string,age:number){
}
}
const test = new Test()
test.getInfo('yyc',30)
装饰器的实际应用
使用装饰器对异常统一进行处理
const userInfo:any = undefined
function ErrorDecorator(target:any,key:string,descriptor:PropertyDescriptor){
//fn是对函数的引用
const fn = descriptor.value
//对调用的函数进行覆写
descriptor.value = function(){
try{
fn()
}catch(e){
console.log('error happen')
}
}
}
class Test{
@ErrorDecorator
getName(){
return userInfo.name
}
getAge(){
return userInfo.age
}
}
const test = new Test()
test.getName()
//output:error happen
- 更进一步,需要自定义抛出错误的消息
使用工厂模式:
function ErrorDecoratorFactory(errMsg:string){
return function(target:any,key:string,descriptor:PropertyDescriptor){
const fn = descriptor.value
descriptor.value = function(){
try{
fn()
}catch(e){
console.log(errMsg)
}
}
}
}
class Test{
//传递参数
@ErrorDecoratorFactory('invalid name')
getName(){
return userInfo.name
}
@ErrorDecoratorFactory('invalid age')
getAge(){
return userInfo.age
}
}
//invalid name
test.getName()
//invalid age
test.getAge()
metadata 原数据的定义和反射
需要将tsconfig.json这么设置:
"module": "commonjs",
才可以使用reflect metadata
- 利用Reflect检查装饰器使用的时机
是先调用方法上的装饰器,最后调用类上的装饰器
function showData(target:typeof User){
for(let i in target.prototype){
const func = Reflect.getMetadata('data',target.prototype,i)
console.log('func',func)
}
}
function setData(target:any,key:string,description:any){
Reflect.defineMetadata('data',key,target,key)
}
function setDataFactory(value:string){
return function(target:any,key:string,description:any){
Reflect.defineMetadata('data',value,target,key)
}
}
@showData
class User{
constructor(){
}
@setDataFactory('name')
getName(){}
@setDataFactory('age')
getAge(){}
}
const yyc = new User()
//output