FormBinder ICE 表单粘合剂

ICE 表单数据获取方案。

说明:

  1. 如果使用的是 FormBinder 0.x 的版本,请移步到 0.x 参考文档
  2. 如果使用的是 FormBinder 1.x 的版本,需要确保依赖的 react 版本在 16.2.0 以上,1.x 版本使用了 React.Fragments API,支持 FormBinderWrapper 组件返回多个节点。

安装和升级

npm install @icedesign/form-binder

表单功能

表单元素

表单元素指的是 ICE 基础组件以及业务组件中的 InputCheckboxSelectRangeDatePickerTimePickerNumberPickerSwitchUpload 等以及用户自定义的组件,它能够响应 onChange 等用来获取用户输入

参数(Props)

方法名 说明 类型 默认值
validateFields 校验并获取一组输入域的值与 Error,若 fieldNames 参数为空,则校验全部组件 ([fieldNames: string[]],callback(errors,values)) => void

FormBinderWrapper

整个表单的容器,支持传入 value 和 onChange 等属性,其中 value 会作为整个表单数据根节点,交由下层组件去获取、更新操作

经过 FormBinderWrapper 包装的组件,数据传递和同步将被接管,这意味着:

  1. 你不需要使用表单元素的 onChange 来做同步,但还是可以继续监听 onChange 等事件
  2. 你不能使用组件的表单元素的 valuedefaultValue 等属性来设置表单元素的值,但可以通过初始的 value 进行设置
  3. 你不需要通过 setState 来动态更新表单的值,因为表单默认支持双向数据通信,但可以通过 setFieldValue 和 getFieldValue 来设置或者更新表单域的值

参数(Props)

属性参数 说明 类型 默认值
value 表单值 object {}
onChange 任一表单域的值发生改变时的回调 function(value) () => {}
enableScrollErrorField 全局校验时,是否开启滚动到报错表单位置 boolean false
scrollErrorFieldTopOffset 全局校验滚动到报错位置时,距离顶部的偏移值(适用于头部 fixed 的场景) number 0

FormBinder

表单组件粘合剂,将其作为 FormBinderWrapper 的子组件,即可实现双向绑定特性,之后表单域的改变会通过 FormBinder 转发从而响应到 FormBinderWrapper 的 onChange 方法进行通信

FormBinder 支持的属性包含以下两部分:

自定义规则

参数(Props)

属性参数 说明 类型 默认值
rules 校验规则,参考下方文档 object[]
name​ 表单域名称 string
setFieldValue 设置一个输入控件的值 Function(fieldName: string)
getFieldValue 获取一个输入控件的值 Function(fieldName: string)
triggerType 指定合适的触发事件 string 'onChange'

校验规则

参数(Props)

参数 说明 类型 默认值
enum 枚举类型 string
len 字段长度 number
max 最大长度 number
message 校验文案 string
min 最小长度 number
pattern 正则表达式校验 RegExp
required 是否必选 boolean false
transform 校验前转换字段值 function(value) => transformedValue:any
type 内建校验类型 string 'string'
validator 自定义校验 function(rule, value, callback)
whitespace 必选时,空格是否会被视为错误 boolean false

