一、前言
在之前的 《用TypeScript写前端》 专栏文章中,我们已经讲过了大量装饰器来配置表格、表单、文案、验证等相关的文章。
今天我们主要来讲一下如何通过 Relect
来存储装饰器传入的各种配置项,以及在何时取出配置项来使用。
二、先看示例
例如我们声明了一个 角色类
的 RoleEntity
对象:
@Model({
label: '角色',
})
export class RoleEntity extends BaseEntity {
@Table({
forceShow: true,
})
@Search()
@Form({
requiredString: true,
})
@Field({
label: '角色名称',
})
name!: string
@Table({
forceShow: true,
})
@Search()
@Form({
placeholder: '不填按编码规则自动生成',
})
@Field({
label: '角色编码',
})
code!: string
@Field({
type: MenuEntity,
array: true,
})
menuList!: MenuEntity[]
@Field({
type: PermissionEntity,
array: true,
}) permissionList!: PermissionEntity[]
}
接下来,我们假设从某个地方得到了一个 RoleEntity
对象,我们打印出来看看:
const role = new RoleEntity()
console.log(role)
咦,为什么打印出来的原型上还挂了这么多的东西?这都是些什么玩意?
原来,上面的 RoleEntity
类中装饰器配置的所有属性,都被通过 Reflect
完整的存在了对象的原型上。
Reflect
是一个内置对象,它提供了一些方法,用于操作对象。这些方法与 Object
的方法类似,但更安全,更易用。
这里我们需要使用到 Reflect
的两个方法:
三、Reflect.defineProperty
如其名,表示我们将定义一个属性值,于是我们可以在装饰器中调用这个方法,来把装饰器的参数都存到对象的原型上。
/**
* 反射添加属性
* @param target 目标类
* @param key 属性名称
* @param value 装饰器的参数
*/
Reflect.defineProperty(target, key, {
enumerable: false,
value,
writable: false,
configurable: true,
})
这样,我们就成功的存储的装饰器的配置,那么具体要存些什么你就可以自由发挥啦。
我们存储了下面这些配置:
3.1 类的信息
我们存储了这些信息:
export interface IModelConfig {
/**
* ### 模型名称
*/
label?: string
/**
* ### 模型属性前缀
*/
fieldPrefix?: string
/**
* ### 是否显示搜索框
*/
showSearch?: boolean
/**
* ### 添加按钮的标题
*/
addTitle?: string
/**
* ### 添加权限标识 还有各种增删改查等通用的权限表示的配置
*/
addPermission?: string
/**
* ### 全局隐藏字段列选择器
*/
hideFieldSelector?: boolean
}
3.2 属性的信息
例如 UserEntity
的 nickname
属性,我们存储了关于属性的各种表单、表格、搜索等配置:
3.2.1 表单信息
包含了属性是否必填、输入格式、是否下拉框、可选词典、默认值、输入提示信息等等
3.2.2 表格信息
包含了属性是否显示到表格列、列宽、列排序、数据格式、是否字典、输入类型(如数字、字符串、日期等)、对齐方式、是否可复制等信息
同时还有存储一个数组用于表示,哪些列配置了装饰器。于是你的表格组件可以取出来自动渲染列了。
3.2.3 搜索信息
包含了是否展示搜索框到表格顶部,也支持了很多关于搜索的数据类型等信息。
3.3 存储配置的意义
存储这些配置,是为了在 Table
、Form
、Search
等组件中,可以取出这些配置,然后进行各种渲染。
接下来,我们来看看如何取出这些配置并使用:
3.4 Reflect.get
Reflect.get
方法用于读取对象的属性值。
我们可以封装一个组件,比如 Table
,要求它接受一个 Entity
类的参数,于是使用时即可这样:
<Table :entity="UserEntity" />
接下来,表格组件内部即可通过 UserEntity
获取到所有的表格列、列的具体配置,来完成表格的正常渲染:
// 举个例子
const fieldConfigs = Reflect.get(UserEntity, 'fields')
然后就可以通过循环渲染表格列了。
四、为何要这么使用
回到最开始的截图中,我们可以看到,当我们拿到一个RoleEntity
对象时,它上面挂了很多东西,这些东西都是通过装饰器来配置的。
于是我们可以在调试阶段清楚的看到这些配置的信息,也可以在组件中取出这些配置,然后完成更多的事情。
数据对象不在是纯粹的数据信息,也同时包含了这个数据对象的一些其他的配置信息,我们在参数传递的时候就能同时也传递这些配置,可以实现更多更方便的功能。
而不是当我需要你渲染一个表格的时候,我会用下面传统的方式提供给你多个参数:
interface User {
name: string
age: number
}
const list: Ref<User[]> = ref([
{
name: '张三'
age: 18
}
])
const fields = ref([
{
field: 'name',
label: '姓名'
},
{
field: 'age',
label: '年龄'
isNumber: true
}
])
我们也只是将 User
结构的声明和 fields
的配置通过 类和装饰器
放到了一起而已:
@Model({label: '用户'})
class User {
@Field({label: '姓名'})
name!: string
@Field({label: '年龄', isNumber: true})
age!: number
}
五、总结
今天算是 2025 年的开篇,祝大家新的一年开心快乐~
昨天的文章是个意外~~~