今天看项目的代码,发现有同事给一个typescript的属性装饰器添加了修饰,强制调用Object.getOwnPropertyDescriptor返回了Descriptor的内容,不清楚为啥这么写,了解后发现是为了解决属性装饰器不生效的问题。这里简单记录一下
一、问题背景
先来看个简单的装饰器例子
代码语言:javascript复制import 'reflect-metadata';
function simpleDecorator(target: any, propertyName: string) {
console.log('Applying decorator to:', target, propertyName);
let value = target[propertyName];
const getter = () => {
console.log(`Getting value of ${propertyName}:`, value);
return value;
};
const setter = (newValue: any) => {
console.log(`Setting value of ${propertyName}:`, newValue);
value = newValue;
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
});
}
class Example {
@simpleDecorator
public myProperty: string = 'Hello, world!';
}
const example = new Example();
console.log('Example:', example);
console.log('Getting myProperty:', example.myProperty);
example.myProperty = 'New value';
console.log('Getting myProperty:', example.myProperty);
这里会打印出来如下的内容
代码语言:javascript复制Applying decorator to: {} myProperty
Example: Example { myProperty: 'Hello, world!' }
Getting myProperty: Hello, world!
Getting myProperty: New value
这里会发现,setter相关的代码没有被执行,这是因为使用属性装饰器来修改属性的行为(例如拦截属性的访问或修改),则需要返回一个属性描述符。属性描述符包含有关属性的配置信息,例如属性是否可写(writable
)、是否可枚举(enumerable
)以及属性的get
和set
函数等
二、问题解决
添加Object.getOwnPropertyDescriptor(target, propertyName) 返回属性描述符,即可解决问题
代码语言:javascript复制import 'reflect-metadata';
function simpleDecorator(target: any, propertyName: string) {
console.log('Applying decorator to:', target, propertyName);
let value = target[propertyName];
const getter = () => {
console.log(`Getting value of ${propertyName}:`, value);
return value;
};
const setter = (newValue: any) => {
console.log(`Setting value of ${propertyName}:`, newValue);
value = newValue;
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
});
return Object.getOwnPropertyDescriptor(target, propertyName)
}
class Example {
@simpleDecorator
public myProperty: string = 'Hello, world!';
}
const example = new Example();
console.log('Example:', example);
console.log('Getting myProperty:', example.myProperty);
example.myProperty = 'New value';
console.log('Getting myProperty:', example.myProperty);
添加后可以看到返回了如下的内容
代码语言:javascript复制Example: Example {}
Getting value of myProperty: undefined
Getting myProperty: undefined
Setting value of myProperty: New value
Getting value of myProperty: New value
Getting myProperty: New value
可以看到setter函数已经成功执行了,不过控制台打印的example对象是空的,这是因为属性被装饰器处理不再存在对象上,但是仍然可以通过example.myProperty访问。不过这里这样处理后,初始化赋值的Hello, world!丢失了,这里可以使用下面的方式修复一下。
代码语言:javascript复制import 'reflect-metadata';
function simpleDecorator(initString: string) {
return function (target: any, propertyName: string, ) {
console.log('Applying decorator to:', target, propertyName);
let value = initString; // 设置初始值
const getter = () => {
console.log(`Getting value of ${propertyName}:`, value);
return value;
};
const setter = (newValue: any) => {
console.log(`Setting value of ${propertyName}:`, newValue);
value = newValue;
};
Object.defineProperty(target, propertyName, {
get: getter,
set: setter,
});
return Object.getOwnPropertyDescriptor(target, propertyName)
}
}
class Example {
@simpleDecorator('success ~!')
public myProperty: string;
}
const example = new Example();
console.log('Example:', example);
console.log('Getting myProperty:', example.myProperty);
example.myProperty = 'New value';
console.log('Getting myProperty:', example.myProperty);
最后的结果如下
代码语言:javascript复制Applying decorator to: {} myProperty
Example: Example {}
Getting value of myProperty: success ~!
Getting myProperty: success ~!
Setting value of myProperty: New value
Getting value of myProperty: New value
Getting myProperty: New value
三、小结
这里分享了一点装饰器使用遇到的问题,实际开发,可能会遇到babel编译导致的属性装饰器失败的问题,原理就是因为没有返回属性描述符,这里可以修复下装饰器,强制返回Object.getOwnPropertyDescriptor(target, propertyName)解决