内建校验类型,可选项 自定义校验(注意,callback 必须被调用

推荐:

  1. 建议统一使用 async-validator 的校验规则,尽量不要使用表单元素的相关检验属性,这样做有利于代码的可维护性和优雅。
  2. 内建校验类型,可选项
  3. 更多高级用法可参考 async-validator

FormError

自定义表单的报错信息,自定义报错信息时需要指定 name,以此来获取当前报错的表单域来源

参数(Props)

参数 说明 类型 默认值
name​ 表单域名称 string
style 自定义样式对象 object
className 自定义样式类名 string
render 自定义渲染报错的组件和处理逻辑 Function(errors):ReactNode

双向绑定协议

双向绑定协议指的是组件接收 valueonChange 两个参数,其用户输入值由 value 提供,当用户操作组件导致数据变更,组件会调用 onChange 并把新的 value 作为第一个参数传出。React 社区的大多数组件都遵守这个设计,如 @alifd/nextInput, Select, Checkbox 等,如果你希望你的表单类组件能够接入 FormBinder ,请务必遵守这个协议。

DEMO 列表

登录表单

普通的登录表单,可以自由组合布局,自定义排列标签和表单域

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

class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: {
        username: '',
        password: '',
        checkbox: false
      }
    };
  }

  formChange = value => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value });
  };

  validateFields = () => {
    const { validateFields } = this.refs.form;

    validateFields((errors, values) => {
      console.log({ errors })

      if (!errors) {
        Message.success('登录成功')
      }
    });
  }

  render() {
    return (
      <div style={styles.container}>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
          ref="form"
        >
          <div style={styles.content}>
            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>名称:</span>
              <FormBinder name="username" required message="请输入正确的名称" >
                <Input />
              </FormBinder>
              <FormError style={styles.formItemError} name="username" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>密码:</span>
              <FormBinder name="password" required message="请输入正确的密码">
                <Input htmlType="password" />
              </FormBinder>
              <FormError style={styles.formItemError} name="password" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>记住密码:</span>
              <FormBinder
                name="checkbox"
              >
                <Checkbox />
              </FormBinder>
            </div>

            <Button type="primary" style={{width: '242px'}}  onClick={this.validateFields}>
              登 录
            </Button>
          </div>
        </FormBinderWrapper>

        <div style={styles.preview}>
          <strong>当前表单数据</strong>
          <pre>{JSON.stringify(this.state.value, null, 2)}</pre>
        </div>

      </div>
    );
  }
}

const styles = {
  formItem: {
    marginBottom: '20px'
  },
  formItemLabel: {

  },
  formItemError: {
    marginLeft: '10px',
  },
  preview: {
    border: '1px solid #eee',
    marginTop: 20,
    padding: 10
  }
}

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

注册表单

普通的注册表单,展示多表单元素的组合,用户填写必须的信息才能注册新用户。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { FormBinderWrapper, FormBinder, FormError } from '@icedesign/form-binder';
import { Input, Button, Checkbox, Message, CascaderSelect, Switch, DatePicker } from '@alifd/next';

const cityData = [
  {
    value: "2973",
    label: "陕西",
    children: [
      {
        value: "2974",
        label: "西安",
        children: [
          { value: "2975", label: "西安市" },
          { value: "2976", label: "高陵县" }
        ]
      },
      {
        value: "2980",
        label: "铜川",
        children: [
          { value: "2981", label: "铜川市" },
          { value: "2982", label: "宜君县" }
        ]
      }
    ]
  },
  {
    value: "3371",
    label: "新疆",
    children: [
      {
        value: "3430",
        label: "巴音郭楞蒙古自治州",
        children: [
          { value: "3431", label: "库尔勒市" },
          { value: "3432", label: "和静县" }
        ]
      }
    ]
  }
];

