8、TypeScript的装饰器
装饰器概念
装饰器-Decorators 在 TypeScript 中是一种可以在不修改类代码的基础上通过添加标注的方式来对类型进行扩展的一种方式
- 减少代码量
- 提高代码扩展性、可读性和维护性
在
TypeScript中,装饰器只能在类中使用
装饰器的使用
使用装饰器需要配置文件中启用experimentalDecorators: true
// 定义方法装饰器
function log(target: Function, name: string, descriptor: PropertyDescriptor) {
/**
* target : 被装饰的方法所属的类
* name : 当前被装饰方法的名称
* descriptor : 描述符
*/
// 将原始方法提取出来
let fn = descriptor.value
// 定义新的方法
descriptor.value = function (a: number, b: number) {
// 调用原来的方法
const result = fn(a, b)
// 增加新的方法
console.log(`${a}+${b}=${result}`)
// 将结果返回
return result
}
}
class M {
@log
static add(a: number, b: number) {
return a + b
}
}
let v1 = M.add(1, 2)
console.log(v1)装饰器
装饰器 是一个函数,它可以通过 @装饰器函数 这种特殊的语法附加在 类、方法 、访问符、属性、参数 上,对它们进行包装,然后返回一个包装后的目标对象(类、方法 、访问符、属性、参数 ),装饰器工作在类的构建阶段,而不是使用阶段
function 装饰器1() {}
...
@装饰器1
class MyClass {
@装饰器2
a: number;
@装饰器3
static property1: number;
@装饰器4
get b() {
return 1;
}
@装饰器5
static get c() {
return 2;
}
@装饰器6
public method1(@装饰器5 x: number) {
//
}
@装饰器7
public static method2() {}
}装饰器的参数
function d1(target: Function) {
console.log(typeof target, target);
}
function d11(target: Function) {
console.log(typeof target, target, '1111111');
}
function d2(target: any, name: string) {
console.log(typeof target, name);
}
function d3(target: any, name: string, descriptor: PropertyDescriptor) {
console.log(typeof target, name, descriptor);
}
function d4(target: any, name: string, descriptor: PropertyDescriptor) {
console.log(typeof target, name, descriptor);
}
function d5(target: any, name: string, index: number) {
// name : 当前参数所在的方法
console.log(typeof target, name, index);
}
@d1
@d11
class MyClass {
@d2
static property1: number;
@d2
a: number;
@d3
get b() {
return 1;
}
@d3
static get c() {
return 2;
}
@d4
public method1(@d5 x: number, @d5 y: number) {}
@d4
public static method2() {}
}| 装饰器 | 目标 | 第一个参数 | 第二个参数 | 第三个参数 |
|---|---|---|---|---|
| 类装饰器 | 应用于类的构造函数 | 类的构造函数作为其唯一的参数 | × | × |
| 方法装饰器 | 应用于类的方法上 | 静态方法:类的构造函数 实例方法:类的原型对象 | 方法名称 | 方法描述符对象 |
| 属性装饰器 | 应用于类的属性上 | 静态方法:类的构造函数 实例方法:类的原型对象 | 属性名称 | × |
| 访问器装饰器 | 应用于类的访问器(getter、setter)上 | 静态方法:类的构造函数 实例方法:类的原型对象 | 属性名称 | 方法描述符对象 |
| 参数装饰器 | 应用在参数上 | 静态方法:类的构造函数 实例方法:类的原型对象 | 方法名称 | 参数在函数参数列表中的索引 |
装饰器的执行顺序
装饰器工厂
function log(type: string) {
return function (target: Function, name: string, descriptor: PropertyDescriptor) {
let value = descriptor.value;
descriptor.value = function(x: number, y: number) {
let result = value(x, y);
console.log({
type,
name,
x,
y,
result
});
return result;
}
}
}
class M {
@log('log')
static add(x: number, y: number) {
return x + y;
}
@log('storage')
static sub(x: number, y: number) {
return x - y;
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);
export default {}类的装饰器
装饰器的合理写法
// 装饰器本身是一个函数
function testDecorator<T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor {
age = 23
}
}
@testDecorator
class Test {
age: Number
constructor(age: Number) {
console.log(1)
this.age = age
console.log(2)
}
}
const test = new Test(21)
console.log(test.age)
/**
* 1
* 2
* 23
*/
扩展方法
利用工厂模式,在使用装饰器时就不是当作装饰器来使用,而是当成函数来使用,函数内传入类,将返回的结果作为类的constructor。
function testDecorator() {
return function <T extends new (...args: any[]) => any>(constructor: T) {
return class extends constructor {
age = 23
getName() {
return '小康'
}
}
}
}
const Test = testDecorator()(
class Test {
age: Number
constructor(age: Number) {
this.age = age
}
}
)
const test = new Test(21)
console.log(test.age, test.getName()) // 23 小康
属性装饰器
属性装饰器修改的是原型上的属性的值,不能直接修改实例上的值。
function nameDecorator(target: any, key: string): any {
target[key] = 23
}
class Test {
@nameDecorator
age = 21
}
const test = new Test()
console.log(test.age) // 21
console.log((test as any).__proto__.age) //23方法装饰器
const userInfo: any = undefined
function catchError(msg: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const fn = descriptor.value
descriptor.value = function () {
try {
fn()
} catch (e) {
console.log(msg)
}
}
}
}
class User {
@catchError('userInfo.name不存在')
getName() {
return userInfo.name
}
@catchError('userInfo.age不存在')
getAge() {
return userInfo.age
}
}
const user = new User()
user.getName()元数据
元数据会被附加到指定的 类、方法 等数据之上,但是又不会影响 类、方法 本身的代码
使用 reflect-metadata
https://www.npmjs.com/package/reflect-metadata
首先,需要安装 reflect-metadata
npm install reflect-metadata定义元数据
我们可以 类、方法 等数据定义元数据
设置
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)
metadataKey:meta 数据的 keymetadataValue:meta 数据的 值target:meta 数据附加的目标propertyKey:对应的 property key
调用方式
通过
Reflect.defineMetadata方法调用来添加 元数据通过
@Reflect.metadata装饰器来添加 元数据
import "reflect-metadata"
@Reflect.metadata("n", 1)
class A {
@Reflect.metadata("n", 2)
public static method1() {
}
@Reflect.metadata("n", 4)
public method2() {
}
}
// or
Reflect.defineMetadata('n', 1, A);
Reflect.defineMetadata('n', 2, A, 'method1');
let obj = new A();
Reflect.defineMetadata('n', 3, obj);
Reflect.defineMetadata('n', 4, obj, 'method2');
console.log(Reflect.getMetadata('n', A));
console.log(Reflect.getMetadata('n', A, ));获取
Reflect.getMetadata(metadataKey, target, propertyKey)
参数的含义与 defineMetadata 对应
使用元数据的 log 装饰器
import "reflect-metadata"
function L(type = 'log') {
return function(target: any) {
Reflect.defineMetadata("type", type, target);
}
}
// 装饰器函数
function log(callback: Function) {
return function(target: any, name: string, descriptor: PropertyDescriptor) {
let value = descriptor.value;
let type = Reflect.getMetadata("type", target);
descriptor.value = function(a: number, b: number) {
let result = value(a, b);
if (type === 'log') {
console.log('日志:', {
name,
a,
b,
result
})
}
if (type === 'storage') {
localStorage.setItem('storageLog', JSON.stringify({
name,
a,
b,
result
}));
}
return result;
}
}
}
// 原始类
@L('log')
class M {
@log
static add(a: number, b: number) {
return a + b;
}
@log
static sub(a: number, b: number) {
return a - b;
}
}
let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2); 使用 emitDecoratorMetadata
在 tsconfig.json 中有一个配置 emitDecoratorMetadata,开启该特性,typescript 会在编译之后自动给 类、方法 、访问符、属性、参数 添加如下几个元数据
- design:type:被装饰目标的类型
- 成员属性:属性的标注类型
- 成员方法:
Function类型
- design:paramtypes
- 成员方法:方法形参列表的标注类型
- 类:构造函数形参列表的标注类型
- design:returntype
- 成员方法:函数返回值的标注类型
import "reflect-metadata"
function n(target: any) {
}
function f(name: string) {
return function(target: any, propertyKey: string, descriptor: any) {
console.log( 'design type', Reflect.getMetadata('design:type', target, propertyKey) );
console.log( 'params type', Reflect.getMetadata('design:paramtypes', target, propertyKey) );
console.log( 'return type', Reflect.getMetadata('design:returntype', target, propertyKey) );
}
}
function m(target: any, propertyKey: string) {
}
@n
class B {
@m
name: string;
constructor(a: string) {
}
@f('')
method1(a: string, b: string) {
return 'a'
}
}编译后
__decorate([
m,
__metadata("design:type", String)
], B.prototype, "name", void 0);
__decorate([
f(''),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], B.prototype, "method1", null);
B = __decorate([
n,
__metadata("design:paramtypes", [String])
], B);不同请求方法的装饰器生成
import { Router } from 'express'
export const router = Router()
enum Method {
get = 'get',
post = 'post'
}
// 将类中原型上的路径和请求方法添加路由
export function controller(target: any) {
for (let key in target.prototype) {
const path = Reflect.getMetadata('path', target.prototype, key)
const method: Method = Reflect.getMetadata('method', target.prototype, key)
const handler = target.prototype[key]
if (path && method && handler) {
router[method](path, handler)
}
}
}
// 在类的原型对象中添加请求路径和请求方法
function getRequestDecorator(type: Method) {
return function (path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata('path', path, target, key)
Reflect.defineMetadata('method', type, target, key)
}
}
}
export const get = getRequestDecorator(Method.get)
export const post = getRequestDecorator(Method.post)
export const put = getRequestDecorator(Method.put)
export const del = getRequestDecorator(Method.delete)使用
@controller
class LoginController {
@post('/login')
login(req: BodyRequest, res: Response) {}
@get('/logout')
logout(req: BodyRequest, res: Response) {}
@get('/')
home(req: BodyRequest, res: Response) {}
}中间件装饰器
import 'reflect-metadata';
import { RequestHandler } from 'express';
import { CrowllerController, LoginController } from '../controller';
export function use(middleware: RequestHandler) {
return function(target: CrowllerController | LoginController, key: string) {
const originMiddlewares = Reflect.getMetadata('middlewares', target, key) || [];
originMiddlewares.push(middleware);
Reflect.defineMetadata('middlewares', originMiddlewares, target, key);
};
}
export function controller(target: any) {
for (let key in target.prototype) {
const path = Reflect.getMetadata('path', target.prototype, key);
const method: Method = Reflect.getMetadata('method', target.prototype, key);
const handler = target.prototype[key];
const middleware = Reflect.getMetadata('middleware', target.prototype, key);
if (path && method && handler) {
if (middleware && middleware.length) {
router[method](path, ...middleware, handler);
} else {
router[method](path, handler);
}
}
}
}使用
const checkLogin = (req: Request, res: Response, next: NextFunction) => {
const isLogin = req.session ? req.session.login : false;
if (isLogin) {
next();
} else {
res.json(getResponseData(null, '请先登录'));
}
};
@controller
class LoginController {
@post('/login')
@use(checkLogin)
login(req: BodyRequest, res: Response) {}
@get('/logout')
@use(checkLogin)
logout(req: BodyRequest, res: Response) {}
@get('/')
@use(checkLogin)
home(req: BodyRequest, res: Response) {}
}










