formily

465 阅读5分钟

为什么?

背景

我们在开发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核心特性是基于表单生命周期和分布式管理实现的字段联动。场景越复杂,越能体现他的价值。由于我们在字段联动方面场景比较简单,所以这块的应用还有待后续拓展。

怎么用?

核心特性已介绍,具体使用方式可参考官方文档

参考文档

官方地址

github

知乎专栏