Angular RxJS TypeScript 转换字典添加validator属性方案
问题背景
现有返回类型为 { [key: string]: QueryBuilderFieldsDto } 的字典对象,原始结构如下:
{ "2221": { "label": "Risk Domain", "name": "2221", "type": "dropdown", "typeLabel": "Dropdown", "isStandardField": false, "fieldEntityType": 2, "fieldEntityTypeLabel": "Risk", "options": [{ "uniqueId": null, "id": 2636, "name": "Unassigned", "isOrganizationUnit": false }] }, "2222": { "label": "Number Field", "name": "2222", "type": "number", "typeLabel": "Number", "isStandardField": false, "fieldEntityType": 2, "fieldEntityTypeLabel": "Risk" } }
QueryBuilderFieldsDto 接口的属性定义如下:
export interface QueryBuilderFieldsDto { name: string; label: string; value?: string; type: string; nullable?: boolean; options?: Option[] | FieldOptionsDto[]; operators?: string[]; defaultValue?: any; defaultOperator?: any; isStandardField: boolean; fieldEntityType: number; fieldEntityTypeLabel?: string; typeLabel?: string; validator?: (rule: Rule, parent: RuleSet) => any | null; }
实现目标
为字典对象的每个字段项追加自定义validator属性,校验逻辑如下:
validator: (rule) => { if (rule.value == undefined || rule.value == "") { return { age: { rule: rule, message: 'Value cannot be empty' } } } }
最终效果参考(以2222字段为例):
"2222": { "label": "Number Field", "name": "2222", "type": "number", "typeLabel": "Number", "isStandardField": false, "fieldEntityType": 2, "fieldEntityTypeLabel": "Risk", "validator": (rule) => { if (rule.value == undefined || rule.value == "") { return { age: { rule: rule, message: 'Value cannot be empty' } } } } }
现存问题
当前通过如下代码订阅Observable获取数据:
this.metricCreateConfigureStore.queryBuilderFields$.subscribe(item => { this.fields = item as { [key: string]: QueryBuilderFieldsDto }; })
其中queryBuilderFields$的类型为 queryBuilderFields$: Observable<{ [key: string]: QueryBuilderFieldsDto; }>,订阅拿到的返回值默认不包含validator属性。已知可通过RxJS的pipe搭配map操作符完成对象转换,要求map操作的返回值类型仍为{ [key: string]: QueryBuilderFieldsDto },但自行编写转换代码时VSCode会提示类型错误,需要可直接运行的正确实现方案。
正确实现代码
类型报错的核心原因是遍历对象追加属性时,TS没有正确识别新增属性的类型匹配关系,同时直接修改源状态对象也会带来隐性风险,正确代码如下:
import { map } from 'rxjs/operators'; // 抽离通用校验函数,避免重复创建函数实例 const nonEmptyValidator = (rule: Rule) => { if (rule.value === undefined || rule.value === "") { return { age: { rule: rule, message: 'Value cannot be empty' } } } // 校验通过返回null,符合接口类型定义 return null; }; this.metricCreateConfigureStore.queryBuilderFields$ .pipe( // 显式声明map返回值类型,让TS做结构校验 map((originFields): { [key: string]: QueryBuilderFieldsDto } => { const result: { [key: string]: QueryBuilderFieldsDto } = {}; // 遍历所有字段,生成带validator的新字段对象 for (const fieldKey of Object.keys(originFields)) { result[fieldKey] = { ...originFields[fieldKey], validator: nonEmptyValidator }; } return result; }) ) .subscribe(fields => { this.fields = fields; })
注意事项
- 不要直接修改源Observable返回的对象:如果
queryBuilderFields$取自全局状态store,直接修改原对象属性会破坏状态不可变原则,导致状态更新异常,因此代码中通过新建对象、展开原有属性的方式生成新字段副本 - 给
map操作符显式标注返回值类型,TS会自动校验返回结构是否符合接口要求,避免隐式类型推导带来的类型不匹配报错 - 校验逻辑最后补充
return null,符合接口中validator返回any | null的定义;原逻辑校验通过时无返回值会隐式返回undefined,部分查询构建器组件会将非null返回值判定为校验失败,补充返回null可避免校验逻辑异常 - 校验函数单独抽离定义,避免每次数据流触发都创建新的函数实例,减少不必要的内存开销
内容的提问来源于stack exchange,提问作者San Jaisy
相关产品推荐
相关产品推荐

