---
name: Form 表单
route: /form
menu: 表单组件
---

import { Playground } from 'docz';
import Form from './index';
import Checkbox from '../checkbox/index';
import Radio from '../radio/index';
import Input from '../input/index';
import InputNumber from '../input-number/index';
import Switch from '../switch/index';
import { IconUser, IconLock, IconSmile, IconMeh, IconFrown, IconDollarSign, IconHeart, IconThumbsUp } from '../icon/index';

redefy 的`Form`组件设计比较简单，没有过多的封装。

可能你会有这样的疑问：表单的封装程度低会降低组件的易用性吧？
在编码实践过程中，我见过一些封装程度很高的表单组件，看到这么个庞然大物，我内心是拒绝的。
因为封装程度高、组件设计复杂，导致上手难度增加，个性化定制也会令人头疼。

例如，有的表单组件会把`onChange`事件给封装起来，以实现组件内部的一些**“数据流”**，看似设计感满满的，
但这在外部使用者看来是个**黑盒**，如果想对这其中的某个数据进行修改，可能需要一些比较 hack 的操作，
同时需要对组件的内部实现有一定的了解。我认为这是个糟糕的设计。

## 演示

### 简单示例

<Playground>
  {() => {
    const [value, changeValue] = React.useState({
      username: '',
      password: '',
    });
    const [labelPosition, changeLabelPosition] = React.useState('right');
    const handleChange = React.useCallback(
      (propValue, target) => {
        changeValue({
          ...value,
          [target.name]: propValue,
        });
      },
      [value]
    );
    return (
      <>
        <Radio.Group value={labelPosition} onChange={changeLabelPosition}>
          <Radio value="right">right</Radio>
          <Radio value="left">left</Radio>
          <Radio value="top">top</Radio>
        </Radio.Group>
        <br />
        <Form
          style={{ width: '360px' }}
          value={value}
          labelPosition={labelPosition}
        >
          <Form.Item
            label={
              <>
                <IconUser /> Username
              </>
            }
          >
            <Input
              name="username"
              value={value.username}
              onChange={handleChange}
            />
          </Form.Item>
          <Form.Item
            label={
              <>
                <IconUser /> Password
              </>
            }
          >
            <Input
              name="password"
              value={value.password}
              onChange={handleChange}
            />
          </Form.Item>
        </Form>
      </>
    );
  }}
</Playground>

### 校验

实现校验需要两步：

1. 实现`validator`。一般是个对象，如果需要实现多级校验，可以传递数组。
2. 给`Form.Item`传递`prop`属性，对应于需要校验的字段。

