3 分钟教你用阿里 Formily.js 快速做一个登录表单

19,106 阅读6分钟

⭐️⭐️ Demo 源码: github.com/ericlee33/m…

1. Formily 是什么?

Formily 是 alibaba 开源表单框架,抽象了表单领域模型的 MVVM 表单解决方案。

这里引用官网的介绍:

众所周知,表单场景一直都是前端中后台领域最复杂的场景,它的复杂度主要在哪里呢?

  • 字段数量多,如何让性能不随字段数量增加而变差?
  • 字段关联逻辑复杂,如何更简单的实现复杂的联动逻辑?字段与字段关联时,如何保证不影响表单性能?
  • 表单数据管理复杂
  • 表单状态管理复杂
  • 表单的场景化复用
  • 动态渲染诉求很强烈

image.png

这张图主要将 Formily 分为了内核层,UI 桥接层,扩展组件层,和配置应用层。

内核层是 UI 无关的,它保证了用户管理的逻辑和状态是不耦合任何一个框架,这样有几个好处:

  • 逻辑与 UI 框架解耦,未来做框架级别的迁移,业务代码无需大范围重构
  • 学习成本统一,如果用户使用了 @formily/react,以后业务迁移 @formily/vue,用户不需要重新学习

JSON Schema 独立存在,给 UI 桥接层消费,保证了协议驱动在不同 UI 框架下的绝对一致性,不需要重复实现协议解析逻辑。

扩展组件层,提供一系列表单场景化组件,保证用户开箱即用。无需花大量时间做二次开发。

2. 核心优势

  • 高性能
  • 开箱即用
  • 联动逻辑实现高效
  • 跨端能力,逻辑可跨框架,跨终端复用
  • 动态渲染能力

3. formily 能解决什么问题

  • 解决复杂场景,表单性能问题
  • 可以实现复杂的表单联动逻辑
  • 表单状态管理
  • 表单场景化复用
  • 后端也能搭建前端表单

4. 如何用 Formily 渲染一个登录表单

这里有一个场景,我们要实现如下效果:

image.png

正常来说需要用 Antd 等 UI 库写 Form 组件来实现,但是这里我们用 Formily 框架提供的能力来实现,接下来我们先了解一下 Formily 的渲染模式。

4.1 Formily 的渲染模式

Formily 支持 3 种渲染模式,JSX,Markup Schema 和 JSON Schema。我这里主要介绍后两种。

4.1.1 Markup Schema

Markup Schema 的使用方式,如下方代码所示,每个描述标签都代表一个 Schema 节点,与 JSON-Schema 等价,最终也会被编译为 JSON Schema。

<Form
  form={normalForm}
  layout="vertical"
  size="large"
  onAutoSubmit={console.log}
>
  <SchemaField>
    <SchemaField.String
      name="username"
      title="Username"
      required
      x-decorator="FormItem"
      x-component="Input"
      x-validator={{
        required: true,
      }}
      x-component-props={{
        prefix: "{{icon('UserOutlined')}}",
      }}
    />
    <SchemaField.String
      name="password"
      title="Password"
      required
      x-decorator="FormItem"
      x-component="Password"
      x-component-props={{
        prefix: "{{icon('LockOutlined')}}",
      }}
    />
  </SchemaField>

Markup schema 是一个对源码开发比较友好的 Schema 开发模式,同样是使用 SchemaField 组件。

因为用 JSON Schema 在 JSX 环境下很难得到最好的智能提示体验,而且也不方便维护,用标签的形式可维护性会更好,智能提示也很强。

4.1.2 JSON schema

Formily 提供了一套 DSL,可以通过 JSON 渲染表单结构。

下面是一个最简的 formily 定义的 JSON schema,以下 JSON 便可描述 form 的结构。

