IceForm 表单组件

表单组件

参数(Props)

参数名 说明 必填 类型 默认值 备注
initialValues 表单初始值 N object {} -
onSubmit submit函数 Y function - -
onChange 表单变化回调 N function - function(values: object, item: object) => void
参数:
values: {object} 表单数据
item: {object} 详细
item.name: {string} 变化的组件名
item.value: {string} 变化的数据
rules 校验规则 N object {} -
effects 联动规则 N array [] -
layout 表单布局 N object -
renderField 自定义 Field 布局 N function - function({label, component, error}) => dom
参数:
label: {string/element} Field 的 label
component: {string/function} 待渲染的控件
error: {string/element} Field 错误提示信息

其他属性比如 style、className 等均会传递到 form 标签上。

layout 是个对象,包含 4 个属性:

{
  labelAlign: 'left',       // label 的位置,'left'、'top',默认 'left'
  labelTextAlign: 'right',  // label 文字对齐方式,'left'、'right',默认 'right'
  labelCol: 1,             // label 占的栅格宽度,共 12 等分,默认 2
  wrapperCol: 3,           // 控件占的栅格宽度,共 12 等分,默认 6
}

rules 是一个 Object,key 是 <Field> 的 name 属性值,value 是个数组,数组里面的每一项是一个校验规则,参考 async-validator。

<Form
  onSubmit={this.onSubmit}
  style={{color: '#ee7893'}}
  rules={{
    username: [{
      required: true,
      min: 5,
      message: '姓名至少5个字符'
    }],
    age:  [{
      required: true,
      message: '年龄必填'
    }]
  }}
>
  <Field label="姓名:" name="username" component="input" type="text" />
  <Field label="年龄:" name="age" component='input' type="number" />
</Form>

effects 是个数组,写法如下:

<Form
  onSubmit={this.onSubmit}
  effects={[
    {
      field: 'username',
      handler: formCore => {
        if (formCore.getFieldValue('username') === 'ice') {
          formCore.setFieldValue('age', 2)
        }
      }
    }
  ]}
>
  <div>Hello Form</div>
  <Field label="姓名:" name="username" component="input" type="text" />
  <Field label="年龄:" name="age" component='input' type="number" />
  <button type="submit">Submit</button>
</Form>

监听该 field 的 onChange 事件,然后设置其他表单项的数据,从而达到联动效果。handler 的参数是 formCore 对象,该对象暴露一些 api 可以设置 value、error、show/hide 等。

Field 组件

参数名 说明 必填 类型 默认值 备注
label 表单项的 label N string/element - -
name 表单项的 name Y string - -
component 表单类型,原生 html 标签或者三方组件 N string/function - 'input' 'textarea' Input Radio
value 表单项的值 N - '' -
rules 校验规则 N object or array - -
effects 联动规则 N object - -
visible 显示隐藏当前 Field N boolean true true/false
setValueFormatter 格式化控件渲染值 N function function(savedValue) => renderValue
getValueFormatter 格式化控件提交值 N function function(renderValue) => savedValue
layout 设置当前 Field 的布局 N object 同 Form layout 当前 Field 的 layout 会覆盖 Form 的 layout
tips 提示信息 N string
valueName 控件值的名称,比如,radio 的 valueName 为 'checked',value 为 true/false N string 比如 Fusion 的 Switch 组件
errorRender 自定义 error 渲染 N function(error) {}
onChange 自定义 onChange 函数 N function() {} 默认情况下已处理表单的 onChange(eventOrValue) 事件,如果接入的三方控件 onChange 的第一个参数不是 event 或者 value,可以主动设置对应的值。比如,接入控件的 onChange(xxx, value) 第二个参数才是 value,则可以手动设置 formCore.setValue(fieldname, value)

style、className 属性会传递到 Field 最外层 dom 上,其他属性会传递到 component 上,如果没有 component 但有 children,则属性传递到 children 上。

Field 的 rules 和 effects 不需要 name 作为 key 了,写法如下:

