Как создать декоратор, который создает новое свойство класса, являющееся отображением или преобразованием декорированного свойства.

Я пытаюсь понять, как написать декоратор, который использует уже существующее свойство класса и создает сопоставленную или преобразованную версию этого свойства, которая также обновляется каждый раз, когда обновляется значение исходного свойства.

Например, @upperCase создаст новое свойство (с ключом `${DECORATED_PROPERTY}_UPPERCASE`), которое преобразует строку в верхний регистр, а @double создаст новое свойство (с ключом `${DECORATED_PROPERTY}_DOUBLE`), которое удваивает значение каждого числа в массиве.

class MyClass {
  @upperCase word = 'hamburger';
  @double numbers = [1, 2, 3, 4];
}

const foo = new MyClass();
console.log(foo.word_UPPERCASE); // 'HAMBURGER'
console.log(foo.numbers_DOUBLE); // [2, 4, 6, 8]

foo.word = 'new word';
// See the derived property update in accordance with the original when the original changes value
foo.word_UPPERCASE = 'NEW WORD';

Я поиграл с Object.defineProperty и его свойствами get и set, но, похоже, ничего не работает. Например, когда я пробую эту реализацию для @upperCase:

export function upperCase(target:any, propertyKey:string) {
  let value:string = target[propertyKey];

  const getter = function () {
    return value.toUpperCase();      // Error below gets thrown at this line
  }

  const setter = function (newVal:string) {
    value = newVal;
  }

  Object.defineProperty(target, `${propertyKey}_UPPERCASE`, {
    get: getter,
    set: setter,
  })
}

и использовать его в классе как @upperCase word:string = 'foobar';, если я затем console.log(this['word_UPPERCASE']), я получаю следующую ошибку:

TypeError: can't access property "toUpperCase", value is undefined выбрано в строке, прокомментированной выше, где getter пытается вернуть value.toUpperCase().


person davidlav    schedule 22.07.2020    source источник
comment
Геттер должен return this[propertyKey].toUpperCase();. Не очевидно, что должен делать сеттер, поскольку вы не можете снять что-то с верхнего регистра.   -  person Aluan Haddad    schedule 22.07.2020
comment
Благодарю вас! Это сделало это. Я согласен с сеттером, я просто адаптировал другой пример, который я нашел, и подумал, что он необходим для defineProperty, но все, что мне было нужно, это геттер.   -  person davidlav    schedule 23.07.2020
comment
Свойство только с аксессором get очень полезно и позволяет создавать действительно неизменяемые объекты. Вероятно, вы захотите добавить enumerable: true в свой дескриптор. Дополнительные сведения см. в MDN.   -  person Aluan Haddad    schedule 24.07.2020


Ответы (1)


Благодаря Алуану ​​Хаддаду ответ прост:

function upperCase(target:any, propertyKey:string) {

  Object.defineProperty(target, `${propertyKey}_UPPERCASE`, {
    get: function () {
      return this[propertyKey].toUpperCase();
    },
    enumerable: true,
  })

}

Использовать:

class MyClass {
  @upperCase word:string = 'hello';
  word_UPPERCASE!:string;

  constructor() {
    console.log(this.word);
    console.log(this.word_UPPERCASE);
  }
}

// Output:
// hello 
// HELLO
person davidlav    schedule 25.07.2020