<Playground>
  {() => {
    const [value, changeValue] = React.useState({
      username: '',
      password: '',
      habits: [],
      mood: 'smile',
      thumbsUp: false,
      money: 100,
    });
    const handleChange = React.useCallback(
      (propValue, target) => {
        changeValue({
          ...value,
          [target.name]: propValue,
        });
      },
      [value]
    );
    const validators = {
      username: { required: true, message: '用户名必填' },
      password: [
        {
          required: true,
          message: '密码必填',
        },
        {
          trigger: 'blur',
          custom(value) {
            if (value.length <= 6) {
              return '密码长度必须大于 6';
            }
          },
        },
      ],
      habits: { required: true, message: '爱好必填哦' },
      mood: {
        custom(value) {
          if (value === 'frown') {
            return '要开心哦O(∩_∩)O~~';
          }
        },
      },
      thumbsUp: {
        custom(value) {
          if (!value) {
            return 'Big brother is watching you!';
          }
        },
      },
      money: [
        { required: true, message: '必填' },
        { min: 0, max: 300, message: '知足常乐' },
      ],
    };
    return (
      <Form style={{ width: '360px' }} value={value} validators={validators}>
        <Form.Item
          label={
            <>
              <IconUser /> Username
            </>
          }
          prop="username"
        >
          <Input name="username" value={value.username} onChange={handleChange} />
        </Form.Item>
        <Form.Item
          label={
            <>
              <IconLock /> Password
            </>
          }
          prop="password"
        >
          <Input
            type="password"
            name="password"
            value={value.password}
            onChange={handleChange}
          />
        </Form.Item>
        <Form.Item
          label={
            <>
              <IconDollarSign /> Money
            </>
          }
          prop="money"
        >
          <InputNumber
            name="money"
            value={value.money}
            step={100}
            onChange={handleChange}
          />
        </Form.Item>
        <Form.Item
          label={
            <>
              <IconHeart /> Habits
            </>
          }
          prop="habits"
        >
          <Checkbox.Group
            name="habits"
            value={value.habits}
            onChange={handleChange}
          >
            <Checkbox value="study">Study</Checkbox>
            <Checkbox value="play">Play</Checkbox>
            <Checkbox value="eat">Sleep</Checkbox>
          </Checkbox.Group>
        </Form.Item>
        <Form.Item
          label={
            <>
              <IconUser /> Mood
            </>
          }
          prop="mood"
        >
          <Radio.Group name="mood" value={value.mood} onChange={handleChange}>
            <Radio value="smile">
              <IconSmile />
            </Radio>
            <Radio value="meh">
              <IconMeh />
            </Radio>
            <Radio value="frown">
              <IconFrown />
            </Radio>
          </Radio.Group>
        </Form.Item>
        <Form.Item
          label={
            <>
              <IconThumbsUp /> Thumbs up
            </>
          }
          prop="thumbsUp"
        >
          <Switch
            name="thumbsUp"
            checked={value.thumbsUp}
            onChange={handleChange}
          />
        </Form.Item>
      </Form>
    );
  }}
</Playground>

## API

### Form Props

| 参数 | 类型 | 默认值 | 说明 |
|-----|------|-------|-----|
| value | `Record<string, any>` | - | 值 |
| validators | `Record<string, FormValidator | FormValidator[]>` | - | 校验器 |
| labelPosition | `left`, `right`, `top` | `right` | 标签位置 |
| labelWidth | `number`, `string` | `100px` | 标签宽度 |
| labelHeight | `number`, `string` | `32px` | 标签高度 |
| onSubmit | `(valid: boolean) => void` | - | 提交表单事件 |
| className | `string` | - | 类名 |
| style | `React.CSSProperties` | - | 样式 |

### FormValidator

| 参数 | 类型 | 默认值 | 说明 |
|-----|------|-------|-----|
| required | `boolean` | - | 必填 |
| min | `number` | - | 最小值 |
| max | `number` | - | 最大值 |
| message | `string` | - | 校验未通过的提示文案 |
| trigger | `change`, `blur`, `none` | `change` | 校验的触发时机 |
| custom | `(propValue: any, value: NonNullable<FormProps['value']>) => boolean \| string \| undefined;` | - | 自定义校验方法 |

### Form.Item Props

| 参数 | 类型 | 默认值 | 说明 |
|-----|------|-------|-----|
| label | `React.ReactNode` | - | 标签 |
| prop | `string` | - | 属性名 |
| className | `string` | - | 类名 |
| style | `React.CSSProperties` | - | 样式 |

## 其他

Form.Item 表单项内部可以放任意子组件，但如果想实现表单校验功能，需要根据需求在一定的时机通知表单组件进行校验操作。

具体为：

1. 使用`Form`组件暴露出的`toBeField`方法封装自定义的表单项组件；
2. 表单项组件内，根据自己的需求调用`props.fieldContext`上的`onChange`或`onBlur`方法；

redefy 的`Checkbox`, `Radio`, `Input`等组件内部都已支持表单校验功能，以`Checkbox`组件为例：

```jsx
// CheckboxGroup.tsx

import { toBeField } from 'redefy';

class CheckboxGroup extends React.Component {
  // 数据改变的事件回调
  handleChange = () => {
    // ... 省略其他细节
    this.props.fieldContext.onChange();
  }
}

export default toBeField(CheckboxGroup);
```