class Register extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: {
        email: '',
        password: '',
        confirmPassword: '',
        nickname: '',
        birthdate: '',
        city: '',
        notification: false,
        agreement: false,
      }
    };
  }

  formChange = value => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value });
  };

  checkPassword = (rule, values, callback) => {
    if (!values) {
      callback('请输入正确的密码');
    } else if (values.length < 8) {
      callback('密码必须大于8位');
    } else if (values.length > 16) {
      callback('密码必须小于16位');
    } else {
      callback();
    }
  };

  checkConfirmPassword = (rule, values, callback) => {
    if (!values) {
      callback('请输入正确的密码');
    } else if (values && values !== this.state.value.password) {
      callback('两次输入密码不一致');
    } else {
      callback();
    }
  };

  validateFields = () => {
    const { validateFields } = this.refs.form;

    validateFields((errors, values) => {
      console.log({ errors })

      if (!errors) {
        Message.success('注册成功')
      }
    });
  }

  render() {
    return (
      <div style={styles.container}>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
          ref="form"
        >
          <div style={styles.content}>
            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>邮箱:</span>
              <FormBinder name="email" required type="email" message="请输入正确的邮箱" >
                <Input placeholder="ice-admin@alibaba-inc.com" />
              </FormBinder>
              <FormError style={styles.formItemError} name="email" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>昵称:</span>
              <FormBinder name="nickname" required message="请输入正确的昵称" >
                <Input placeholder="淘小宝" />
              </FormBinder>
              <FormError style={styles.formItemError} name="nickname" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>密码:</span>
              <FormBinder name="password" required validator={this.checkPassword}>
                <Input htmlType="password" placeholder="输入密码" />
              </FormBinder>
              <FormError style={styles.formItemError} name="password" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>确认密码:</span>
              <FormBinder name="confirmPassword" required validator={this.checkConfirmPassword}>
                <Input htmlType="password" placeholder="输入确认密码" />
              </FormBinder>
              <FormError style={styles.formItemError} name="confirmPassword" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>出生日期:</span>
                <FormBinder name="birthdate" getFieldValue={(date, formatDate) => { return formatDate }}>
                  <DatePicker format="YYYY-MM-DD" style={{ width: '200px' }} />
                </FormBinder>
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>籍贯地址:</span>
              <FormBinder name="city" >
                <CascaderSelect dataSource={cityData} style={{ width: '200px' }} />
              </FormBinder>
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>开启通知:</span>
              <FormBinder name="notification" valuePropName="checked">
                <Switch />
              </FormBinder>
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>用户协议:</span>
              <FormBinder
                name="agreement"
                valuePropName="checked"
              >
                <Checkbox />
              </FormBinder>
            </div>

            <Button type="primary" style={{width: '270px'}}  onClick={this.validateFields}>
              注 册
            </Button>
          </div>
        </FormBinderWrapper>

        <div style={styles.preview}>
          <strong>当前表单数据</strong>
          <pre>{JSON.stringify(this.state.value, null, 2)}</pre>
        </div>

      </div>
    );
  }
}

const styles = {
  formItem: {
    marginBottom: '20px',
    display: 'flex',
    alignItems: 'center',
  },
  formItemLabel: {
    width: '70px',
    mariginRight: '10px',
    display: 'inline-block',
    textAlign: 'right',
  },
  formItemError: {
    marginLeft: '10px',
  },
  preview: {
    border: '1px solid #eee',
    marginTop: 20,
    padding: 10
  }
}

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

时间类组件

时间类组件的 value 类型为 moment 对象,一般在提交服务器前需要预处理

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { FormBinderWrapper, FormBinder, FormError } from '@icedesign/form-binder';
import { DatePicker, TimePicker, Button } from '@alifd/next';
import moment from 'moment';

moment.locale('zh-cn');

const { MonthPicker, RangePicker } = DatePicker;

class Time extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: {
        datePicker: '',
        dateTimePicker: '',
        monthPicker: '',
        rangePicker: [],
        rangeRimePicker: [],
        timePicker: '',
      }
    };
  }

  formChange = value => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value });
  };

  validateFields = () => {
    const { validateFields } = this.refs.form;

    validateFields((errors, values) => {
      console.log(errors, values)
    });
  }

  render() {
    const config = {
      required: true,
      message: "请选择",
    }

    const style = {
      width: '350px',
    }

    return (
      <div style={styles.container}>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
          ref="form"
        >
          <div style={styles.content}>
            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>日期选择:</span>
              <FormBinder name="datePicker" {...config}>
                <DatePicker format="YYYY-MM-DD" style={{...style}} />
              </FormBinder>
              <FormError style={styles.formItemError} name="datePicker" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>日期时间:</span>
              <FormBinder name="dateTimePicker" {...config}>
                <DatePicker showTime format="YYYY-MM-DD" style={{...style}} />
              </FormBinder>
              <FormError style={styles.formItemError} name="dateTimePicker" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>月份选择:</span>
              <FormBinder name="monthPicker" {...config}>
                <MonthPicker format="YYYY-MM" style={{...style}} />
              </FormBinder>
              <FormError style={styles.formItemError} name="monthPicker" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>区间选择:</span>
              <FormBinder name="rangePicker" {...config}>
                <RangePicker format="YYYY-MM-DD" style={{...style}} />
              </FormBinder>
              <FormError style={styles.formItemError} name="rangePicker" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>区间时间:</span>
              <FormBinder name="rangeRimePicker" {...config}>
                <RangePicker showTime format="YYYY-MM-DD" />
              </FormBinder>
              <FormError style={styles.formItemError} name="rangeRimePicker" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formItemLabel}>时间选择:</span>
              <FormBinder name="timePicker" {...config}>
                <TimePicker format="HH:mm:ss" style={{...style}} />
              </FormBinder>
              <FormError style={styles.formItemError} name="timePicker" />
            </div>

            <Button type="primary" style={{marginLeft: '80px'}}  onClick={this.validateFields}>
              确 认
            </Button>
          </div>
        </FormBinderWrapper>

        <div style={styles.preview}>
          <strong>当前表单数据</strong>
          <pre>{JSON.stringify(this.state.value, null, 2)}</pre>
        </div>

      </div>
    );
  }
}

