Antd Form表单封装

1,134 阅读6分钟

这是3.0Antd里Form的源代码

import { Form, Icon, Input, Button } from 'antd';
import React from 'react'

function hasErrors(fieldsError) {
  return Object.keys(fieldsError).some(field => fieldsError[field]);
}

class HorizontalLoginForm extends React.Component {
  componentDidMount() {
    // To disable submit button at the beginning.
    this.props.form.validateFields();
  }

  handleSubmit = e => {
    e.preventDefault();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        console.log('Received values of form: ', values);
      }
    });
  };

  render() {
    const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;

    // Only show error after a field is touched.
    const usernameError = isFieldTouched('username') && getFieldError('username');
    const passwordError = isFieldTouched('password') && getFieldError('password');
    return (
      <Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
          {getFieldDecorator('username', {
            rules: [{ required: true, message: 'Please input your username!' }],
          })(
            <Input
              prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
              placeholder="Username"
            />,
          )}
        </Form.Item>
        <Form.Item validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
          {getFieldDecorator('password', {
            rules: [{ required: true, message: 'Please input your Password!' }],
          })(
            <Input
              prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
              type="password"
              placeholder="Password"
            />,
          )}
        </Form.Item>
        <Form.Item>
          <Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
            Log in
          </Button>
        </Form.Item>
      </Form>
    );
  }
}

const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);

export default WrappedHorizontalLoginForm;

我们来尝试自行封装一个Form表单组件

先简单写一个MFrom表单包含文本密码按钮,用一个高阶组件MFormCreate 封装一层

import React, { Component } from 'react'
const MFormCreate = Comp => {
    return class extends Component{
        constructor(props){
            super(props);
            this.options = {}; //各个字段的选项值 它的变化不会影响组件渲染
            this.state = {}; // 各个字段的值 它的变化 要出发校验的函数 对数据进行渲染
        }
        render(){
            return (
                <div>
                    <Comp {...this.props}></Comp>
                </div>
            )
        }
    }
}

class MFrom extends Component {
    handleSubmit = () => {
        alert('提交')
    }
    render() {
        return (
            <div>
                <input type="text"/>
		<input type="password" />
                <input type="button" value="登录" onClick={this.handleSubmit} />
            </div>
        )
    }
}

export default MFormCreate(MFrom);

此时我们要添加对表单项进行校验的功能

参考源代码里用getFieldDecorator高阶组件传值,包括表单项名称,校验规则,以及一个input标签。

  1. 改写MFrom渲染的dom结构
  2. 在MFrom的render方法中用this.props.form获取该函数
render() {
        const { getFieldDecorator } = this.props.form;
        return (
            <div>
                {
                    getFieldDecorator('username',{
                        rules:[
                            {
                                required: true,
                                message: '用户名是必填项'
                            }
                        ]
                    })(<input type="text"/>)
                }
                {
                    getFieldDecorator('password',{
                        rules:[
                            {
                                required: true,
                                message: '密码是必填项'
                            }
                        ]
                    })(<input type="password" />)
                }
                
                <input type="button" value="登录" onClick={this.handleSubmit} />
            </div>
        )
    }
  1. 在MFormCreate组件中返回的Comp组件标签内添加form属性
<Comp {...this.props} form={this.form()}></Comp>
  1. 创建form方法,并且定义getFieldDecorator()
form = () => {return {getFieldDecorator: this.getFieldDecorator,}}
  1. 创建getFieldDecorator方法来实现对表单项变成控件
    1. 将rules配进options
    2. 将传进来的组件设置属性
    3. 当输入框更改时,用this.setState将最新的值保存
getFieldDecorator = (filedName,option) => {
            //设置字段选项配置 存储校验错误的配置
            this.options[filedName] = option;
            // console.log(this.options);
            return (InputComp) => {
                // 更加api的方式
                return <div>
                    {
                        React.cloneElement(InputComp,{
                            name:filedName, //控件的name
                            value: this.state[filedName] || '', //控件值
                            onChange: this.hanleChange
                        })
                    }
                    {
                        this.state[filedName+'Message'] && (
                            <p style={{color:'red'}}>{this.state[filedName + 'Message']}</p>
                        )
                    }
                </div>
            }
        }
  1. 处理表单项输入事件hanleChange
    1. 获取输入框值并且setState更新值
    2. 设置完值后校验各个字段规则
        hanleChange = (e) => {
            const { name, value } = e.target;
            //setState异步 可能先校验再设置值
            this.setState({
                [name]:value
            },()=>{
                //设置完值之后,再校验各个字段
                this.validateField(name);
            })
        }
  1. 表单项的校验validateField
    1. 将表单项规则存储到定义的options
    2. 获取规则读取(这里是必填项校验 required)
    3. 根据校验结果设置state,并且返回值(让后面多次判断需求)
    4. 并且在第五步里添加一个p标签来返回校验不成功的信息
validateField = (filedName) => {
            const { rules } = this.options[filedName];
            const ret = rules.some(rule=>{
                if(rule.required){
                    //输入框中为空,要出现错误的信息展示
                    if(!this.state[filedName]){
                        this.setState({
                            [filedName+'Message']:rule.message,
                        })
                        return true;//校验失败 返回true
                    }
                }
            })
            //如果校验成功
            if(!ret){
                this.setState({
                    [filedName+"Message"]:'',
                })
            }
            return !ret; // 校验成功返回 false
        }

这时候已经可以实时获取校验结果了

  1. 提交校验handleSubmit
    1. 改写MFrom组件里的handleSubmit方法
handleSubmit = () => {
        // 校验各个字段的必填项是否正确
        this.props.form.validateFields((isValid)=>{
            if(isValid){
                alert('校验成功');
            }else{
                alert('校验失败,请检查规则');
            }
        })
    }
  1. 在MFormCreate 函数中对应的form方法里添加对应字段
  2. 创建对应的方法validateFields
    • 获取校验字段options里
    • 将校验字段放到validateField里去校验
    • 根据返回值来给出提示消息
        validateFields = (cb) => {
            const rets = [];
            Object.keys(this.options).map(fieldName=>{
                rets.push(this.validateField(fieldName))
            })
            const ret = rets.every(v=>v===true)
            cb(ret);
        }

截至到目前为止的完整代码

import React, { Component } from 'react'

//高阶组件
const MFormCreate = Comp => {
    return class extends Component{
        constructor(props){
            super(props);
            this.options = {}; //各个字段的选项值 它的变化不会影响组件渲染
            this.state = {}; // 各个字段的值 它的变化 要出发校验的函数 对数据进行渲染
        }

        //处理表单项输入事件
        hanleChange = (e) => {
            const { name, value } = e.target;
            // console.log(`name:${name} value:${value}`);
            //setState异步 可能先校验再设置值
            this.setState({
                [name]:value
            },()=>{
                //设置完值之后,再校验各个字段
                this.validateField(name);
            })


        }

        //表单项的校验
        validateField = (filedName) => {
            const { rules } = this.options[filedName];
            const ret = rules.some(rule=>{
                if(rule.required){
                    //输入框中为空,要出现错误的信息展示
                    if(!this.state[filedName]){
                        this.setState({
                            [filedName+'Message']:rule.message,
                        })
                        return true;//校验失败 返回true
                    }
                }
            })
            //如果校验成功
            if(!ret){
                this.setState({
                    [filedName+"Message"]:'',
                })
            }
            // console.log(this.state);
            return !ret; // 校验成功返回 false
        }

        //按钮校验
        validateFields = (cb) => {
            console.log(Object.keys(this.options));
            const rets = [];
            Object.keys(this.options).map(fieldName=>{
                rets.push(this.validateField(fieldName))
            })
            const ret = rets.every(v=>v===true)
            cb(ret);
        }

        //1. 将rules配进options
        // 2. 将传进来的组件设置属性
        // 3. 当输入框更改时,用this.setState将最新的值保存
        getFieldDecorator = (filedName,option) => {
            //设置字段选项配置 存储校验错误的配置
            this.options[filedName] = option;
            // console.log(this.options);
            return (InputComp) => {
                // 更加api的方式
                return <div>
                    {
                        React.cloneElement(InputComp,{
                            name:filedName, //控件的name
                            value: this.state[filedName] || '', //控件值
                            onChange: this.hanleChange
                        })
                    }
                    {
                        this.state[filedName+'Message'] && (
                            <p style={{color:'red'}}>{this.state[filedName + 'Message']}</p>
                        )
                    }
                    
                </div>
            }
        }

        form = () => {
            return {
                getFieldDecorator: this.getFieldDecorator,
                validateFields: this.validateFields,
            }
        }

        render(){
            return (
                <div>
                    <Comp {...this.props} form={this.form()}></Comp>
                </div>
            )
        }
    }
}

