为什么?
背景
我们在开发CRUD中后台录入系统时,大部分页面都由列表(table)和表单(form)组成。这时候我们往往会编写很多重复的代码,包括表单的数据收集,字段校验,字段联动等等,代码可维护性极低。所以我们需要一个表单解决方案,让你真正做到只关心业务逻辑。
问题
1.性能问题
由于React单向数据流,根组件驱动渲染的特性,一个子组件的更新,就会导致整树重渲染。
使用shouldComponentUpdate或hooks的memo API来控制,需要额外的props diff判断,是浅比较还是深比较同时,粗暴使用的话,还可能在组件嵌套渲染的情况下出现问题。
2. 代码可维护性问题
如果一个表单页面有很多联动,我们将不得不在一个业务组件内写很多的onChange Handler,最后导致业务组件变得非常臃肿。
3. 表单研发效率问题
Antd Form或者Fusion Next Form,组件内部到处都是FormItem组件,到处都是onChange Handler 到处都是{...formItemLayout},这些重复而又低效的代码其实是不应该存在的。
4. 后端数据驱动问题
有一些场景,我们的表单页面是非常动态化的,某一些字段是否存在,前端完全感知不到,是由后端建表,由不同职业属性的用户手工录入的字段信息,这样就需要前端拥有动态化渲染表单的能力了,不管是Antd Form还是Fusion Next Form都没有原生就支持这样的动态化渲染的能力,你只能在上层在封装一层动态化渲染机制。
是什么?
Formily 是一个由阿里巴巴集团多 BU 共建的面向中后台复杂场景的表单解决方案,它也是一个表单框架。
核心特性
Formily 解决方案的本质是构造了一个 Observable Form Graph,在这个 Form Graph 中,我们抽象了整个表单领域模型,同时这个模型又是一个无限循环状态机。
这个状态机主要有 3 个特点:
- 无限循环
- 分布式管理状态
- UI 无关
在这样一个状态机下,我们就能很简单的来描述字段间的联动关系。我们甚至可以用一句极简表达式来描述:
setFieldState(
Subscribe(
FormLifeCycle,
Selector(Path)
),
TargetState
)
这句表达式描述了
- 任何联动,都需要一个路径来描述具体字段
- 通过一个选择器来选择字段,同时任何联动都是从表单生命周期而发起
- 联动的最终操作是操作具体字段的状态,可以是值,可以是它的显示隐藏,也可以是具体组件属性等等。
所以,Formily 借助这样一个内核,我们轻松的实现了:
- 在复杂联动场景下更加清晰简单的描述联动的方式
- 在超多表单项场景下可以获得更好的表单操作性能
- 在跨终端场景下实现通用表单解决方案
整体架构
整个 Formily 是由一个 UI 无关的内核所驱动的,可以轻松做到跨终端的,同时,在上层,我们拥有一份标准的表单协议,可以做表单动态渲染,一份 JSON Schema 驱动多端的表单页面动态渲染
对每刻的意义
1.自定义表单
每刻的自定义表单实现方式是根据后端配置的组件类型(type),动态渲染不同的前端组件,内部再实现字段赋值,校验,字段联动等。这样的实现方式有几个缺陷,
(1)前端有大量hardCode代码,判断当前是什么类型,决定渲染什么组件。
(2)逻辑不能复用,新老主板web、app,云彩,电子档案,同样的功能需要实现六次,效率十分低下。
(3)不易维护,每次变更或者添加自定义组件相关功能,都需要再次更改组件代码,无法做到真正的数据驱动,后端渲染。
解决方案:
formily提供了表单渲染功能,根据约定好的字段可自动生成相应表单。
const Custom = () => {
setup()
const [schemaConfig, setSchemaConfig] = useState({});
const [initValue, setInitValue] = useState({});
useEffect(() => {
getFormConfig().then(schemaConfig => {
setSchemaConfig(schemaConfig);
})
getInitialValues().then(initValue => {
setInitValue(initValue);
})
}, [])
return (
<SchemaForm
labelCol={5}
wrapperCol={14}
schema={schemaConfig}
initialValues={initValue}
onSubmit={(values)=>{
console.log(values)
}}
>
<FormButtonGroup offset={5}>
<Submit>提交</Submit>
</FormButtonGroup>
</SchemaForm>
)
}
export default Custom;
除去后端请求,20几行代码就可以实现表单的后端渲染,包含数据更改,校验,联动等功能。schemaConfig大致为以下方式,具体可查看官方文档
{"type":"object",
"properties":{
"input":{ //属性字段(对应sumbit对象属性)
"type":"string", //标识对应的组件类型
"title":"单行",
"name": "input", //字段所属的父节点属性名
"x-component-props": { //原组件(antD)的属性
"placeholder": "please Input"
}
},
"select": {
"key": "select",
"type": "string",
"title": "选择框",
"name": "select",
"enum": ["选项一", "选项二", "选项三"],
},
}
2.性能提升
原先每刻单纯使用Ant Form,或者自己实现的表单,都存在某一子组件更新,都会整树渲染的问题,这在数据量大的情况下是很影响性能的。
Ant Design Form
Formily
3.更多探索
其实formily核心特性是基于表单生命周期和分布式管理实现的字段联动。场景越复杂,越能体现他的价值。由于我们在字段联动方面场景比较简单,所以这块的应用还有待后续拓展。
怎么用?
核心特性已介绍,具体使用方式可参考官方文档