const styles = {
  formItem: {
    marginBottom: '20px'
  },
  formItemLabel: {
    marginRight: '10px',
  },
  formItemError: {
    marginLeft: '10px',
  },
  preview: {
    border: '1px solid #eee',
    marginTop: 20,
    padding: 10
  }
}

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

重置表单

经过 FormBinder 包裹的 Select 组件,重置值为未选择状态。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { FormBinderWrapper, FormBinder, FormError } from '@icedesign/form-binder';
import { Select, Button, Grid } from '@alifd/next';
const { Row, Col } = Grid;

const dataSource = [];

class Reset extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: {
        bu: 'taobao',
      },
    };
  }

  formChange = value => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value });
  };

  handleReset = () => {
    this.setState({
      value: {
        bu: 'taobao',
      },
    });
  };

  render() {
    return (
      <div>
        <FormBinderWrapper value={this.state.value} onChange={this.formChange} ref="form">
          <div style={styles.content}>
            <div style={styles.formItem}>
              <span>请选择:</span>
              <FormBinder name="bu" required message="请选择">
                <Select
                  dataSource={[
                    {
                      value: 'taobao',
                      label: '淘宝',
                    },
                    {
                      value: 'tmall',
                      label: '天猫',
                    },
                    {
                      value: 'aliyun',
                      label: '阿里云',
                    },
                    {
                      value: 'alitrip',
                      label: '飞猪',
                    },
                  ]}
                  placeholder="请选择"
                  autoWidth={false}
                />
              </FormBinder>
              <FormError name="bu" />
            </div>
            <Button type="primary" onClick={this.handleReset} style={styles.resetButton}>重 置</Button>
          </div>
        </FormBinderWrapper>

        <div style={styles.preview}>
          <strong>当前表单数据:</strong>
          <pre>
            {JSON.stringify(this.state.value, null, 2)}
          </pre>
        </div>
      </div>
    );
  }
}

const styles = {
  formItem: {
    display: 'flex',
    alignItems: 'center',
    marginBottom: '20px',
  },
  resetButton: {
    marginLeft: '56px',
  },
  preview: {
    border: '1px solid #eee',
    margin: '20px 0',
    padding: '10px'
  }
}

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

分步校验表单

