中后台领域中,数据录入是一个重要的场景,在该场景中form(表单)扮演一个重要的角色。表单中涉及到大量的交互,主要表现在以下几个方面:
收集用户输入(input, textarea等)
事件处理(onChange等)
联动(数据同步,异步)
数据校验,提交
我们从一个简单登录页的例子说起,来总结form是如何使用的。
react原生实现
我们直接使用react的受控组件模式,对每个输入项的状态保存在组件state中,通过onChange事件,实时更新值。
import React from 'react'
class FormDemo extends React.Component {
state = {
username: '',
password: '',
usernameMsg: '',
passwordMsg: '',
onChangeName = e => {
const value = e.target.value
this.setState({
username: value,
usernameMsg: !value ? '请填写' : ‘’, // 非空校验
onChangePassword = e => {
const value = e.target.value
this.setState({
password: value,
passwordMsg: !value ? '请填写' : ‘', // 非空校验
handleSubmit = () => {
// post data
render() {
// 获取数据和错误信息
const { username, password, usernameMsg, passwordMsg } = this.state
return (
<input value={username} onChange={this.onChangeName} />
<span>{usernameMsg}</span>
<input value={password} onChange={this.onChangePassword} />
<span>{passwordMsg}</span>
<button type="submit" onClick={this.handleSubmit}>提交</button>
</form>
复制代码
这种方法百分之百使用原生的方式实现:表单的每一个field都对应于组件内的state的一个值,每一个field有一个对应的错误信息用于展示错误,每个field的值通过onChange事件进行改变。这种方式实现简单明了,但是当field较多时,需要大量的重复这种
value + onChange
的模式。所以,需要一种方式,将重复的工作抽象出来。
rc-form,antd实现
rc-form内部使用fieldsStore对所有field进行集中式管理,当数据改变时,重绘整个form。
import { createForm } from 'rc-form';
class Form extends React.Component {
submit = () => {
this.props.form.validateFields((error, value) => {
console.log(error, value);
render() {
let usernameErrors, passwordErrors;
const { getFieldProps, getFieldError } = this.props.form;
return (
<input {...getFieldProps('username', {rules: [{required: true}]})}/>
{(usernameErrors = getFieldError('password')) ? errors.join(',') : null}
<input {...getFieldProps('password', {rules: [{required: true}]})}/>
{(passwordErrors = getFieldError('password')) ? errors.join(',') : null}
<button onClick={this.submit}>submit</button>
export createForm()(Form);
复制代码
createForm()
使用高阶组件的方式,对form注入了一些额外的方法与属性。
getFieldProps
方法用于把field注册到fieldsStore里面,fieldsStore对field的变化进行追踪。可以看出相比第一种方式,rc-form把状态与UI进行分层,我们省去了对每一个field的状态管理,不用再去为每一个field进行状态进行
value + onChange
的重复。
在以上过程中,我们完成了登录的功能逻辑,但是还缺少表单的样式。表单样式包括两部分:表单的整体样式和每个field的样式。于是我们抽象出Form和FormItem用于承载样式,于是就有了antdForm。antd form就是在rc-form的基础上增加了form布局演化而来。我们也可以通过自定义Form和FormItem + rc-form的形式去实现form的布局。
代码如下:
<Form onSubmit={(values) => { submit(values) }}>
<Form.Item label="姓名">
{getFieldDecorator('name')(<Input />)}
</Form.Item>
<Form.Item label="密码">
{getFieldDecorator('password')(<Input.Password />)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">提交</Button>
</Form.Item>
</Form>
复制代码
antd-form的架构如下:
uform使用
在使用antd-form过程中,遇到以下问题:
antd-form采用单向数据流的方式管理状态,任何字段变动都会导致fieldsStore的改变进而导致组件的全量渲染,出现性能问题
在实现联动时,联动逻辑分散在各个表单组件的onChange方法中,通过this.props.form.setFieldsValue来处理联动。如果出现很多联动,不得不写很多onChange,导致业务组件变得非常臃肿且分散
antdForm中到处都是FormItem组件,到处都是onChange,到处都是{…formItemLayout},重复且低效,导致研发效率低下
uform很好的解决了使用form过程中遇到的各种问题,以uform 0.4.x版本为例(1.x版本发生了很大的变化)。
UForm 主要分为三层结构:
@uform/core 层,负责表单内部的数据状态管理,校验管理,副作用逻辑管理
@uform/react 层,负责在 React 中集成 UForm,帮助用户快速接入各种 React 组件库
组件库层,属于 @uform/react 的插件包,可以接入各种组件库,比如:Ant Design/Fusion Design
uform采用分布式状态管理,数据同步靠根组件广播需要更新子组件重绘,根组件只负责消息分发。这样可以做到只更新单个组件。uform支持json schema和集中性的副作用管理。
使用uform代码如下:
const Schema = {
user: {
type: 'object',
properties: {
username: {
type: 'string',
title: '用户名',
required: true,
password: {
type: 'string',
title: '密码',
required: true,
const defaultValue = {
user: {
username: '',
password: ''
<SchemaForm
labelCol={8}
wrapperCol={16}
schema={Schema}
defaultValue={defaultValue}
onSubmit={this.onSubmit}
</SchemaForm>
复制代码
使用uform,我们可以先定义form的schema和默认值,之后交给uform进行渲染。
uform可以方便的处理多种场景,如联动等。