作者
? 本文作者:Dynamite0707
背景:
工作中有许多逻辑冗杂、迭代频繁的业务代码,随着迭代将越来越难以维护,一些场景适合通过配置化的方式来处理便于维护。
eg. 表单项非常多并且需要跟随用户选中条件动态切换的场景:
-
当选中设备类型为空调时,有开启和关闭两种状态
- 当选中状态为开启时,又有温度、风速和模式表单项
- 当选中状态为关闭时,则不显示其他表单项
-
当选中设备类型为其他时,又有其他非常多的状态
方案设计:
根据业务场景使用配置化的 Object|Array|Map 处理条件判断逻辑,通常需要配置文件 CONFIG.json。 配置化思路:
- 表驱动法,使用表数据,存储对应的状态处理
- 对表单项进行抽象,制定一份协议去描述每个表单项
- 本质上 if/else 逻辑是一种状态匹配, 我们用动态配置取代很多的状态匹配判断
解决方案:
- 设计协议,把开发巨型表单系统转换成编写 JSON,即我们的配置config:
// DynamicFormOption
id: 树状层级,比如 1-1
type: 组件类型,比如 input、select、inputNumber、还有自定义组件 sliderPercent...
label: 表单项的名称/描述。
prop: 我们提交数据到后段的字段名,比如 [form.name](http://form.name/) 的 'name'
validator: antd formItem 校验器格式
hidden?: 不显示该组件,一般用于需要用到表数据存储对应的状态,但又不需要展示给用户时使用
conditions?: 动态后续条件, 比如select 选中值为 percent 时,会再显示一个表示窗帘开合度的slider滑动条的子表单项
needUnit?: 是否需要一个hidden的formItem上报所需单位
unitProp?: unit上报的字段名
attrs?: 表单项的额外参数
eg: {
placeholder?: 缺省提示
options?: select的选项
type?: 'text' | 'phone' | 'email', input的类型
addonAfter?: input的后缀
stringMode?: inputNumber开启高精度小数支持
....
}
复制代码
eg:
// CONFIG.json
// 根据用户选中的设备类型(1/2/3/...),返回对应的 DynamicFormOption[]
复制代码
- 动态组件:根据配置化数据结构,递归动态渲染联动的组件。
// AsyncFormItems.tsx
const lazyComponent = (componentName?: string) => {
return React.lazy(() => import(`./components/${componentName}`));
};
function dynamicRendeControl(item: DynamicFormOption) {
const componentName = item.type;
const DynamicFormItem = lazyComponent(componentName);
return DynamicFormItem ? <DynamicFormItem {...item.attrs} /> : null;
}
// 递归渲染联动的组件
function renderSubItems(items: DynamicFormOption[]) {
return items.map((item) => {
return (
<React.Fragment key={item.id}>
<Form.Item
label={item.label}
className={styles.inline_form_item}
name={[field.name, item.prop]}
fieldKey={[field.fieldKey, item.prop]}
rules={parseValidator(item.validator)}
key={item.id}
preserve={false}
initialValue={item.defaultValue}
hidden={item.hidden}
validateFirst={true}
// 由于InputNumber组件不支持pattern校验,所以转换为stringMode,填入form中时再转换成数字
normalize={item.type === 'input.number' ? (value) => Number(value) : undefined}
>
{dynamicRendeControl(item)}
</Form.Item>
{item.needUnit && (
<Form.Item
hidden
name={[field.name, item.unitProp || 'unit']}
fieldKey={[field.fieldKey, item.unitProp || 'unit']}
initialValue={item.suffix}
preserve={false}
>
<Input />
</Form.Item>
)}
<Form.Item
noStyle
shouldUpdate={(pre, next) =>
pre[formListName][index]?.[item.prop] !== next[formListName][index]?.[item.prop]
}
>
{({ getFieldValue }) => {
const value = getFieldValue([formListName, index, item.prop]);
if (!value) return null;
const subItems = item.conditions?.[value];
if (!subItems) return null;
return renderSubItems(subItems);
}}
</Form.Item>
</React.Fragment>
);
});
}
function parseValidator(rules?: BaseRule[]): ValidatorRule[] | undefined {
if (!rules) return rules;
const newRules = rules.map((rule) => {
if (rule.pattern) {
try {
// 实践上传递的是正则的字符串,所以需要再转一遍
return {
...rule,
pattern: new RegExp(rule.pattern.slice(1, -1)),
};
} catch (error) {
console.log('parseValidator erro', error);
return { ...rule, pattern: undefined } as ValidatorRule;
}
}
return rule as ValidatorRule;
});
return newRules;
}
复制代码
成效(优点):
高复用,可读性好,可维护性高。提升开发效率,解放生产力。
- 高复用性: 相似的业务逻辑进行统一处理,复用在相似领域的业务场景。
- 可读性好: 减少了繁杂嵌套的 if-else,读取配置,逻辑更清晰
- 可维护性高: 实现配置代替开发,逻辑分支的增删只是 CONFIG 的增删。即使把配置抽离,交到非技术人员处,其根据协议一样能实现表单项的增删,完成业务。让复杂的表单变得清晰好维护
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END