#typescript #reflect-metadata
#typescript #отражение-метаданных
Вопрос:
Я использую декоратор свойств, Field
который передает свой ‘ключ к fields
свойству Reflect metadata:
export function Field(): PropertyDecorator {
return (target, key) => {
const fields = Reflect.getMetadata('fields', target) || [];
if (!fields.includes(key)) {
fields.push(key)
}
Reflect.defineMetadata('fields', fields, target)
}
}
Затем у меня есть абстрактный базовый класс Form
, который обращается к метаданным в аксессуаре для получения:
abstract class Form {
get fields() {
return Reflect.getMetadata('fields', this) || [];
}
}
До сих пор мне удавалось успешно использовать это, чтобы отличать поля формы от других свойств класса. Рассмотрим эти классы:
abstract class UserForm extends Form {
@Field()
public firstName: string
@Field()
public lastName: string
get fullName() {
return this.firstName ' ' this.lastName;
}
}
class AdminForm extends UserForm {
@Field()
roles: string[]
}
const form = new AdminForm()
console.log(form.fields)
// ['roles', 'firstName', 'lastName']
Проблема возникает, когда я определяю дочерний класс для AdminForm
— MemberForm
. Когда существует несколько подклассов, Form
кажется, что fields
средство получения возвращает все поля:
class MemberForm extends UserForm {
@Field()
memberSince: Date;
}
const form = new AdminForm()
console.log(form.fields)
// ['roles', 'firstName', 'lastName', 'memberSince'] <--!!!
Для меня это не имеет смысла. Почему memberSince
поле появляется в экземпляре AdminForm
? Как я могу определить разные поля в разных подклассах?
Ответ №1:
Проблема в том, что getMetadata
происходит по цепочке прототипов и всегда возвращает то, что определено в базовом типе (поскольку это присваивается первым). Вам нужно использовать getOwnMetadata
для получения поля массива текущего класса только при добавлении нового поля, а при получении полей вам нужно пройти вверх по цепочке свойств, чтобы получить все поля базового класса.
Это должно сработать:
import 'reflect-metadata'
export function Field(): PropertyDecorator {
return (target, key) => {
const fields = Reflect.getOwnMetadata('fields', target) || [];
if (!fields.includes(key)) {
fields.push(key)
}
Reflect.defineMetadata('fields', fields, target)
}
}
abstract class Form {
get fields() {
let fields = []
let target = Object.getPrototypeOf(this);
while(target != Object.prototype) {
let childFields = Reflect.getOwnMetadata('fields', target) || [];
fields.push(...childFields);
target = Object.getPrototypeOf(target);
}
return fields;
}
}
abstract class UserForm extends Form {
@Field()
public firstName!: string
@Field()
public lastName!: string
get fullName() {
return this.firstName ' ' this.lastName;
}
}
class AdminForm extends UserForm {
@Field()
roles!: string[]
}
const form1 = new AdminForm()
console.log(form1.fields) // ['roles', 'firstName', 'lastName']
class MemberForm extends UserForm {
@Field()
memberSince!: Date;
}
const form2 = new MemberForm()
console.log(form2.fields) // ["memberSince", "firstName", "lastName"]
Комментарии:
1. Интересно! Если я изменю
getMetadata
наgetOwnMetadata
в декораторе свойств, то я получу['roles']
включеноfields
. Итак, что вы подразумеваете под продвижением по цепочке свойств?2. Вау! Это потрясающе! Большое спасибо!