<Form onSubmit={this.onSubmit}>
  <Field label="姓名:" name="username" component="input" type="text" />
  <Field label="昵称:" name="nickname" component="input" type="text" effects={{
    handler: formCore => {
      if (formCore.getFieldValue('nickname') === 'snow') {
        formCore.setFieldProps('age', {
          visible: true,
        });
      } else {
        formCore.setFieldProps('age', {
          visible: false,
        });
      }
    }
  }} />
  <Field label="年龄:" name="age" component='input' type="number" rules={[{
    required: true,
    message: '年龄必填'
  }]} />
  <button type="submit">Submit</button>
</Form>

FieldArray 组件

FieldArray 表示渲染数组类型的数据,属性同 Field:

<Form
  onSubmit={this.onSubmit}
>
  <FieldArray label="新增顾客:" name="customers">
    <Field name="customer0" component={Input} placeholder="customer name" />
    <Field name="customer1" component={Input} placeholder="customer name" />
    <Field name="customer2" component={Input} placeholder="customer name" />
  </FieldArray>
  <Field label="日期:" name="date" component={DatePicker} />
  <Field label="">
    <Button htmlType="submit">Submit</Button>
  </Field>
</Form>

formCore API

formCore 会暴露一些 API,使用这些 API 可以获取、设置表单的数据、状态等。

也可以通过属性的方式获取到一些数据:

延伸阅读

开发 @ice/form 表单背景

对于前端,表单开发是一件特别繁琐的事情,尤其在中后台业务中,大家常常会被各种五花八门的表单折磨,又不得不面对现实地去寻找最佳方案,但最终都会发现过度设计的表单组件性能不好,使用简单的表单组件还是需要写大量的业务代码。经过长期的积累以及在社区的调研,我们开发了一个表单组件帮助大家快速地创建一个高性能表单。

组件特性

架构方案

如上图所示,整个表单的数据都放在 FormCore 这一层,同时 FormCore 会暴露一些 API,以便获取、设置、处理数据。Form、Field 组件通过 Sub/Pub 模式与 FormCore 通信,FormCore 通知组件何时重新渲染。表单提供了校验、联动以及结合 Fusion、Antd 三方组件库使用等能力。

竞品对比

NoForm

NoForm 是一个表单操作(比如说校验、提交、联动等)抽象到上层,下层又包装了 Next、Antd 等组件,UI 上的能力较强,也封装了一些常用的布局,但功能能力较弱,用户实现复杂逻辑还是需要写很多代码。

Formik、react-final-form

这两个组件有一些共性,都是通过 render props 的方式实现了复杂的状态管理,在性能上也非常地卓越,在社区得到了大量的好评,但在联动上的能力较弱(目前只能更新 value),而且如果要集成 Next 或者 Antd 需要将库的表单组件都封装成 Field,成本较高。

相关链接

DEMO 列表

基本用法

Form 的基本用法

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, Switch, Select, Checkbox } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const Option = Select.Option;

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  onChange(values, field) {
    console.log(values, field);
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          onChange={this.onChange}
          initialValues={{
            username: 'icer',
            age: 3,
            intro: '让前端开发简单而友好'
          }}
        >
          <h2>个人资料</h2>
          <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" />
          <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
          <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
          <Field label="开关:" name="open" component={Switch} valueName="checked" />
          <Field label="尺寸:" name="size" component={Select} value="medium" placeholder="请选择尺寸">
            <Option value="small" key="small">小</Option>
            <Option value="medium" key="medium">中</Option>
            <Option value="large" key="large">大</Option>
          </Field>
          <Field label="选项:" name="checkbox" value={['b']} component={Checkbox.Group}>
            <Checkbox value="a">选项一</Checkbox>
            <Checkbox value="b">选项二</Checkbox>
            <Checkbox disabled value="c">选项三(disabled)</Checkbox>
          </Field>
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

重置表单

Form 子组件使用 function 渲染时,可以调用 FormCore 中的 API

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    const initialValues = {
      username: 'icer',
      age: 3,
      intro: '让前端开发简单而友好'
    };

    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          initialValues={initialValues}
        >
          {formCore => (
            <div>
              <h2>个人资料</h2>
              <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" />
              <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
              <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
              <Field label="">
                <div>
                  <Button htmlType="submit" style={{marginRight: '20px'}}>Submit</Button>
                  <Button onClick={() => formCore.reset()}>Reset</Button>
                </div>
              </Field>
            </div>
          )}
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

基础校验

校验规则可以写在 Form 属性上或者 Field 属性上

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          rules={{
            username: [{
              required: true,
              min: 5,
              message: '姓名至少5个字符'
            }]
          }}
        >
          <h2>个人资料</h2>
          <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" placeholder="请输入名字"/>
          <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" rules={[{
            message: '年龄必填且大于18岁',
            required: true,
            validator: (rule, value) => value > 18
          }]} />
          <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

异步校验

异步校验,校验结果的 message 直接 callback 即可

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form onSubmit={this.onSubmit}>
          <h2>个人资料</h2>
          <Field name="name" label="名称:" component={Input} autoComplete="off" placeholder="请输入名字" rules={{
              async asyncValidator(rule, value, callback) {
                if (!value) {
                  callback('名称必填');
                } else {
                  await sleep(500);

                  if (~['john', 'paul', 'george', 'ringo'].indexOf(value)) {
                    callback('名称已存在');
                  } else {
                    callback(undefined)
                  }
                }
              }
            }} />
          <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" rules={[{
            message: '年龄必填且大于18岁',
            required: true,
            validator: (rule, value) => value > 18
          }]} />
          <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

基础联动

基础联动

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, Radio } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          rules={{
            username: [{
              required: true,
              min: 5,
              message: '姓名至少5个字符'
            }],
            age: [{
              required: true,
              message: '大于18岁',
              validator: (rule, value) => value > 18
            }]
          }}
          effects={[
            {
              field: 'username',
              handler: formCore => {
                const name = formCore.getFieldValue('username');
                if (name === 'Khaleesi') {
                  formCore.setFieldValue('age', 28)
                }
              }
            },
            {
              field: 'gender',
              handler: formCore => {
                const gender = formCore.getFieldValue('gender');
                if (gender === 'female') {
                  formCore.setFieldProps('age', {
                    visible: false,
                  });
                } else {
                  formCore.setFieldProps('age', {
                    visible: true,
                  });
                }
              }
            }
          ]}
        >
          <h2>个人资料</h2>
          <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" />
          <Field label="性别:" name="gender" component={Radio.Group}>
            <Radio value="male">男</Radio>
            <Radio value="female">女</Radio>
            <Radio value="x">X</Radio>
          </Field>
          <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
          <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

省市联动

省市联动

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Input, Button, Select } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const provinceData = ['Zhejiang', 'Hubei', 'Jiangsu'];
const cityData = {
    Zhejiang: ['Hangzhou', 'Ningbo', 'Wenzhou'],
    Hubei: ['Wuhan', 'Yichang', 'Jingzhou'],
    Jiangsu: ['Nanjing', 'Suzhou', 'Zhenjiang']
};

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          effects={[
            {
              field: 'province',
              handler: formCore => {
                const province = formCore.getFieldValue('province')
                formCore.setValues({
                  city: '',
                  population: 0
                })
                formCore.setFieldProps('city', {dataSource: cityData[province]})
              }
            },
            {
              field: 'city',
              handler: formCore => {
                const city = formCore.getFieldValue('city')
                if (city === 'Suzhou') {
                  formCore.setFieldProps('population', {disabled: true})
                  formCore.setFieldValue('population', 0)
                } else {
                  formCore.setFieldProps('population', {disabled: false})
                }
              }
            }
          ]}
        >
          <div style={{
            marginBottom: 20,
          }}>当城市为“苏州”,则 disabled 人口</div>
          <Field label="省:" layout={{wrapperCol: 2}} name="province" placeholder="Select Province"  dataSource={provinceData} component={Select} />
          <Field label="市:" layout={{wrapperCol: 2}} name="city" placeholder="Select City" dataSource={[]} component={Select} />
          <Field label="人口:" name="population" placeholder="The population of the city" component={Input} htmlType="number" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

复杂联动

同时监听两个 Field

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';


const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          effects={[
            {
              field: 'number1',
              handler: formCore => {
                const value1 = formCore.getFieldValue('number1');
                const value2 = formCore.getFieldValue('number2');
                if (Number(value1) < Number(value2)) {
                  formCore.setFieldError('number2', 'number1不能小于number2');
                } else {
                  formCore.setFieldError('number2', undefined);
                }
              }
            },
            {
              field: 'number2',
              handler: formCore => {
                const value1 = formCore.getFieldValue('number1');
                const value2 = formCore.getFieldValue('number2');
                if (Number(value1) < Number(value2)) {
                  formCore.setFieldError('number2', 'number1不能小于number2');
                } else {
                  formCore.setFieldError('number2', undefined);
                }
              }
            }
          ]}
        >
          <Field label="Number1:" name="number1" component={Input} htmlType="number" placeholder="number1 必须大于等于 number2" />
          <Field label="Number2:" name="number2" component={Input} htmlType="number" placeholder="number2 必须小于等于 number1" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

自定义 Field 布局

通过 Form 的 renderField 自定义 Field 布局

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          renderField={({label, component, error}) => (
            <div style={{marginBottom: '10px'}}>
              <div>{label}</div>
              <span>{component}</span>
              <span style={{color: '#ee7893'}}>{error}</span>
            </div>
          )}
          rules={{
            username: [{
              required: true,
              min: 5,
              message: '姓名至少5个字符'
            }]
          }}
        >
          <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" />
          <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
          <Button htmlType="submit">Submit</Button>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

格式化 Field 值

Format Feild's value

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, DatePicker, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form onSubmit={this.onSubmit}>
          <h4>姓名显示、存储均为小写,日期存储格式为时间戳</h4>
          <Field name="name" label="姓名:" component={Input} placeholder="请输入姓名" setValueFormatter={
            value => value && value.toLowerCase()
          } getValueFormatter={
            value => value && value.toLowerCase()
          } />
          <Field label="生日:" name="date" component={DatePicker} getValueFormatter={
            value => Date.parse(value)
          } />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

复用 Field 组

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const Address = ({ name, label }) => (
  <React.Fragment>
    <div>
      <Field
        name={`${name}.street`}
        component={Input}
        label={`${label} Street:`}
        placeholder={`${label} Street`}
      />
    </div>
    <div>
      <Field
        name={`${name}.city`}
        label={`${label} City:`}
        component={Input}
        placeholder={`${label} City`}
      />
    </div>
    <div>
      <Field
        name={`${name}.postalCode`}
        label={`${label} Postal Code:`}
        component={Input}
        placeholder={`${label} Postal Code`}
      />
    </div>
  </React.Fragment>
)

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form onSubmit={this.onSubmit}>
          <h1>可复用的 Field Group</h1>
          <Address name="billing" label="Billing" />
          <Address name="shipping" label="Shipping" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

异步加载数据渲染

异步加载数据进行首屏渲染

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const load = async () => {
  await sleep(2000);
  return {
    name: 'erikras',
    age: 20,
    desc: '我是 erikras'
  };
}

class App extends Component {
  state = { data: {} }

  async componentDidMount() {
    this.setState({ loading: true });
    const data = await load();
    this.setState({ loading: false, data });
  }

  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    const Age = (<span style={{color: 'green'}}>年龄:</span>);
    return (
      <div>
        <h2>加载表单数据并且初始化(label支持自定义jsx)</h2>
        <Form
          initialValues={this.state.data}
          onSubmit={this.onSubmit}
          effects={[{
            field: 'name',
            handler: formCore => {
              const name = formCore.getFieldValue('name');
              if (name === 'erikras') {
                formCore.setFieldValue('age', 28)
              }
            }
          }]}
        >
          {this.state.loading && <div className="loading">loading...</div>}
          <Field name="name" label={<span style={{color: 'red'}}>名称:</span>} component={Input} placeholder="请输入名字" />
          <Field name="age" label={Age} component={Input} placeholder="请输入年龄" />
          <Field name="desc" label="简介:" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

Dialog 表单

Form 结合对话框

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Input, Button, Dialog, Switch } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  handleSubmit = null;

  state = {
    visible: false
  };

