Skip to content

qiqiboy/react-antd-formutil

Repository files navigation

react-antd-formutil

npm peerDependencies definitionTypes gzip download issues license github github github github

react-antd-formutil

Happy to use react-formutil in the project based on ant-design@3&4 ^_^

ant-design 项目,结合 react-formutil 来快速构建表单。支持所有的ant-design输入型(data-entry)组件。

如果你在使用其他 react 组件库,可以查阅:

  1. react-bootstrap react-bootstrap-formutil npm
  2. react-md react-md-formutil npm
  3. Material-UI react-material-formutil npm

安装 Installation

react-antd-formutil

react-antd-formutil1.0.0版本开始,同时支持 Ant Design 3.x4.x版本

# npm
npm install react-antd-formutil --save

# yarn
yarn install react-antd-formutil

使用 Usage

react-antd-formutil 整合了 react-formutil 的组件,所以可以直接从react-antd-formutil中导出所需要的 react-formutil 组件。不用单独从 react-formutil 中导出。

先看一个使用示例(点击查看在线完整示例: react-antd-formutil on codesandbox.io):

import React, { Component } from 'react';
import { withForm, FormItem } from 'react-antd-formutil';
import { Input, Form } from 'antd'; // 导入antd的Input组件

@withForm
class MyForm extends Component {
    submit = () => {
        const { $invalid, $getFirstError, $params } = this.props.$formutil;

        if ($invalid) {
            alert($getFistError());
        } else {
            // submit your data
        }
    };

    render() {
        return (
            <Form onSubmit={this.onSubmit}>
                <FormItem
                    name="username"
                    itemProps={{
                        label: 'Username'
                    }}>
                    <Input />
                </FormItem>
            </Form>
        );
    }
}

FormItemreact-antd-formuitl 新增加的组件,withFormreact-formutil的组件(没错,你可以直接从react-antd-formutil中导出react-formutil的组件啦)。

只需要将ant-design的交互组件,嵌套在FormItem下,即可实现自动的表单状态同步。

<FormItem />

要实现将ant-design的交互组件的值能同步到 react-formutil 的状态中,需要通过 FormItem 这个组件来实现中间态绑定。

它的作用有些类似 antd 中的getFieldDecorator方法,但是用法比getFieldDecorator更优雅,更 JSX 语法。FormItem完全是标签声明式用法,它是对 antd 的Form.Item组件的再次封装。

所以FormItem会完整实现Form.Item所可以显示的校验状态、错误暂时等 UI 变化。

如果给 FormItem 传递了多个子节点,可能会出现无法非预期的异常情况。你可以了解如何正确的使用FormItem嵌套渲染多个节点元素?

支持传递的属性

FormItem可以接收所有antd中的Form.Item组件所接收的所有属性,另外还新增以下属性支持:

name

设置输入项的 name 值,表单项将会以 name 作为 key 收集到 formutil 的状态中。支持嵌套语法 (同react-formutilField同名参数,可以参考 name

$defaultValue

设置该表单项的默认值 (同react-formutilField同名参数,可以参考$defaultvalue

$validators

设置校验方法 (同react-formutilField同名参数, 可以参考 $validators

同 react-formutil 的 EasyField,FormItem 也内置了同样的校验规则:

  • required 必填 required
  • maxLength 。最大输入长度,有效输入时才会校验 maxLength="100"
  • minLength 最小输入长度,有效输入时才会校验 minLength="10"
  • max 最大输入数值,仅支持 Number 比较。有效输入时才会校验 max="100"
  • min 最小输入数值,仅支持 Number 比较。有效输入时才会校验 min="10"
  • pattern 正则匹配。有效输入时才会校验 pattern={/^\d+$/}
  • enum 枚举值检测。有效输入时才会校验 enum={[1,2,3]}
  • checker 自定义校验函数。checker={value => value > 10 && value < 100 || '输入比如大于10小与100'}

注:校验属性的值为 null 时表示不进行该校验

内置的校验规则无需再次声明,除非规则不符合预期,需要替换,则可以通过$validators 传递同名校验方法即可替换默认的。另外,内置的校验规则,如果校验不通过,会尝试去 validMessage 匹配错误信息。

itemProps

该属性为要传递给Form.Item组件的配置项:

<FormItem
    itemProps={{
        label: 'Username',
        colon: false
    }}>
    <Input />
</FormItem>
$parser

请参考react-formutil$parser介绍。

$formatter

请参考react-formutil$formatter介绍。

checked unchecked

对于 <Switch /> <Checkbox /> <Radio /> 这三种组件,其值默认是 checked 属性,为布尔值。可以通过checked unchecked来设置 checked 状态时所要映射的值:

<FormItem checked="yes" unchecked="no">
    <Switch />
</FormItem>

该示例中, 当 Switch 为开时,获取的值将为 yes。

$validateLazy

可以用来优化表单的校验速度,请参考: $validateLazy

$memo

可以用来优化当前表单项的性能,避免过多的重复渲染。如果你遇到了表单性能问题,可以尝试该属性来改善。

详细解释和使用、注意事项请参考: $memo

validMessage

设置校验结果的错误信息。

<FormItem
    name="username"
    required
    validMessage={{
        required: '请输入用户名'
    }}>
    <Input />
</FormItem>
valuePropName changePropName focusPropName blurPropName

该四个参数可以用来设置绑定到组件上的值或者值变动、是否聚焦等事件回调。该项一般不需要设置,FormItem 已经针对 antd 中的所有 data-entry 型组件做了兼容处理。

对于一些特殊场景,例如不需要同步 focusblur,则可以通过将该值设为{null}来禁用:

//禁用focus、blur状态同步
<FormItem focusPropName={null} blurPropName={null} name="username">
    <Input />
</FormItem>
getValueFromEvent

请参考 getValueFromEvent()

noStyle

该属性从 v1.1.0 起可用

该属性同时兼容antd@3.xantd@4.x,都可以使用!

noStyleAntDesign v4.0中新版本的Form.ItemnoStyle类似,可以用来控制是否输出Form.Item的额外的样式元素。缺省情况下默认值为false

noStyletrue时,将会只渲染字段节点本身,但是其表单状态依然会被处理收集。此时,如果其存在父级嵌套的FormItem,那么其表达校验状态将会传递给父级的FormItem来展现。

这对于连续的紧凑型表单元素将非常有用!可以避免校验错误描述信息都堆叠在一起! 但是没有额外的样式显示,包括表单校验状态都无法显示了。此时可以在其外层包裹一层不带nameFormItem,这些noStyle的表单项就会把他们自身的状态向上进行注册显示了!

但是有以下几点需要注意:

  1. 最外层的FormItem不能设置name属性,否则将不会被当作子级的校验状态容器
  2. 内层的FormItem需要添加相应的name值(向表单控制器注册自身)以及noStyle属性(不渲染额外的样式,避免和上层冲突)
// 这里不能设置name
<FormItem label="FormItem Group">
    <Input.Group compact>
        {/* 与普通的FormItem用法一致,只是多了个noStyle */}
        <FormItem name="address.province" noStyle required validMessage={{ required: 'Province requird!' }}>
            <Select placeholder="Select province">
                <Select.Option value="Zhejiang">Zhejiang</Select.Option>
                <Select.Option value="Jiangsu">Jiangsu</Select.Option>
            </Select>
        </FormItem>

        <FormItem name="address.street" noStyle required validMessage={{ required: 'Street requird!' }}>
            <Input style={{ width: '50%' }} placeholder="Input street" />
        </FormItem>
    </Input.Group>
</FormItem>

以上运行示例请参考 示例

errorLevel

用来覆盖全局的 errorLevel 设置。参考setErrorLevel(level)

setErrorLevel(level)

setErrorLevel 该方法可以用来全局设置错误信息何时出现,有三个级别可以设置:

  • 0$dirty $touched $invalid 都为 true 时
  • 1$dirty $invalid 都为 true 时
  • 2$invalid 为 true 时
  • off 关闭错误显示

默认值为 1

注意,该方法影响全局,如果只是希望单独对某个表单项进行设置,可以通过errorLevel属性进行设置:参考errorLevel

import { setErrorLevel } from 'react-antd-formutil';

setErrorLevel(0);

// 当关闭错误显示时,errorLevel='off',你可以手动自行设置错误展示方式:
<FormGroup
    name="errorOff"
    errorLevel="off"
    itemProps={{
        validateStatus: $formutil.$errors.errorOff ? 'error' : undefined,
        help: $formutil.$errors.errorOff ? <div>出错啦</div> : null
    }}>
    <Input />
</FormGroup>;

支持的组件

支持Checkbox.Group

DatePicker TimePicker DatePicker.WeekPicker DatePicker.MonthPicker DatePicker.RangePicker 等几个日期类组件,都是深度结合了moment使用的。如果希望收集到表单中的值是格式化好的时间字符串,可以通过$parser $formatter实现:

<FormItem name="datepicker" $parser={moment => moment.format('YYYY-MM-DD')} $formatter={date => moment(date)}>
    <DatePicker />
</FormItem>

对于DatePicker.RangePicker,由于其值是一个数组,所以需要这样处理:

<FormItem
    name="datepicker"
    $parser={moments => moments.map(moment => moment.format('YYYY-MM-DD'))}
    $formatter={dates => dates.map(date => moment(date))}>
    <DatePicker.RangePicker />
</FormItem>

Pagination 并非antd所归纳的data entry组件,但是其接口设计也可以支持FormItem

<FormItem name="page" $defaultValue={2}>
    <Pagination pageSize={10} total={100} />
</FormItem>

支持Radio.Group

Switch Checkbox(不包括Checkbox.Group) Radio(不包括Radio.Group)三个组件,可以通过给FormItem传递checked unchecked属性来改变被勾选时所映射到表单状态中的值:

<FormItem checked="yes" unchecked="no">
    <Switch />
</FormItem>

Transfer收集到表单状态中的是targetKeys

参考 DatePicker

Upload组件非常特殊,其接受fileList对象作为整个组件的状态。而实际业务中,往往只需要获取上传文件的返回的地址,或者一组文件的地址。可以通过$parser控制如何获取上传结果的值,并且可以通过$parser的第二个回调方法$setViewValue来控制fileList对象,实现对文件上传数量的控制。

单个文件上传,获取单个文件上传地址

<FormItem
    name="upload"
    $formatter={url =>
        url && [
            {
                url,
                uid: url,
                status: 'done',
                name: url.split('/').slice(-1)[0]
            }
        ]
    }
    $parser={(info, $setViewValue) => {
        // 必不可少,限制只能上传一个文件
        $setViewValue(info.fileList.slice(-1));

        if (info.file.status === 'done') {
            return info.file.response.url;
        }
    }}
    itemProps={{ ...formItemLayout, label: 'Upload' }}
    required>
    <Upload {...uplodConfig}>
        <Button>
            <UploadOutlined /> Click to Upload
        </Button>
    </Upload>
</FormItem>

多文件列表上传,获取多个文件上传地址数组

<FormItem
    name="upload"
    $formatter={urls =>
        urls &&
        urls.map(url => ({
            url,
            uid: url,
            status: 'done',
            name: url.split('/').slice(-1)[0]
        }))
    }
    $parser={(info, $setViewValue) => {
        // 限制最大上传文件数量为3,如果不需要限制,可以移除该行,或者修改该值
        $setViewValue(info.fileList.slice(-3));

        return info.fileList.filter(file => file.status === 'done').map(file => file.url || file.response.url);
    }}
    itemProps={{ ...formItemLayout, label: 'Upload' }}
    required>
    <Upload {...uplodConfig}>
        <Button>
            <UploadOutlined /> Click to Upload
        </Button>
    </Upload>
</FormItem>

动态className

FormGroup会自动给表单节点增加与该表单项校验状态相关的 className:

  • has-error
  • is-invalid
  • is-valid
  • is-touched
  • is-untouched
  • is-focused
  • is-unfocused
  • is-dirty
  • is-pristine

FAQ

给组件设置的onChange、onFocus等方法无效、不执行

FormItem会覆盖掉直接添加到 antd 组件上的onFocus onBlur onChange方法,所以如果需要这三个事件方法,需要添加到 FormItem上:

<FormItem name="test" onChange={ev => console.log('change', ev)} onFocus={ev => console.log('focus', ev)}>
    <Input />
</FormItem>

RangePicker 在safari下假死?

经过 debug,在3.8.x版本上,依然存在对RangePicker设置onFocus onBlur会异常频繁触发(比如在光标经过日期选择面板中每个数字时)的问题。可以禁用onFocus onBlur状态同步:

<FormItem name="datepicker" focusPropName={null} blurPropName={null}>
    <DatePicker.RangePicker />
</FormItem>

在生产环境(NODE_ENV==='production')部分组件调用有异常?

如果在生产环境,发现例如Checkbox Radio Switch等组件无法正确捕获用户输入的值,这种情况一般是由于项目中使用了babel-plugin-import插件。

react-antd-formutil中是使用 import { Switch } from 'antd' 这种写法来调用 Switch 组件的,而babel-plugin-import插件会将项目源代码中的类似语句,替换成import Switch from 'antd/lib/switch'。这两种写法获取到的Switch其实并不是严格意义上的相等,前者是对后者的又一层导出封装。

而由于babel-plugin-import一般仅仅会配置成仅仅对项目代码进行处理,所以处于项目node_modules目录中的react-antd-formutil中的语句不会被处理。我们需要通过修改项目 webpack 配置的方式,来使babel-plugin-import插件能处理react-antd-formutil的代码。

可以编辑项目的 webpack 配置(只需要配置生产环境的构建配置即可),在rules模块下添加以下的代码:

{
    test: /\.(js|mjs)$/,
    include: /react-antd-formutil/, // 仅仅处理react-antd-formutil即可
    loader: require.resolve('babel-loader'),
    options: {
        babelrc: false,
        plugins: [[
            "import",
            {
                "libraryName": "antd"
            },
            "antd"
        ]]
    }
}

如何正确的使用FormItem嵌套渲染多个节点元素?

你可以通过给给children属性传递render props函数,来自由定义要渲染出的节点。但是请注意,当传递一个render props函数时,需要手动绑定相关绑定事件和 value 属性!

children函数接受一个$fieldHandler的对象,默认情况下其包含value onChange onFocus onBlur四个属性,但是如果你给FormItem传递了valuePropName等属性的话,这个值将会变为你通过valuePropName所定义的名字。

更具体解释可以参考 react-formutil.$fieldHandler

<FormItem name="username">
    {$fieldHandler => (
        <>
            <Input {...$fieldHandler} />
            <div>其它节点内容</div>
        </>
    )}
</FormItem>