zoukankan      html  css  js  c++  java
  • 如何实现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       <Form layout='inline' onSubmit={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={<Icon type='user' style={{ color: 'rgba(0,0,0,.25)' }} />}
    47               placeholder='Username'
    48             />
    49           )}
    50         </Form.Item>
    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={<Icon type='lock' style={{ color: 'rgba(0,0,0,.25)' }} />}
    60               type='password'
    61               placeholder='Password'
    62             />
    63           )}
    64         </Form.Item>
    65         <Form.Item>
    66           <Button
    67             type='primary'
    68             htmlType='submit'
    69             disabled={hasErrors(getFieldsError())}
    70           >
    71             Log in
    72           </Button>
    73         </Form.Item>
    74       </Form>
    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       <div>
       7         用户名:<input type='text' />
       8         密码:<input type='password' />
       9         <button>Log in</button>
      10       </div>
      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         <div>
      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         </div>
      40       )
      41     };
      42     render() {
      43       return (
      44         <Comp {...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       <div>
      57         用户名:{getFieldDecorator('username', {
      58           rules: [{ required: true, message: '请填写用户名' }]
      59         })(<input type='text' />)}
      60         密码:{getFieldDecorator('password', {
      61           rules: [{ required: true, message: '请填写密码' }]
      62         })(<input type='password' />)}
      63         <button>Log in</button>
      64       </div>
      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 (
              <div>
                {/* 把已经存在的jsx克隆一份,并修改它的属性,直接修改属性是不允许的。
                这里在更高级别定义onChange事件,控制元素的值,
                这样当组件发生变化时,就不用进行组件之间的来回通信 */}
                {React.cloneElement(InputComp, {
                  name: field, // 控件name
                  value: this.state[field] || '', // 控件值
                  onChange: this.handleChange // change事件处理
                })}
              </div>
            )
          };
          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 (
            <div>
              用户名:{getFieldDecorator('username', {
                rules: [{ required: true, message: '请填写用户名' }]
              })(<input type='text' />)}
              密码:{getFieldDecorator('password', {
                rules: [{ required: true, message: '请填写密码' }]
              })(<input type='password' />)}
              <button onClick={this.onSubmit}>Log in</button>
            </div>
          )
        }
      }
      
      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         <div>
       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         </div>
       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       <div className='formItem'>
      127         { this.props.children }
      128         { this.props.validateStatus === 'error' && (
      129           <p style={ { color: 'red' } }>{ this.props.help}</p>
      130         )}
      131       </div>
      132     )
      133   }
      134 }
      135 
      136 class Input extends Component {
      137   render() {
      138     return (
      139       <div>
      140         {/* 前缀图标 */}
      141         {this.props.prefix}
      142         <input {...this.props} />
      143       </div>
      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       <div>
      167         <FormItem
      168           validateStatus={ usernameError ? 'error' : '' }
      169           help={usernameError || ''}
      170         >
      171         用户名:{getFieldDecorator('username', {
      172             rules: [{ required: true, message: '请填写用户名' }]
      173           })(<Input type='text' prefix={<Icon type='user' />} />)}
      174         </FormItem>
      175         <FormItem
      176           validateStatus={ passwordError ? 'error' : '' }
      177           help={passwordError || ''}
      178         >
      179         密码:{getFieldDecorator('password', {
      180             rules: [{ required: true, message: '请填写密码' }]
      181           })(<Input type='password' prefix={<Icon type='lock' />} />)}
      182         </FormItem>
      183         <button onClick={this.onSubmit}>Log in</button>
      184       </div>
      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(<MForm />, document.querySelector('#root'))

    最终效果:

     总结:

    • react的组件是自上而下的扩展,将扩展的能力由上往下传递下去,Input组件在合适的时间就可以调用传递下来的值。
    • react开发组件的原则是:把逻辑控制往上层提,低层级的组件尽量做成傻瓜组件,不接触业务逻辑。
  • 相关阅读:
    POI处理Excel工具类
    Mac打开隐藏文件夹
    markdown语法
    U盘分区合并
    数组
    Java插入到mysql数据库显示问号?
    使用vmware打开别人提供好的vmx没反应怎么办?
    求长方形的外接圆
    读取mysql数据库
    excel对列的常用操作
  • 原文地址:https://www.cnblogs.com/dora-zc/p/10763746.html
Copyright © 2011-2022 走看看