如何实现Ant design表单组件封装?

目标:自己实现一个antd表单组件

先看下Ant Design官网上给出的表单组件用法:

 1 import React, { Component } from 'react'
 2 import { Form, Icon, Input, Button } from 'antd'
 3 
 4 function hasErrors(fieldsError) {
 5   return Object.keys(fieldsError).some(field => fieldsError[field])
 6 }
 7 
 8 class HorizontalLoginForm extends React.Component {
 9   componentDidMount() {
10     // To disabled submit button at the beginning.
11     this.props.form.validateFields()
12   }
13 
14   handleSubmit = e => {
15     e.preventDefault()
16     this.props.form.validateFields((err, values) => {
17       if (!err) {
18         console.log('Received values of form: ', values)
19       }
20     })
21   };
22 
23   render() {
24     const {
25       getFieldDecorator,
26       getFieldsError,
27       getFieldError,
28       isFieldTouched
29     } = this.props.form
30 
31     // Only show error after a field is touched.
32     const userNameError =
33       isFieldTouched('userName') && getFieldError('userName')
34     const passwordError =
35       isFieldTouched('password') && getFieldError('password')
36     return (
37       
this.handleSubmit}> 38 <Form.Item 39 validateStatus={userNameError ? 'error' : ''} 40 help={userNameError || ''} 41 > 42 {getFieldDecorator('userName', { 43 rules: [{ required: true, message: 'Please input your username!' }] 44 })( 45 <Input 46 prefix={} 47 placeholder='Username' 48 /> 49 )} 50 51 <Form.Item 52 validateStatus={passwordError ? 'error' : ''} 53 help={passwordError || ''} 54 > 55 {getFieldDecorator('password', { 56 rules: [{ required: true, message: 'Please input your Password!' }] 57 })( 58 <Input 59 prefix={} 60 type='password' 61 placeholder='Password' 62 /> 63 )} 64 65 66 <Button 67 type='primary' 68 htmlType='submit' 69 disabled={hasErrors(getFieldsError())} 70 > 71 Log in 72 73 74 75 ) 76 } 77 } 78 79 const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })( 80 HorizontalLoginForm 81 ) 82 83 export default WrappedHorizontalLoginForm

 

组件功能分析:

  • 1-每个input输入框被触发后开始做非空校验并提示错误

  • 2-表单提交时做表单项校验,全部校验成功则提示登录,否则提示校验失败

  • 3-表单项增加前置图标

组件封装思路:

  • 1-需要一个高阶函数hoc FormCreate,用来包装用户表单,增加数据管理能力;hoc需要扩展四个功能:getFieldDecorator, getFieldsError, getFieldError, isFieldTouched。获取字段包装器方法getFieldDecorator的返回值是个高阶函数,接收一个Input组件作为参数,返回一个新的组件。这就是让一个普通的表单项,变成了带有扩展功能的表单项(例如:增加该项的校验规则)
  • 2-FormItem组件,负责校验及错误信息的展示,需要保存两个属性,校验状态和错误信息,当前校验通过时错误信息为空
  • 3-Input组件,展示型组件,增加输入框前置icon
  • 4-导出FormCreate装饰后的MForm组件,MForm组件负责样式布局以及提交控制

组件封装步骤:

  • 1-完成一个基础的组件MForm,让页面先展示出来

  • 2-写一个高阶组件FormCreate对MForm进行扩充,使MForm组件拥有数据管理的能力。

    • 保存字段选项设置 this.options = {}; 这里不需要保存为state,因为我们不希望字段选项变化而让组件重新渲染

    • 保存各字段的值 this.state = {}

    • 定义方法 getFieldDecorator()(),第一个参数传递配置项,第二个参数传入Input组件;第一个参数包括:当前校验项、校验规则 'username',{rules:[require:true,message:'请输入用户名']}

    • 在FormCreate中,克隆一份Input组件,并且定义Input的onChange事件。首先这里需要把已经存在的jsx克隆一份,并修改它的属性,直接修改属性是不允许的;这里在更高级别定义onChange事件,控制元素的值,这样当组件发生变化时,就不用进行组件之间的来回通信。数据变化交给容器型组件去做,低层级的组件只负责展示即可。

  • 3-增加提交校验功能

  • 4-增加FormItem组件,在表单项触发后做实时校验并提示错误信息

 