class MFrom extends Component {
    handleSubmit = () => {
        // 校验各个字段的必填项是否正确
        this.props.form.validateFields((isValid)=>{
            if(isValid){
                alert('校验成功');
            }else{
                alert('校验失败,请检查规则');
            }
        })
    }

    render() {
        const { getFieldDecorator } = this.props.form;
        return (
            <div>
                {
                    getFieldDecorator('username',{
                        rules:[
                            {
                                required: true,
                                message: '用户名是必填项'
                            }
                        ]
                    })(<input type="text"/>)
                }
                {
                    getFieldDecorator('password',{
                        rules:[
                            {
                                required: true,
                                message: '密码是必填项'
                            }
                        ]
                    })(<input type="password" />)
                }
                
                <input type="button" value="登录" onClick={this.handleSubmit} />
            </div>
        )
    }
}

export default MFormCreate(MFrom);

FormItem和Input封装

FormItem

查看源代码的FormItem标签,有validateStatus和help属性来判断是否提示错误信息。所以是把错误信息的提示放到FormItem中来渲染。

  1. 根据源代码改写MFrom组件
render() {
        const { getFieldDecorator, isFieldTouched, getFieldError } = this.props.form;
        const usernameError = isFieldTouched('username') && getFieldError('username');
        const passwordError = isFieldTouched('password') && getFieldError('password');
        return (
            <div>
                <FormItem validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
                    {
                        getFieldDecorator('username', {
                            rules: [
                                {
                                    required: true,
                                    message: '用户名是必填项'
                                }
                            ]
                        })(<input type="text" />)
                    }
                </FormItem>
                <FormItem validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
                    {
                        getFieldDecorator('password', {
                            rules: [
                                {
                                    required: true,
                                    message: '密码是必填项'
                                }
                            ]
                        })(<input type="password" />)
                    }
                </FormItem>
                <input type="button" value="登录" onClick={this.handleSubmit} />
            </div>
        )
    }
  1. 写FormItem子组件(把高阶组件中展示错误信息的代码去掉放在这里控制)
class FormItem extends Component {
    render() {
        return (
            <div>
                {this.props.children}
                {/* 错误信息展示 */}
                {
                    this.props.validateStatus && (
                        <p style={{ color: 'red' }}>{this.props.help}</p>
                    )
                }
            </div>
        )
    }
}
  1. MFormCreate组件中的form方法添加对应的字段
  2. 判断字段是否被点击过的方法isFieldTouched
    1. 这里要先在表单项控件getFieldDecorator中添加一个onFocus事件 当控件被点击的时候给state里添加[fieldName+'Focus']:true
    2. 判断[fieldName+'Focus']是否为true来判断是否有被点击过
  3. 获取错误信息getFieldError。获取state里的[fieldName+'Message']

Input

  1. 根据源代码改写MForm组件的render方法
  2. 创建Input子组件
