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时,复合的结果(f∘g)(x)等同于f(g(x))。
同样在typescript里面,当多个装饰器应用在一个声明上时会进行如下操作:
- 由上至下一次对装饰器表达式求值
- 求值的结果会被当作函数,由上至下依次调用
即以上代码这样运行
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
- Author:double
- URL:https://double2.wiki//article/e94aefb0-8b72-4396-a53a-9d7fe2b8f76a
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts