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 错误提示信息

其他属性比如 styleclassName 等均会传递到 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>

监听该 fieldonChange 事件,然后设置其他表单项的数据,从而达到联动效果。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)

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

Fieldruleseffects 不需要 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 上的能力较强,也封装了一些常用的布局,但功能能力较弱,用户实现复杂逻辑还是需要写很多代码。

Formikreact-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);