const normalSchema = {
  type: 'object',
  properties: {
    username: {
      type: 'array',
      title: 'Username',
      required: true,
      'x-decorator': 'FormItem',
      'x-component': 'Input',
      'x-component-props': {
        prefix: "{{icon('UserOutlined')}}",
      },
    },
    password: {
      type: 'string',
      title: 'Password',
      required: true,
      'x-decorator': 'FormItem',
      'x-component': 'Password',
      'x-component-props': {
        prefix: "{{icon('LockOutlined')}}",
      },
    },
  },
};

4.2 如何渲染 JSON Schema?

Formily 提供了 @formily/core@formily/react 包,供我们以 JSON Schema 的形式渲染表单。

另外 Formily 也提供了自定义表单组件的能力。

import React from 'react';
import { createForm } from '@formily/core';
import { Form, FormItem, Input, Password, Submit } from '@formily/antd';
import { createSchemaField } from '@formily/react';
import * as ICONS from '@ant-design/icons';

const normalForm = createForm({
  validateFirst: true,
});

const SchemaField = createSchemaField({
  components: {
    FormItem,
    Input,
    Password,
  },
  scope: {
    icon(name) {
      return React.createElement(ICONS[name]);
    },
  },
});

// render it
const Basic = () => {
  return (
    <Form
      form={normalForm}
      layout="vertical"
      size="large"
      onAutoSubmit={console.log}
    >
      <SchemaField schema={normalSchema} />
      <Submit block size="large">
        Login
      </Submit>
    </Form>
  )
}

优势:

  • 后端也可以通过接口下发 JSON schema 来渲染前端表单,对于一个不了解前端技术的后端同学,也能够写表单。
  • 同构的架构可以保证表单字段与服务端定义的一致,降低了前后端对齐接口协议的成本。

5. Formily Schema