在表单检验中,可以分步骤对表单进行校验,先校验一部分表单域通过后在检验另外一部分表单

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

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: {
        email: '',
        name: '',
        password: '',
      }
    };
  }

  formChange = value => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value });
  };

  validateFields = (fieldnames = []) => {
    const cb = (errors, values) => {
      console.log('validateFields:', errors, values)
    }

    const { validateFields } = this.refs.form;

    if (fieldnames.length) {
      validateFields(fieldnames, cb);
    } else {
      validateFields(cb);
    }
  }

  render() {

    return (
      <div>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
          ref="form"
        >
          <div>
            <div style={styles.formItem}>
              <span style={styles.formLabel}>名称:</span>
              <FormBinder name="name" required message="请输入正确的名称" >
                <Input placeholder="淘小宝" />
              </FormBinder>
              <FormError style={styles.formError} name="name" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formLabel}>邮箱:</span>
              <FormBinder name="email" type="email" required message="请输入正确的邮箱">
                <Input placeholder="ice-admin@alibaba-inc.com" />
              </FormBinder>
              <FormError style={styles.formError} name="email" />
            </div>

            <div style={styles.formItem}>
              <span style={styles.formLabel}>设置密码:</span>
              <FormBinder name="password" required message="请输入新密码" >
                <Input htmlType="password" placeholder="设置新密码"  />
              </FormBinder>
              <FormError style={styles.formError} name="password" />
            </div>

          </div>
        </FormBinderWrapper>

        <div style={{marginTop: 20}}>
        <Button type="primary" style={{marginRight: 10}} onClick={() => this.validateFields(['name', 'email'])}>
            先校验名称和邮箱
          </Button>
          <Button type="secondary" onClick={this.validateFields}>
            校验整个表单
          </Button>
        </div>

        <div style={styles.preview}>
          <strong>当前表单数据</strong>
          <pre>{JSON.stringify(this.state.value, null, 2)}</pre>
        </div>

      </div>
    );
  }
}

const styles = {
  formItem: {
    marginBottom: '20px'
  },
  formLabel: {
    width: '70px',
    marginRight: '10px',
    textAlign: 'right',
    display: 'inline-block'
  },
  formError: {
    marginLeft: '10px',
  },
  preview: {
    border: '1px solid #eee',
    marginTop: 20,
    padding: 10
  }
}

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

自定义校验

可以使用 validator 自定义校验,根据不同情况执行不同的校验规则

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

const { Row, Col } = Grid;

class CustomValidator extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: { input: '' },
    };
  }

  // 说明:
  //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
  //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
  formChange = (value) => {
    this.setState({ value })
  }

  // 通过 validator 自定义校验规则,更多用法参考 https://github.com/yiminghe/async-validator#usage
  inputValidator = (rule, value, callback) => {
    const errors = [];
    console.log(value)
    if (!value) {
      callback('输入不能为空');
    } else if (value.length < 8) {
      callback('输入长度必须大于 8 位');
    } else if (value.length > 16) {
      callback('输入长度必须小于 16 位');
    } else {
      callback();
    }
  };

  render() {
    return (
      <div>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
          ref="form"
        >
          <div style={styles.formItem}>
            <span style={styles.formLabel}>自定义校验:</span>
            <FormBinder name="input" required validator={this.inputValidator} >
              <Input placeholder="请输入"/>
            </FormBinder>
            <FormError name="input" style={styles.formError} />
          </div>
        </FormBinderWrapper>
        <p style={styles.desc}>输入不能为空,且长度必须大于8位小于16</p>
        <div style={styles.preview}>
          <strong>当前表单数据:</strong>
          <pre>
            {JSON.stringify(this.state.value, null, 2)}
          </pre>
        </div>
      </div>
    );
  }
}

const styles = {
  formItem: {
    dispaly: 'flex',
    alignItems: 'center',
  },
  formLabel: {
    marginRight: '10px'
  },
  formError: {
    marginLeft: '10px',
  },
  desc: {
    margin: '5px 0 20px 94px',
    color: '#999',
    fontSize: '12px'
  },
  preview: {
    border: '1px solid #eee',
    margin: '20px 0',
    padding: '10px'
  }
}

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

自定义 valuePropName

有时候自定义或第三方的表单组件的取值属性不是 value,可以通过 valuePropName 来进行修改,也可以通过 setFieldValue 和 getFieldValue 进行转换

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { FormBinderWrapper, FormBinder, FormError } from '@icedesign/form-binder';
import { Checkbox, Switch } from '@alifd/next';

class App extends Component {

  state = {
    value: {
      checkbox: true,
      switch: 1
    }
  };