  onOpen = () => {
    this.setState({
      visible: true
    });
  };

  onClose = reason => {
    this.setState({
      visible: false
    });
  };

  onOk = (e) => {
    this.handleSubmit(e);
  };

  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Button onClick={this.onOpen} type="primary">
          Open dialog
        </Button>
        <Dialog
          title="表单 Form 对话框"
          visible={this.state.visible}
          onOk={this.onOk.bind(this)}
          onCancel={this.onClose.bind(this, 'cancelClick')}
          onClose={this.onClose}
          style={{
            width: 600,
          }}
        >
          <Form 
            onSubmit={this.onSubmit}
            layout={{
              labelCol: 4,
              wrapperCol: 8
            }}
          >
            {formCore => {
              this.handleSubmit = formCore.submit.bind(formCore);
              return (
                <div>
                  <Field name="name" label="名称:" component={Input} placeholder="请输入名字" />
                  <Field name="age" label="年龄:" component={Input} placeholder="请输入年龄" />
                  <Field name="desc" label="简介:" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
                  <Field name="open" label="是否打开:" component={Switch} valueName="checked" value={true} />
                  <Field name="openDesc" label="打开时的描述:" component={Input} placeholder="description when opening"/>
                  <Field name="closeDesc" label="关闭时的描述:" component={Input} placeholder="description when closing" />
                </div>
              )
            }}
          </Form>
        </Dialog>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

结合 Antd 组件

使用 Antd 组件

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, Select, Switch, Checkbox, Radio } from 'antd';
import 'antd/dist/antd.css';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
        >
          {formCore => (
            <div>
              <h2>Antd 组件</h2>
              <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" />
              <Field label="年龄:" name="age" component={Input} type="number" placeholder="请输入年龄" />
              <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
              <Field label="性别:" name="gender" rules={[{
                required: true,
                message: '性别必选'
              }]} component={Radio.Group}>
                <Radio id="apple" value="male">Male</Radio>
                <Radio id="watermelon" value="female">Female</Radio>
                <Radio id="orange" value="x">X</Radio>
              </Field>
              <Field label="前端框架:" name="framework" value={[]} component={Checkbox.Group}>
                <Checkbox value="react">React</Checkbox>
                <Checkbox value="vue">Vue</Checkbox>
                <Checkbox value="angular">Angular</Checkbox>
              </Field>
              <Field label="加班:" name="switch" component={Switch} valueName='checked' value={true} checkedChildren="on" unCheckedChildren="off" />
              <Field label="职级:" name="grade" component={Select}>
                <Select.Option value="p6">P6</Select.Option>
                <Select.Option value="p7">P7</Select.Option>
                <Select.Option value="p8" disabled>P8</Select.Option>
              </Field>
              <Field label="">
                <Button htmlType="submit">Submit</Button>
              </Field>
            </div>
          )}
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

原生 html 标签

使用原生 html 标签

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          rules={{
            username: [{
              required: true,
              min: 5,
              message: '姓名至少5个字符'
            }]
          }}
        >
          <h2>个人资料</h2>
          <Field label="姓名:" name="username" component="input" placeholder="请输入名字" />
          <Field label="年龄:" name="age" component="input" type="number" placeholder="请输入年龄" />
          <Field label="简介:" name="intro" component="textarea" placeholder="请简单介绍一下自己的工作经历" />
          <Field label="框架:" name="framework" component="select" value="react">
            <option value="vue">Vue</option> 
            <option value="react">React</option>
            <option value="angular">Angular</option>
          </Field>
          <Field label="ICE背景:" name="ice" component="input" valueName='checked' value={false} type="radio" />
          <Field label="">
            <button type="submit">Submit</button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

异步更新 Form props

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  state = {
    renderField: undefined,
    data: {},
    rules: {
      username: [{
        required: true,
        min: 5,
        message: '姓名至少5个字符'
      }]
    }
  }

  async componentDidMount() {
    await sleep(1000);
    this.setState({
      renderField: ({label, component, error}) => (
        <div style={{marginBottom: '15px'}}>
          <div>{label}</div>
          <span>{component}</span>
          <span style={{marginLeft: '10px', color: '#ee7893'}}>{error}</span>
        </div>
      ),
      data: {
        username: 'erikras',
        age: 20,
        desc: '我是 erikras'
      },
      rules: {
        username: [{
          required: true,
          min: 10,
          message: '姓名至少10个字符'
        }]
      }
    })
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          initialValues={this.state.data}
          renderField={this.state.renderField}
          rules={this.state.rules}
        >
          <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" />
          <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
          <Field label="简介: " name="desc" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

表单布局

表单布局

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    const layout = {
      labelCol: 2,
      wrapperCol: 4,
      labelTextAlign: 'left',
      labelAlign: 'top'
    };
    return (
      <div>
        <Form
          layout = {layout}
          onSubmit={this.onSubmit}
          rules={{
            username: [{
              required: true,
              min: 5,
              message: '姓名至少5个字符'
            }]
          }}
        >
          <h2>个人资料</h2>
          <Field label="姓名:" name="username" component={Input} placeholder="请输入名字" errorRender={ 
            error => <span style={{color: '#ff7000'}}>{error}</span>
          } />
          <Field label="年龄:" name="age" component={Input} tips="身份证上的年龄" placeholder="请输入年龄" />
          <Field label="简介:" name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历" tips="介绍自己的经历介绍自己的经历介绍自己的经历介绍自己的经历" />
          <Field label="" name="submit">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

标签布局

标签布局,当前支持 labelTextAlign 和 labelAlign

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';

class App extends Component {
  render() {
    return (
      <div>
        <Form
          layout = {{
            labelTextAlign: 'left'
          }}
        >
          <h2>标签靠左(labelTextAlign: left)</h2>
          <Field label="name: " name="name" component={Input} placeholder="please input name" />
          <Field label="age: " name="age" component={Input} placeholder="please input age" />
          <Field label="intro: " name="intro" component={Input} placeholder="please input intro" />
        </Form>
        <Form
          layout = {{
            labelTextAlign: 'right'
          }}
        >
          <h2>标签靠右(labelTextAlign: right)</h2>
          <Field label="name: " name="name" component={Input} placeholder="please input name" />
          <Field label="age: " name="age" component={Input} placeholder="please input age" />
          <Field label="intro: " name="intro" component={Input} placeholder="please input intro" />
        </Form>
        <Form
          layout = {{
            labelAlign: 'top',
            labelTextAlign: 'left'
          }}
        >
          <h2>标签在上面(labelAlign: top)</h2>
          <Field label="name: " name="name" component={Input} placeholder="please input name" />
          <Field label="age: " name="age" component={Input} placeholder="please input age" />
          <Field label="intro: " name="intro" component={Input} placeholder="please input intro" />
        </Form>
        <Form
          layout = {{
            labelAlign: 'left'
          }}
        >
          <h2>标签在左边(labelAlign: left)</h2>
          <Field label="name: " name="name" component={Input} placeholder="please input name" />
          <Field label="age: " name="age" component={Input} placeholder="please input age" />
          <Field label="intro: " name="intro" component={Input} placeholder="please input intro" />
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

自定义标签

Form 自定义标签,给每个标签增加必填符号

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, Switch, Select, Checkbox } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const Option = Select.Option;

function renderLabel(name) {
  return (
    <span>
      <span style={{
        color: 'red',
      }}>*</span>
      {name}
    </span>
  );
}

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          rules={{
            username: [{
              required: true,
              min: 5,
              message: '姓名至少5个字符'
            }],
            age: [{
              required: true,
              message: '年纪必填'
            }],
            intro: [{
              required: true,
              message: '简介必填'
            }],
          }}
        >
          <h2>个人资料</h2>
          <Field label={renderLabel('姓名:')} name="username" component={Input} placeholder="请输入名字" />
          <Field label={renderLabel('年龄:')} name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
          <Field label={renderLabel('简介:')} name="intro" component={Input.TextArea} placeholder="请简单介绍一下自己的工作经历"/>
          <Field label="开关:" name="open" component={Switch} />
          <Field label="尺寸:" name="size" component={Select} placeholder="请选择尺寸">
            <Option value="small" key="small">小</Option>
            <Option value="medium" key="medium">中</Option>
            <Option value="large" key="large">大</Option>
          </Field>
          <Field label="选项:" name="checkbox" component={Checkbox.Group}>
            <Checkbox value="a">选项一</Checkbox>
            <Checkbox value="b">选项二</Checkbox>
            <Checkbox disabled value="c">选项三(disabled)</Checkbox>
          </Field>
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

大表单

输入框后面的数字代表每个 Field 渲染的次数。 当在某个 Field 输入内容时,可以看到 form 组件,只有当前的 Field 会重新渲染,而 formBinder 则是所有的 Field 都会重新渲染。form 渲染性能优于 formBinder。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input } from '@alifd/next';
import { FormBinderWrapper, FormBinder } from '@icedesign/form-binder';

const renderCountStyles = {
  position: 'absolute',
  top: 0,
  right: 0,
  fontStyle: 'normal',
  textAlign: 'center',
  height: '26px',
  width: '26px',
  lineHeight: '26px',
  borderRadius: '13px',
  border: '1px solid #ddd',
  background: '#eee'
};

class RenderCount extends Component {
  renders = 0

  render() {
    return <span style={renderCountStyles}>{++this.renders}</span>
  }
}

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

let dataSource = [];
for (let i = 0; i < 150; i++) {
  const item = {
    label: `Label${i}`,
    name: `name${i}`,
    placeholder: `placeholder ${i}`
  };

  dataSource.push(item);
}

class App extends Component {
  state = {
    value: {
      email: '',
      name: '',
      password: '',
    },
    isFormBinder: false
  };

  toggle = () => {
    this.setState({
      isFormBinder: !this.state.isFormBinder
    })
  }

  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <h3>表单性能测试 form vs formBinder</h3>
        <Button onClick={this.toggle} style={{marginBottom: '10px'}}>Toggle Form</Button>
        {!this.state.isFormBinder && (
          <Form
            onSubmit={this.onSubmit}
            renderField={({label, component, error}) => (
              <div style={{marginBottom: '10px'}}>
                <span style={{display: 'inline-block', width: '80px'}}>{label}</span>
                <div style={{position: 'relative', display: 'inline-block'}}>
                  <span>{component}</span>
                  <RenderCount />
                </div>
                <RenderCount />
                <span style={{color: '#ee7893'}}>{error}</span>
              </div>
            )}
          >
            {
              dataSource.map(field => <Field key={field.name} name={field.name} placeholder={`new form ${field.placeholder}`} label={field.label} component={Input} /> )
            }
            <Button htmlType="submit">Submit</Button>
          </Form>
        )}

        {
          this.state.isFormBinder && (
            <div>
              <FormBinderWrapper
                value={this.state.value}
                ref="form"
              >
                {
                  dataSource.map(field => (
                    <div key={field.name} style={{marginBottom: '10px'}}>
                      <span style={{display: 'inline-block', width: '80px'}}>{field.label}:</span>
                      <FormBinder name={field.name} require >
                        <div style={{position: 'relative', display: 'inline'}}>
                          <RenderCount />
                          <Input placeholder={`formBinder ${field.placeholder}`} />
                        </div>
                      </FormBinder>
                    </div>
                  ))
                }
              </FormBinderWrapper>
            </div>
          )
        }
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);

一行两个 Field

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, Switch, Select, Checkbox } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const Option = Select.Option;

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          className="ice-inline-form"
        >
          <h2>个人资料</h2>
          <div className="field-wrapper">
            <Field label="姓名:" name="username" component={Input} placeholder="请输入姓名" />
            <Field label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
            <Field label="地址:" name="address" component={Input} placeholder="请输入地址" />
            <Field label="手机:" name="tel" component={Input} htmlType="number" placeholder="请输入手机号码" />
            <Field label="邮件:" name="email" component={Input} placeholder="请输入邮件地址" />
          </div>
          
          <Button className="btn" htmlType="submit">Submit</Button>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);
.ice-inline-form .field-wrapper {
  display: flex;
  flex-wrap: wrap;
}
.ice-inline-form .ice-field {
  width: 50%;
  height: 40px;
}
.ice-inline-form .field-wrapper {
  overflow: hidden;
  background-color: #ddd;
  padding-top: 15px;
  padding-bottom: 15px;
}
.ice-inline-form .btn {
  margin-top: 20px;
}

第一行两个 Field

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, Switch, Select, Checkbox } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const Option = Select.Option;

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
          layout={{
            labelTextAlign: 'left'
          }}
          className="ice-first-inline-form"
        >
          <h2>个人资料</h2>
          <div style={{display: 'flex'}}>
            <Field style={{flexBasis: '35%'}} label="姓名:" name="username" component={Input} placeholder="请输入姓名" />
            <Field style={{flexBasis: '30%'}} label="年龄:" name="age" component={Input} htmlType="number" placeholder="请输入年龄" />
          </div>
          <Field label="地址:" name="address" component={Input} placeholder="请输入地址" />
          <Field label="手机:" name="tel" component={Input} htmlType="number" placeholder="请输入手机号码" />
          <Field label="邮件:" name="email" component={Input} placeholder="请输入邮件地址" />
          <Button style={{marginLeft: '60px', marginTop: '15px'}} className="btn" htmlType="submit">Submit</Button>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);