属性描述类型字段模型映射
type类型SchemaTypesGeneralField
title标题React.ReactNodetitle
description描述React.ReactNodedescription
default默认值AnyinitialValue
readOnly是否只读BooleanreadOnly
writeOnly是否只写Booleaneditable
enum枚举SchemaEnumdataSource
const校验字段值是否与 const 的值相等Anyvalidator
multipleOf校验字段值是否可被 multipleOf 的值整除Numbervalidator
maximum校验最大值(大于)Numbervalidator
exclusiveMaximum校验最大值(大于等于Numbervalidator
minimum校验最小值(小于)Numbervalidator
exclusiveMinimum最小值(小于等于)Numbervalidator
maxLength校验最大长度Numbervalidator
minLength校验最小长度Numbervalidator
pattern正则校验规则RegExpStringvalidator
maxItems最大条目数Numbervalidator
minItems最小条目数Numbervalidator
uniqueItems是否校验重复Booleanvalidator
maxProperties最大属性数量Numbervalidator
minProperties最小属性数量Numbervalidator
required必填Booleanvalidator
format正则校验格式ValidatorFormatsvalidator
properties属性描述SchemaProperties-
items数组描述SchemaItems-
additionalItems额外数组元素描述Schema-
patternProperties动态匹配对象的某个属性的 SchemaSchemaProperties-
additionalProperties匹配对象额外属性的 SchemaSchema-
x-indexUI 展示顺序Number-
x-patternUI 交互模式FieldPatternTypespattern
x-displayUI 展示FieldDisplayTypesdisplay
x-validator字段校验器FieldValidatorvalidator
x-decorator字段 UI 包装器组件String | React.FCdecorator
x-decorator-props字段 UI 包装器组件属性Anydecorator
x-component字段 UI 组件String | React.FCcomponent
x-component-props字段 UI 组件属性Anycomponent
x-reactions字段联动协议SchemaReactionsreactions
x-content字段内容,用来传入某个组件的子节点React.ReactNodecontent
x-visible字段显示隐藏Booleanvisible
x-hidden字段 UI 隐藏(保留数据)Booleanhidden
x-disabled字段禁用Booleandisabled
x-editable字段可编辑Booleaneditable
x-read-only字段只读BooleanreadOnly
x-read-pretty字段阅读态BooleanreadPretty
definitionsSchema 预定义SchemaProperties-
$ref从 Schema 预定义中读取 Schema 并合并至当前 SchemaString-
x-data扩展属性Objectdata
x-compile-omitted忽略编译表达式的属性列表string[][]

更多细节,可以参考 Formily API 文档

6. 表单联动展示

登录场景我们实现的是一个比较简单的 JSON Schema,仅仅描述了 2 个表单项。

现在有一个新的场景:

我们想实现当 showInput 为 show 时,展示 Input 表单项,hide 时,隐藏 Input 表单项,如下图所示:

image.png

image.png

这是一个一对一联动的 case。

在 Formily 中,联动分为主动联动和被动联动:

  • 主动联动:只需要关注某个字段所依赖的字段即可,依赖字段变化了,被依赖的字段即可自动联动

  • 被动联动:必须要监听一个或多个字段的事件变化去控制另一个或者多个字段的状态

6.1 主动联动

const schema = {
  type: 'object',
  properties: {
    showInput: {
      type: 'string',
      title: 'showInput',
      enum: [
        { label: 'show', value: 'visible' },
        { label: 'hide', value: 'none' },
      ],
      default: 'visible',
      'x-decorator': 'FormItem',
      'x-component': 'Select',
      'x-reactions': {
        target: 'input',
        fulfill: {
          state: {
            display: '{{$self.value}}',
          },
        },
      },
    },
    input: {
      type: 'string',
      title: 'Input',
      required: true,
      'x-decorator': 'FormItem',
      'x-component': 'Input',
    },
  },
};

6.2 被动联动

被动联动怎么写呢,这里举一个例子,实际上只有 x-reactions 参数的区别。在被动联动的表单项中,定义 dependencies 为依赖项:

const schema = {
  type: 'object',
  properties: {
    showInput: {
      type: 'string',
      title: 'showInput',
      enum: [
        { label: 'show', value: 'visible' },
        { label: 'hide', value: 'none' },
      ],
      default: 'visible',
      'x-decorator': 'FormItem',
      'x-component': 'Select',
    },
    input: {
      type: 'string',
      title: 'Input',
      required: true,
      'x-decorator': 'FormItem',
      'x-component': 'Input',
      'x-reactions': {
        dependencies: ['showInput'],
        fulfill: {
          state: {
            display: '{{$deps[0]}}',
          },
        },
      },
    },
  },
};

一对多联动也是类似的,只是增加了一个表单项的区别。

另外,Formily 也支持链式联动,异步联动等联动方式。

7. 表单联动校验

如果我们想实现表单联动校验,要怎么做呢?

例如下图,我们想实现 AA 必须比 BB 大。

image.png

formily Schema 支持写 function 形式的 x-reactions,在参数中我们可以拿到当前 form 实例挂载的其他字段的值,在函数中可以直接做判断。

const schema = {
  type: 'object',
  properties: {
    aa: {
      title: 'AA',
      required: true,
      'x-reactions': `{{(field) => {
          field.selfErrors =
            field.query('bb').value() >= field.value ? 'AA must be greater than BB' : ''
      }}}`,
      'x-component': 'NumberPicker',
      'x-decorator': 'FormItem',
    },
    bb: {
      title: 'BB',
      required: true,
      'x-reactions': {
        dependencies: ['aa'],
        fulfill: {
          state: {
            selfErrors:
              "{{$deps[0] <= $self.value ? 'AA must be greater than BB' : ''}}",
          },
        },
      },
      'x-component': 'NumberPicker',
      'x-decorator': 'FormItem',
    },
  },
};

8. 模型 inspector 工具

Formily 官方也提供了了 inspector 工具,可以查看 formily 表单每一个 Field 的内置状态,让你更好进行开发调试。

image.png

9. 拖拽开发

Formily 同样支持可视化拖拽表单开发,可以参考文档

image.png

10. 总结

表单场景一直都是前端中后台领域最复杂的场景。

Formily 是面向复杂场景的表单解决方案,提供了表单联动方案,可跨端,性能高,即便是后端,也能搭建前端表单。