type
Post
status
Published
date
Aug 13, 2022
slug
summary
tags
开发
前端
typescript
category
学习思考
icon
password
Property
Aug 13, 2022 02:33 PM
装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明方法访问符属性参数上
装饰器使用@decorators这种形式,decorators 求值之后必须为一个函数,他会在运行时被调用,被装饰的声明信息作为参数传入(有点类似高阶函数)
装饰器组合
多个装饰器可以同时应用到一个声明上,就像下面的示例:
  • 书写在同一行上
@f @g x
  • 书写在多行上:
@f @g x
当多个装饰器应用于一个声明上,他们求值方式与复合函数相似。在这个模型下,当复合f和g时,复合的结果(fg)(x)等同于f(g(x))。
同样在typescript里面,当多个装饰器应用在一个声明上时会进行如下操作:
  1. 由上至下一次对装饰器表达式求值
  1. 求值的结果会被当作函数,由上至下依次调用
即以上代码这样运行
f 赋值 g 赋值 g 调用 f 调用
类装饰器
类装饰器会在运行时被当作函数调用,类的构造函数作为其唯一的参数
// 为类添加一个state属性 function addState<T extends {new(...args:any[]):{}}>(constructor: T){ return class extends constructor { state = { count: 1 } } } @addState class Test { constructor(...props: any){ console.log(props) } } console.log(new Test())
方法装饰器
方法装饰器表达式会在运行时被当作函数调用,传入下列三个参数
  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字
  • 成员的属性描述符
如果方法装饰器返回一个值,它会被用作方法的属性描述符
//设置类的方法不可写 function unWrite(){ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.writable = false }; } class Test{ @unWrite() name(){ return 'something' } } const test = new Test() test.name = () => '555' //TypeError: Cannot assign to read only property 'name' of object '#<Test>'
访问器装饰器
访问器装饰器表达式与方法装饰器参数和返回值规则一样
//通过装饰器修改访问器的属性描述符的configurable属性 function configurable(value: boolean) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.configurable = value; } } class Point { private _x: number private _y: number constructor(x: number, y: number){ this._x = x this._y= y } @configurable(false) get x() {return this._x} @configurable(false) get y() {return this._x} }
属性装饰器
属性装饰器会在运行时被当作函数调用,传入下面两个参数
  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字。
//通过属性装饰器log属性变化 class Test { @logChange name = 'test'; } function logChange(target: any, key: string){ var _val = target[key] var getter = function () { console.log(`Get: ${key} => ${_val}`) return _val } var setter = function (newVal: any) { console.log(`Set: ${key} => ${newVal}`) _val = newVal } if (delete target[key]) { Object.defineProperty(target, key, { get: getter, set: setter, enumerable: true, configurable: true }) } } // Set: name => test const test = new Test() // Set: name => hello test.name = 'hello' // Get: name => hello console.log(test.name) // hello
参数装饰器
参数装饰器会有三个参数,没有返回值
  • 正在装饰的类的原型
  • 包含要装饰的参数的方法的名称
  • 要装饰的参数的索引
//使用参数装饰器为类的原型添加函数参数的log class Person { public name: string public surname: string constructor(name: string, surname: string) { this.name = name this.surname = surname } public saySomething(@logParameter something: string): string { return this.name + " " + this.surname + " says: " + something } } function logParameter(target: any, key: string, index: number){ var metadataKey = `log_${key}_parameters` if(Array.isArray(target[metadataKey])){ target[metadataKey].push(index) }else{ target[metadataKey] = [index] } } const person = new Person('Tong', 'Gang') console.log(person.saySomething('code'))// Tong Gang says: code console.log(Person.prototype)//{ log_saySomething_parameters: [ 0 ] }
装饰工厂
装饰器工厂是一种接受任意数量参数的函数,并且必须返回一种参数的装饰器
@logClass class Person { @logProperty public name: string public surname: string constructor(name: string, surname: string){ this.name = name this.surname = surname } public saySomething(@logParameter something: string): string { return this.name + " " + this.surname + " says: " + something } }
以上的代码可以正常工作,但是如果我们可以在任何地方使用装饰器而不用担心其类型将更好,如下所示:
@log class Person { @log public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @log public saySomething(@log something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
我们可以通过装饰器工厂包装来实现这一点。装饰器工厂能够通过检查传递给装饰器的参数返回不同装饰器
function log(...args: any[]){ switch(args.length){ case 1: return logClass.apply(this, args) case 2: return logProperty.apply(this, args) case 3: if(typeof args[2] === "number"){ return logParameter.apply(this, args) } return logMethod.apply(this, args) default: throw new Error("decorators are not valid here!") } }
元数据反射(reflect-metadata)
反射用来描述能够检查同一系统(或其自身)中的其他代码的代码
反射对于许多用例(组合/依赖注入,运行时类型断言,测试)很有用
typescript中的元数据反射需要使用reflect-metadata仓库
npm install reflect-metadata
我们需要实现自己的装饰器并且使用一个可用的反射元数据设计键。目前只有三种可用:
  • 类型元数据design:type
  • 参数类型元数据design:paramtypes
  • 返回类型元数据design:returntype
我们可以使用元数据反射实现一个简单的DI
import 'reflect-metadata' type Constructor<T = any> = new (...args: any[]) => T; const Injectable = (): ClassDecorator => target => {}; class OtherService { a = 1; } @Injectable() class TestService { constructor(public readonly otherService: OtherService) {} testMethod() { console.log(this.otherService.a); } } const Factory = <T>(target: Constructor<T>): T => { // 获取所有注入的服务 const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService] const args = providers.map((provider: Constructor) => new provider()); return new target(...args); }; Factory(TestService).testMethod(); // 1
rxjs入门前端构建工具浅谈