Form使用总结

3,289 阅读4分钟

背景

中后台领域中,数据录入是一个重要的场景,在该场景中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 (
            <form>
                <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 (
      <div>
        <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>
      </div>
    );
  }
}

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的架构如下:

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可以方便的处理多种场景,如联动等。