Автоматическое добавление декоратора свойств Typescript в прототип

Я хотел бы создать украшение для свойств, которое позволит мне автоматически добавлять свойство без необходимости записывать его в конструктор. Я довольно новичок в сценариях Java/Type, поэтому, надеюсь, я не слишком сильно испортил это. Кажется, я не могу этого сделать, вот что у меня есть до сих пор...

Цель состоит в том, чтобы что-то вроде:

class A {
  @uuid
  'property': string;
  'another': string;
}

function uuid(target: any, key: string): void {
  Reflect.defineMetadata('isUuid', true, target, key);
}

Позже я смогу использовать конструктор A new () => Object для получения списка всех свойств и того, являются ли они UUID. Я предполагаю, что это будет выглядеть примерно так:

Object.keys(A).forEach(key => {
  console.log(`[${key}].isUuid? ${Reflect.getMetadata('isUuid', A, key) === true}`);
});

Что, надеюсь, даст что-то вроде:

[property].isUuid? true
[another].isUuid? false

В качестве примечания: если я изменю класс A на:

class A {
  constructor() {
    this.property = undefined;
    this.another = undefined;
  }
  @uuid
  'property': string;
  'another': string;
}

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


person TheHebrewHammer    schedule 27.09.2017    source источник
comment
зачем вам не-uuid свойства?   -  person jcalz    schedule 27.09.2017
comment
Я пытаюсь использовать декораторы, чтобы добавить дополнительные критерии к свойству. В конечном итоге это будет использоваться в качестве модели для таблицы базы данных.   -  person TheHebrewHammer    schedule 27.09.2017


Ответы (1)


Если вам нужно получить доступ к каждому свойству, вам нужно будет украсить каждое свойство. Поскольку reflect-metadata API не позволяет вам перечислить targetKey, используемые в объекте, вы должны хранить метаданные в самом объекте.

Сначала определите, какую информацию вы хотите отметить для каждого свойства. Пока есть isUuid:

interface DbPropInfo {
  isUuid: boolean; 
  // other stuff
}

Полезно иметь значение по умолчанию для каждой части информации, чтобы аннотации декоратора были краткими:

const defaultDbPropInfo: DbPropInfo = {
  isUuid: false 
}

Нам нужно сохранить метаданные в виде объекта, ключи которого совпадают с ключами свойств класса, а значениями являются DbPropInfo, которые мы установили для этих свойств. Вот тип для этого объекта:

interface DbProps {
  [k: string]: DbPropInfo;
}

А теперь о декораторе:

const dbPropsKey = 'dbProps';

function dbProp(info?: Partial<DbPropInfo>) {
  return function(target: any, key: string): void {
    if (!Reflect.hasMetadata(dbPropsKey, target)) {
      Reflect.defineMetadata(dbPropsKey, {}, target);
    }
    const dbProps: DbProps = Reflect.getMetadata(dbPropsKey, target);
    dbProps[key] = Object.assign({}, defaultDbPropInfo, info);
  }
}

И чтобы получить декорированные данные:

function getDbProps(ctor: { prototype: any }): DbProps | undefined {
  return Reflect.getMetadata(dbPropsKey, ctor.prototype);
}

Наконец, мы можем попробовать это на вашем классе:

class A {
  @dbProp({ isUuid: true }) property: string;
  @dbProp() another: string;
}

Посмотрим, работает ли это:

console.log(JSON.stringify(getDbProps(A)));
// { 
//   "property": {"isUuid": true},
//   "another": {"isUuid": false}
// }

Это работает для вас?

person jcalz    schedule 28.09.2017
comment
Интересно, это отличная идея. В итоге (прошлой ночью) я нашел способ внедрить свойство, сначала проверив, находится ли key в Object.getOwnPropertyNames(target), а затем добавив его с помощью Object.defineProperty(target, key, ...). Но я думаю, что ваш способ имеет больше смысла. - person TheHebrewHammer; 28.09.2017