Typescript 装饰器 + 反映元数据
Typescript decorators + Reflect metadata
我正在使用 属性 装饰器 Field
,它将其键推送到 fields
反映元数据 属性:
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)
}
}
然后我有一个抽象基础-class Form
访问 getter 附件中的元数据:
abstract class Form {
get fields() {
return Reflect.getMetadata('fields', this) || [];
}
}
到目前为止,我已经能够成功地使用它来区分表单字段和其他 class 属性。考虑这些 classes:
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']
当我定义一个姐妹class到AdminForm
-MemberForm
时出现问题。当 Form
存在多个子 class 时,似乎 fields
getter returns 所有字段:
class MemberForm extends UserForm {
@Field()
memberSince: Date;
}
const form = new AdminForm()
console.log(form.fields)
// ['roles', 'firstName', 'lastName', 'memberSince'] <--!!!
这对我来说毫无意义。为什么 memberSince
字段出现在 AdminForm
的实例上?如何在不同的 subclasses 上定义不同的字段?
问题是 getMetadata
沿着原型链向下移动,并且总是 return 在基类型上定义的内容(因为它首先被分配)。需要使用getOwnMetadata
获取当前class的数组字段,只有在添加新字段时,获取字段时需要沿着属性链向上走,获取所有base class 个字段。
这应该有效:
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"]
我正在使用 属性 装饰器 Field
,它将其键推送到 fields
反映元数据 属性:
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)
}
}
然后我有一个抽象基础-class Form
访问 getter 附件中的元数据:
abstract class Form {
get fields() {
return Reflect.getMetadata('fields', this) || [];
}
}
到目前为止,我已经能够成功地使用它来区分表单字段和其他 class 属性。考虑这些 classes:
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']
当我定义一个姐妹class到AdminForm
-MemberForm
时出现问题。当 Form
存在多个子 class 时,似乎 fields
getter returns 所有字段:
class MemberForm extends UserForm {
@Field()
memberSince: Date;
}
const form = new AdminForm()
console.log(form.fields)
// ['roles', 'firstName', 'lastName', 'memberSince'] <--!!!
这对我来说毫无意义。为什么 memberSince
字段出现在 AdminForm
的实例上?如何在不同的 subclasses 上定义不同的字段?
问题是 getMetadata
沿着原型链向下移动,并且总是 return 在基类型上定义的内容(因为它首先被分配)。需要使用getOwnMetadata
获取当前class的数组字段,只有在添加新字段时,获取字段时需要沿着属性链向上走,获取所有base class 个字段。
这应该有效:
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"]