所有的完整代码
import React, { Component } from 'react'
import {Icon} from 'antd'
//高阶组件
const MFormCreate = Comp => {
    return class extends Component {
        constructor(props) {
            super(props);
            this.options = {}; //各个字段的选项值 它的变化不会影响组件渲染
            this.state = {}; // 各个字段的值 它的变化 要出发校验的函数 对数据进行渲染
        }

        //处理表单项输入事件
        hanleChange = (e) => {
            const { name, value } = e.target;
            // console.log(`name:${name} value:${value}`);
            //setState异步 可能先校验再设置值
            this.setState({
                [name]: value
            }, () => {
                //设置完值之后,再校验各个字段
                this.validateField(name);
            })


        }

        //表单项的校验
        validateField = (fieldName) => {
            const { rules } = this.options[fieldName];
            const ret = rules.some(rule => {
                if (rule.required) {
                    //输入框中为空,要出现错误的信息展示
                    if (!this.state[fieldName]) {
                        this.setState({
                            [fieldName + 'Message']: rule.message,
                        })
                        return true;//校验失败 返回true
                    }
                }
            })
            //如果校验成功
            if (!ret) {
                this.setState({
                    [fieldName + "Message"]: '',
                })
            }
            // console.log(this.state);
            return !ret; // 校验成功返回 false
        }

        //按钮校验
        validateFields = (cb) => {
            console.log(Object.keys(this.options));
            const rets = [];
            Object.keys(this.options).map(fieldName => {
                rets.push(this.validateField(fieldName))
            })
            const ret = rets.every(v => v === true)
            cb(ret);
        }

        handleFocus = (e) => {
            const fieldName = e.target.name;
            this.setState({
                [fieldName + 'Focus']: true
            })
        }

        //1. 将rules配进options
        // 2. 将传进来的组件设置属性
        // 3. 当输入框更改时,用this.setState将最新的值保存
        getFieldDecorator = (fieldName, option) => {
            //设置字段选项配置 存储校验错误的配置
            this.options[fieldName] = option;
            // console.log(this.options);
            return (InputComp) => {
                // 更加api的方式
                return <div>
                    {
                        React.cloneElement(InputComp, {
                            name: fieldName, //控件的name
                            value: this.state[fieldName] || '', //控件值
                            onChange: this.hanleChange,
                            onFocus: this.handleFocus
                        })
                    }

                </div>
            }
        }

        //判断控件是否被点击过
        isFieldTouched = (fieldName) => {
            return !!this.state[fieldName + 'Focus']
        }

        //获取控件的错误的提示信息
        getFieldError = (fieldName) => {
            return this.state[fieldName + 'Message']
        }

        form = () => {
            return {
                getFieldDecorator: this.getFieldDecorator,
                validateFields: this.validateFields,
                isFieldTouched: this.isFieldTouched,
                getFieldError: this.getFieldError,
            }
        }

        render() {
            return (
                <div>
                    <Comp {...this.props} form={this.form()}></Comp>
                </div>
            )
        }
    }
}

class FormItem extends Component {
    render() {
        return (
            <div>
                {this.props.children}
                {/* 错误信息展示 */}
                {
                    this.props.validateStatus && (
                        <p style={{ color: 'red' }}>{this.props.help}</p>
                    )
                }
            </div>
        )
    }
}


class Input extends Component {
    render() {
        return (
            <div>
                {this.props.prefix}
                <input type="text" {...this.props} />
            </div>
        )
    }
}



class MFrom extends Component {
    handleSubmit = () => {
        // 校验各个字段的必填项是否正确
        this.props.form.validateFields((isValid) => {
            if (isValid) {
                alert('校验成功');
            } else {
                alert('校验失败,请检查规则');
            }
        })
    }

    render() {
        const { getFieldDecorator, isFieldTouched, getFieldError } = this.props.form;
        const usernameError = isFieldTouched('username') && getFieldError('username');
        const passwordError = isFieldTouched('password') && getFieldError('password');
        return (
            <div>
                <FormItem validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
                    {
                        getFieldDecorator('username', {
                            rules: [
                                {
                                    required: true,
                                    message: '用户名是必填项'
                                }
                            ]
                        })(<Input
                            prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
                            placeholder="Username"
                            type="text" 
                            />)
                    }
                </FormItem>
                <FormItem validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
                    {
                        getFieldDecorator('password', {
                            rules: [
                                {
                                    required: true,
                                    message: '密码是必填项'
                                }
                            ]
                        })(<Input 
                            prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
                            type="password"
                            placeholder="Password"
                            />)
                    }
                </FormItem>


                <input type="button" value="登录" onClick={this.handleSubmit} />
            </div>
        )
    }
}

export default MFormCreate(MFrom);

思想小结: 状态提升。将逻辑操作部分尽量放到高阶组件中去完成,而像Input等子组件只进行展示。