.ice-first-inline-form .ice-field .ice-field-label {
  display: inline;
  width: auto ;
}

FieldArray 组件

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, FieldArray, Field } from '@ice/form';
import { Button, Input, DatePicker } from '@alifd/next';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
        >
          <h2>新增顾客名单</h2>
          <FieldArray label="新增顾客:" name="customers">
            <Field className="field-array-item" name="customer0" component={Input} placeholder="请输入顾客的名字" />
            <Field className="field-array-item" name="customer1" component={Input} placeholder="请输入顾客的名字" />
            <Field className="field-array-item" name="customer2" component={Input} placeholder="请输入顾客的名字" />
          </FieldArray>
          <Field label="日期:" name="date" component={DatePicker} />
          <Field label="">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);
.field-array-item [class*='ice-col-'] {
  padding-top: 0;
  padding-left: 0;
}

Fusion 表单组件

Fusion 表单组件

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Form, Field } from '@ice/form';
import { Button, Input, NumberPicker, Switch, Range, Select, DatePicker, TimePicker, Checkbox, Radio } from '@alifd/next';

const Option = Select.Option;

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

class App extends Component {
  async onSubmit(values) {
    await sleep(300)
    window.alert(JSON.stringify(values, 0, 2))
  }

  render() {
    return (
      <div>
        <Form
          onSubmit={this.onSubmit}
        >
          <Field label="Password:" name="password" htmlType="password" component={Input} placeholder="请输入密码" />
          <Field label="NumberPicker:" name="numberPicker" defaultValue={3} component={NumberPicker} />
          <Field label="Switch:" name="switch" component={Switch} valueName="checked" value={false} />
          <Field label="Range:" name="range" defaultValue={30} scales={[0, 100]} marks={[0, 100]} component={Range} />
          <Field label="Select:" name="select" component={Select} defaultValue='lucy'>
            <Option value="jack">jack</Option>
            <Option value="lucy">lucy</Option>
            <Option value="disabled" disabled>disabled</Option>
            <Option value="hugohua">hugohua</Option>
          </Field>
          <Field label="DatePicker:" name="date" component={DatePicker} />
          <Field label="TimePicker:" name="time" component={TimePicker} />
          <Field label="Checkbox:" name="checkbox" defaultValue={[]} component={Checkbox.Group}>
            <Checkbox value="a">option 1 </Checkbox>
            <Checkbox value="b">option 2 </Checkbox>
            <Checkbox disabled value="c">option 3(disabled)</Checkbox>
          </Field>
          <Field label="Radio:" name="radio" component={Radio.Group}>
            <Radio value="apple">apple</Radio>
            <Radio value="banana">banana</Radio>
            <Radio disabled value="cherry">cherry(disabled)</Radio>
          </Field>
          <Field label="" name="submit">
            <Button htmlType="submit">Submit</Button>
          </Field>
        </Form>
      </div>
    );
  }
}

ReactDOM.render((
  <App />
), mountNode);