代码:MForm.js 

  • 以下每一步骤都可以独立运行

  • step1 - 搭建基础代码

  •  1 import React, { Component } from 'react'
     2 
     3 class MForm extends Component {
     4   render() {
     5     return (
     6       
    7 用户名: 8 密码: 9 10
    11 ) 12 } 13 } 14 15 export default MForm

     

  • step2 - 用高阶组件FormCreate对最后导出的MForm组件进行能力扩充;通过表单项组件FormItem展示校验错误信息
  •  1 import React, { Component } from 'react'
     2 
     3 // hoc: 包装用户表单,增加数据管理能力及校验功能
     4 const FormCreate = Comp => {
     5   return class extends Component {
     6     constructor(props) {
     7       super(props)
     8       this.options = {} // 保存字段选项设置
     9       this.state = {} // 保存各字段的值
    10     }
    11 
    12     // 处理表单项输入事件
    13     handleChange = e => {
    14       const { name, value } = e.target
    15       this.setState(
    16         {
    17           [name]: value
    18         },
    19         () => {
    20           // TODO: 处理状态变化后的校验
    21           // 由于setState是异步的,所以这里需要在回调函数中处理后续操作
    22           // 保证状态已经完成改变
    23         }
    24       )
    25     };
    26 
    27     getFieldDecorator = (field, option) => InputComp => {
    28       this.options[field] = option
    29       return (
    30         
    31 {/* 把已经存在的jsx克隆一份,并修改它的属性,直接修改属性是不允许的。 32 这里在更高级别定义onChange事件,控制元素的值,这样当组件发生变化时, 33 就不用进行组件之间的来回通信 */} 34 {React.cloneElement(InputComp, { 35 name: field, // 控件name 36 value: this.state[field] || '', // 控件值 37 onChange: this.handleChange // change事件处理 38 })} 39
    40 ) 41 }; 42 render() { 43 return ( 44 this.props} getFieldDecorator={this.getFieldDecorator} /> 45 ) 46 } 47 } 48 } 49 50 @FormCreate 51 class MForm extends Component { 52 render() { 53 const { getFieldDecorator } = this.props 54 55 return ( 56
    57 用户名:{getFieldDecorator('username', { 58 rules: [{ required: true, message: '请填写用户名' }] 59 })()} 60 密码:{getFieldDecorator('password', { 61 rules: [{ required: true, message: '请填写密码' }] 62 })()} 63 64
    65 ) 66 } 67 } 68 69 export default MForm

     

  • step3 - 增加点击提交按钮时校验表单项的逻辑
  • import React, { Component } from 'react'
    
    // hoc: 包装用户表单,增加数据管理能力及校验功能
    const FormCreate = Comp => {
      return class extends Component {
        constructor(props) {
          super(props)
          this.options = {} // 保存字段选项设置
          this.state = {} // 保存各字段的值
        }
        // 处理表单项输入事件
        handleChange = e => {
          const { name, value } = e.target
          this.setState(
            {
              [name]: value
            },
            () => {
              // 处理状态变化后的校验
              // 由于setState是异步的,所以这里需要在回调函数中处理后续操作
              // 保证状态已经完成改变
              this.validateField(name)
            }
          )
        };
    
        // 表单项校验,可以引用async-validator库来做校验,这里为了简便直接做非空校验
        validateField = field => {
          // this.options数据格式如下 ↓↓↓
          // {
          //   "username": {
          //     "rules": [{
          //       "required": true,
          //       "message": "请填写用户名"
          //     }]
          //   },
          //   "password": {
          //     "rules": [{
          //       "required": true,
          //       "message": "请填写密码"
          //     }]
          //   }
          // }
          const { rules } = this.options[field]
          const ret = rules.some(rule => {
            if (rule.required) {
              if (!this.state[field]) {
                this.setState({
                  [field + 'Message']: rule.message
                })
                // this.state数据格式如下 ↓↓↓
                // {"username":"","usernameMessage":"","password":"","passwordMessage":""}
                return true // 校验失败,返回true
              }
            }
          })
          if (!ret) {
            // 校验成功,将错误信息清空
            this.setState({
              [field + 'Message']: ''
            })
          }
          return !ret
        };
    
        // 校验所有字段
        validate = cb => {
          const rets = Object.keys(this.options).map(field =>
            this.validateField(field)
          )
          // 如果校验结果数组中全部为true,则校验成功
          const ret = rets.every(v => v === true)
          cb(ret)
        };
    
        getFieldDecorator = (field, option) => InputComp => {
          this.options[field] = option
          return (
            
    {/* 把已经存在的jsx克隆一份,并修改它的属性,直接修改属性是不允许的。 这里在更高级别定义onChange事件,控制元素的值, 这样当组件发生变化时,就不用进行组件之间的来回通信 */} {React.cloneElement(InputComp, { name: field, // 控件name value: this.state[field] || '', // 控件值 onChange: this.handleChange // change事件处理 })}
    ) }; render() { return ( <Comp {...this.props} getFieldDecorator={this.getFieldDecorator} validate={this.validate} /> ) } } } @FormCreate class MForm extends Component { onSubmit = () => { this.props.validate(isValid => { if (isValid) { alert('校验成功,可以登录了') console.log(this.props.value) } else { alert('校验失败') } }) }; render() { const { getFieldDecorator } = this.props return (
    用户名:{getFieldDecorator('username', { rules: [{ required: true, message: '请填写用户名' }] })()} 密码:{getFieldDecorator('password', { rules: [{ required: true, message: '请填写密码' }] })()}
    ) } } export default MForm

     

  • step4 - 增加表单输入时实时校验并提示错误逻辑,封装FormItem组件来展示错误信息,封装Input组件,增加前缀图标。至此,整个MForm组件就编写完成了!
  •   1 import React, { Component } from 'react'
      2 import { Icon } from 'antd'
      3 
      4 // hoc: 包装用户表单,增加数据管理能力及校验功能
      5 const FormCreate = Comp => {
      6   return class extends Component {
      7     constructor(props) {
      8       super(props)
      9       this.options = {} // 保存字段选项设置
     10       this.state = {} // 保存各字段的值
     11     }
     12 
     13     // 处理表单项输入事件
     14     handleChange = e => {
     15       const { name, value } = e.target
     16       this.setState(
     17         {
     18           [name]: value
     19         },
     20         () => {
     21           // 处理状态变化后的校验
     22           // 由于setState是异步的,所以这里需要在回调函数中处理后续操作
     23           // 保证状态已经完成改变
     24           this.validateField(name)
     25         }
     26       )
     27     };
     28 
     29     // 表单项校验,可以引用async-validator库来做校验,这里为了简便直接做非空校验
     30     validateField = field => {
     31       // this.options ↓↓↓
     32       // {
     33       //   "username": {
     34       //     "rules": [{
     35       //       "required": true,
     36       //       "message": "请填写用户名"
     37       //     }]
     38       //   },
     39       //   "password": {
     40       //     "rules": [{
     41       //       "required": true,
     42       //       "message": "请填写密码"
     43       //     }]
     44       //   }
     45       // }
     46       const { rules } = this.options[field]
     47       const ret = rules.some(rule => {
     48         if (rule.required) {
     49           if (!this.state[field]) {
     50             this.setState({
     51               [field + 'Message']: rule.message
     52             })
     53             // this.state ↓↓↓
     54             // {"username":"","usernameMessage":"","password":"","passwordMessage":""}
     55             return true // 校验失败,返回true
     56           }
     57         }
     58       })
     59       if (!ret) {
     60         // 校验成功,将错误信息清空
     61         this.setState({
     62           [field + 'Message']: ''
     63         })
     64       }
     65       return !ret
     66     };
     67 
     68     // 校验所有字段
     69     validate = cb => {
     70       const rets = Object.keys(this.options).map(field =>
     71         this.validateField(field)
     72       )
     73       // 如果校验结果数组中全部为true,则校验成功
     74       const ret = rets.every(v => v === true)
     75       cb(ret)
     76     };
     77 
     78     getFieldDecorator = (field, option) => InputComp => {
     79       this.options[field] = option
     80       return (
     81         
    82 {/* 把已经存在的jsx克隆一份,并修改它的属性,直接修改属性是不允许的。 83 这里在更高级别定义onChange事件,控制元素的值, 84 这样当组件发生变化时,就不用进行组件之间的来回通信 */} 85 {React.cloneElement(InputComp, { 86 name: field, // 控件name 87 value: this.state[field] || '', // 控件值 88 onChange: this.handleChange, // change事件处理 89 onFocus: this.handleFocus 90 })} 91
    92 ) 93 }; 94 95 // 控件获取焦点事件 96 handleFocus = e => { 97 const field = e.target.name 98 this.setState({ 99 [field + 'Focus']: true 100 }) 101 } 102 103 // 判断控件是否被点击过 104 isFieldTouched = field => !!this.state[field + 'Focus'] 105 106 // 获取控件错误提示信息 107 getFieldError = field => this.state[field + 'Message'] 108 109 render() { 110 return ( 111 <Comp 112 {...this.props} 113 getFieldDecorator={this.getFieldDecorator} 114 validate={this.validate} 115 isFieldTouched = {this.isFieldTouched} 116 getFieldError = {this.getFieldError} 117 /> 118 ) 119 } 120 } 121 } 122 123 class FormItem extends Component { 124 render() { 125 return ( 126
    127 { this.props.children } 128 { this.props.validateStatus === 'error' && ( 129

    { this.props.help}

    130 )} 131
    132 ) 133 } 134 } 135 136 class Input extends Component { 137 render() { 138 return ( 139
    140 {/* 前缀图标 */} 141 {this.props.prefix} 142 this.props} /> 143
    144 ) 145 } 146 } 147 148 @FormCreate 149 class MForm extends Component { 150 onSubmit = () => { 151 this.props.validate(isValid => { 152 if (isValid) { 153 alert('校验成功,可以登录了') 154 console.log(this.props.value) 155 } else { 156 alert('校验失败') 157 } 158 }) 159 }; 160 render() { 161 const { getFieldDecorator, isFieldTouched, getFieldError } = this.props 162 const usernameError = isFieldTouched('username') && getFieldError('username') 163 const passwordError = isFieldTouched('password') && getFieldError('password') 164 165 return ( 166
    167 <FormItem 168 validateStatus={ usernameError ? 'error' : '' } 169 help={usernameError || ''} 170 > 171 用户名:{getFieldDecorator('username', { 172 rules: [{ required: true, message: '请填写用户名' }] 173 })(} />)} 174 175 <FormItem 176 validateStatus={ passwordError ? 'error' : '' } 177 help={passwordError || ''} 178 > 179 密码:{getFieldDecorator('password', { 180 rules: [{ required: true, message: '请填写密码' }] 181 })(} />)} 182 183 184
    185 ) 186 } 187 } 188 189 export default MForm

     

  • index.js
  • import React from 'react'
    import ReactDOM from 'react-dom'
    import MForm from './components/MForm'
    ReactDOM.render(, document.querySelector('#root'))

     

最终效果:

如何实现Ant design表单组件封装?_第1张图片

 总结:

  • react的组件是自上而下的扩展,将扩展的能力由上往下传递下去,Input组件在合适的时间就可以调用传递下来的值。
  • react开发组件的原则是:把逻辑控制往上层提,低层级的组件尽量做成傻瓜组件,不接触业务逻辑。

 

转载于:https://www.cnblogs.com/dora-zc/p/10763746.html

你可能感兴趣的:(如何实现Ant design表单组件封装?)