typescript属性装饰器不生效的问题

2023-07-25 10:32:11 浏览数 (1)

今天看项目的代码,发现有同事给一个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)以及属性的getset函数等

二、问题解决

添加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)解决

0 人点赞