装饰器
装饰器(也叫注解)对一个类/方法/属性/参数的装饰。它是对这一系列代码的增强,并且通过自身描述了被装饰的代码可能存在行为改变。
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression
这种形式,expression
求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
装饰器分为:类装饰器、方法装饰器、属性装饰器、参数装饰器
要使用装饰器需要在tsconfig.json
中配置:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
一个简单的示例:
在一个User
类中含有一个changeName
方法,在调用方法前,需要调用validate
方法进行校验,此种方式缺点:侵入了changeName
主流程代码,并且耦合性提高
class User {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
public validate(newName: string) {
if (!newName) {
throw Error("name is invalid");
}
}
public changeName(newName: string) {
this.validate(newName)
this.name = newName;
}
}
借助装饰器可以进行以下改造,不侵入代码,降低耦合度
const validate = (target: any,propertyKey: string,descriptor: PropertyDescriptor) => {
let method = descriptor.value;
descriptor.value = function(newValue: string) {
if (!newValue) {
throw Error("name is invalid");
} else {
method.apply(this, arguments)
}
}
}
class User {
name: string;
id: number;
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
public validate(newName: string) {
if (!newName) {
throw Error("name is invalid");
}
}
@validate
public changeName(newName: string) {
this.name = newName;
}
}
装饰器工厂 - 可以传参的装饰器
function print(msg: string) {
return function(traget: string, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`msg: ` + msg)
}
}
class User {
@print('getName evaluated')
public getName () {
return 'name'
}
}
// 调用getName时会输出: getName evaluated
类装饰器
类装饰器表达式会在运行时当做函数被调用,类的构造函数作为其唯一的参数
const sealed = (constructor: Function) {
Object.seal(constructor)
Object.seal(constructor.prototype)
}
@sealed
class User{}
方法装饰器
方法装饰器会在运行时当做函数被调用,有3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字
3、成员的属性描述符{ value: any, writable: boolean, enumerable: boolean, configurable: boolean }
示例参考示例1
访问器装饰器
和函数装饰器一样,只不过是装饰与访问器上
function print(msg: string) {
return function(traget: string, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`msg: ` + msg)
}
}
class User {
private _name: string
@print('getName evaluated')
get name () {
return this._name
}
}
属性装饰器
属性装饰器会在运行时当做函数被调用,有2个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字
const freeze = (target, propertyKey) => {
target[propertyKey] = 0;
Object.defineProperty(target, propertyKey, {
configurable: false,
writable: false,
});
}
class User {
@freeze
id: number
}
参数装饰器
参数装饰器会在运行时当作函数被调用,有3个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字
3、参数在函数参数列表的索引
const required = function (target: any, propertyKey: string, paramterIndex: number) {
console.log(target[propertyKey].prototype)
}
class User {
private _name: string
changeName (@required name: string) {
this._name = name
}
}
将validate
示例用在参数装饰器上
// 定义一个私有 key
const requiredMetadataKey = Symbol("required")
// 定义参数装饰器,大概思路就是把要校验的参数索引保存到成员中
const required = function (target, propertyKey: string, parameterIndex: number) {
// 参数装饰器只能拿到参数的索引
if (!target[propertyKey][requiredMetadataKey]) {
target[propertyKey][requiredMetadataKey] = {}
}
// 把这个索引挂到属性上
target[propertyKey][requiredMetadataKey][parameterIndex] = true
}
// 定义一个方法装饰器,从成员中获取要校验的参数进行校验
const validate = function (target, propertyKey: string, descriptor: PropertyDescriptor) {
// 保存原来的方法
let method = descriptor.value
// 重写原来的方法
descriptor.value = function () {
let args = arguments
// 看看成员里面有没有存的私有的对象
if (target[propertyKey][requiredMetadataKey]) {
// 检查私有对象的 key
Object.keys(target[propertyKey][requiredMetadataKey]).forEach(parameterIndex => {
// 对应索引的参数进行校验
if (!args[parameterIndex]) throw Error(`arguments${parameterIndex} is invalid`)
})
}
}
}
class User {
name: string
id: number
constructor(name:string, id: number) {
this.name = name
this.id = id
}
// 方法装饰器做校验
@validate
changeName (@required newName: string) { // 参数装饰器做描述
this.name = newName
}
}
反射
反射实在运行时动态获取一个对象的一切信息:方法/属性等等,特点在于动态类型反推导,在typescript
中,反射的原理是通过设计阶段对对象注入元数据信息,在运行阶段读取注入的元数据,从而得到对象信息。
使用时需要在tsconfig.json
配置并且引入reflect-metadata
:
{
"compilerOptions": {
"target": "ES5",
"emitDecoratorMetadata": true
}
}
反射可以获取对象的:
1、对象的类型
2、对象/静态属性的信息(类型)
3、方法的参数类型,返回类型
默认注入的三种元数据:
1、design:type
:成员类型
2、design:paramtypes
:成员所有参数类型
3、design:returntype
:成员返回类型
使用反射实现动态路由和自动注入生成
默认元数据示例
import "reflect-metadata";
const Prop = (): PropertyDecorator => {
return (target, key: string) => {
const _type = Reflect.getMetadata('design:type', target, key)
console.log("type: ", _type) // type: [Function: String]
}
}
const Method = (): MethodDecorator => {
return (traget: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const _params = Reflect.getMetadata('design:paramtypes', traget, propertyKey)
console.log('params: ', _params) // params: [ [Function: String] ]
}
}
const GetReturn = (): MethodDecorator => {
return (traget: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const _return = Reflect.getMetadata('design:returntype', traget, propertyKey)
console.log('return type: ', _return) // return type: [Function: String]
}
}
class TestReflect {
@Prop()
public name: String
@GetReturn()
public getName (): String {
return this.name
}
@Method()
public setName (name: String) {
this.name = name
}
}
上面示例拿到了返回,类型和参数,就可以做些略微高级的事
简单的自动注入
import 'reflect-metadata'
const Autowired = (target: any, key: string) => {
const type = Reflect.getMetadata('design:type', target, key)
return target[key] = new type()
}
class ServiceDemo {
public getService () {
console.log('aaa')
}
}
class ReflectDemo {
@Autowired
serviceDemo: ServiceDemo
testAutowired () {
this.serviceDemo.getService()
}
}
let reflectDemo = new ReflectDemo()
reflectDemo.testAutowired() // aaa
自动生成路由
import 'reflect-metadata'
const Prefix = (path: string): ClassDecorator => {
return (target: any) => {
Reflect.defineMetadata(Symbol.for('PREFIX'), path, target)
}
}
const getMethodDecorator = (method: string) => (path: string): MethodDecorator => {
return (target: any, key: string, descriptor: PropertyDescriptor) => {
Reflect.defineMetadata(Symbol.for('PATH_METADATA'), path, descriptor.value)
Reflect.defineMetadata(Symbol.for('METHOD_METADATA'), method, descriptor.value)
}
}
const Get = getMethodDecorator('get')
const Post = getMethodDecorator('post')
@Prefix('/test')
class ReflectDemo {
@Get('/get-method')
getMethod () {
}
@Post('/post-method')
postMethod () {
}
}
const mapRoutes = (instance: Object) => {
const prototype = Object.getPrototypeOf(instance)
const methodNames = Object.getOwnPropertyNames(prototype).filter(item => item !== 'constructor' && item !== 'Function')
const prefix = Reflect.getMetadata(Symbol.for('PREFIX'), prototype['constructor'])
return methodNames.map(methodName => {
const fn = prototype[methodName]
if (isFunction(fn) {
const route = Reflect.getMetadata(Symbol.for('PATH_METADATA'), fn)
const method = Reflect.getMetadata(Symbol.for('METHOD_METADATA'), fn)
return {
route,
method,
fn,
methodName
}
}
})
}
mapRoutes(new ReflectDemo())