  formChange = (value) => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value })
  }

  render() {
    return (
      <div>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
        >
          <div style={styles.content}>
            <div style={styles.formItem}>
              <span>复选框:</span>
              <FormBinder name="checkbox" valuePropName="checked">
                <Checkbox />
              </FormBinder>
            </div>

            <div style={styles.formItem}>
              <span>开关:</span>
              <FormBinder
                name="switch"
                valuePropName="checked"  // Switch 接收的属性是 `checked`
                setFieldValue={(selected) => { return selected === 1 }}  // 转换为 boolean 传给 switch
                getFieldValue={(checked) => { return checked ? 1 : 0 }}  // 返回值转换为 number 给表单值
              >
                <Switch size="small" />
              </FormBinder>
            </div>
          </div>
        </FormBinderWrapper>
        <div style={styles.preview}>
          <pre>{JSON.stringify(this.state, null, 2)}</pre>
        </div>
      </div>
    )
  }
}

const styles = {
  formItem: {
    display: 'flex',
    alignItems: 'center',
    marginBottom: '20px'
  },
  preview: {
    border: '1px solid #eee',
    marginTop: 20,
    padding: 10
  }
}

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

动态增加、减少表单项

演示 Input 组件数据交互和循环数组数据交互,数组表单用法,动态增加、减少表单项。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { FormBinderWrapper, FormBinder, FormError } from '@icedesign/form-binder';
import { Input, Button, Grid, DatePicker } from '@alifd/next';
const { Row, Col } = Grid;

class App extends Component {

  constructor(props) {
    super(props);

    this.state = {
      value: {
        items: [{}]
      }
    };
  }

  addItem = () => {
    this.state.value.items.push({});
    this.setState({ value: this.state.value });
  };

  formChange = value => {
    // 说明:
    //  1. 表单是双向通行的,所有表单的响应数据都会同步更新 value
    //  2. 这里 setState 只是为了实时展示当前表单数据的演示使用
    this.setState({ value });
  };

  changeItem = () => {
    let items = this.state.value.items;
    items[0].aaa = '有趣';
    this.setState({
      value: {
        ...this.state.value,
        items: items
      }
    });
  };

  removeItem = (index) => {
    this.state.value.items.splice(index, 1);
    this.setState({
      value: this.state.value
    });
  }

  validateAllFormField = () => {
     this.refs.form.validateFields((errors, values) => {
      console.log('errors', errors, 'values', values);
    });
  };

  render() {
    return (
      <div>
        <FormBinderWrapper
          value={this.state.value}
          onChange={this.formChange}
          ref="form"
        >
          <ArticleList
            items={this.state.value.items}
            addItem={this.addItem}
            removeItem={this.removeItem}
            validateAllFormField={this.validateAllFormField}
          />
        </FormBinderWrapper>

        <div style={styles.preview}>
          <strong>当前表单数据:</strong>
          <pre>{JSON.stringify(this.state.value, null, 2)}</pre>
        </div>
      </div>
    );
  }
}

class ArticleList extends Component {
  render() {
    return (
      <div>
        {this.props.items.map((item, index) => {
          return (
            <Row key={index} style={styles.row}>
              <Col>
                <span>文章名称:</span>
                <FormBinder required message="文章名称必填" name={`items[${index}].name`} >
                  <Input />
                </FormBinder>
                <FormError name={`items[${index}].name`} style={styles.formError} />
              </Col>
              <Col>
                <span>文章地址:</span>
                <FormBinder name={`items[${index}].url`} type="url" required message="请输入正确的 URL 地址" >
                  <Input />
                </FormBinder>
                <FormError name={`items[${index}].url`} style={styles.formError} />
              </Col>
              <Col>
                <Button type="secondary" onClick={this.props.removeItem.bind(this, index)}>删除</Button>
              </Col>
            </Row>
          );
        })}
        <div style={styles.buttons}>
          <Button type="secondary" onClick={this.props.addItem}>新 增</Button>
          <Button type="primary" style={{marginLeft: 10}} onClick={this.props.validateAllFormField}>
            校 验
          </Button>
        </div>
      </div>
    );
  }
}

const styles = {
  row: {
    marginBottom: '20px',
  },
  formError: {
    display: 'block',
    marginTop: '10px',
    marginLeft: '70px',
  },
  buttons: {
    margin: '20px 0 0 86px',
  },
  preview: {
    border: '1px solid #eee',
    marginTop: 20,
    padding: 10
  }
}

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