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

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

 import React, { Component } from 'react'
import { Form, Icon, Input, Button } from 'antd' function hasErrors(fieldsError) {
return Object.keys(fieldsError).some(field => fieldsError[field])
} class HorizontalLoginForm extends React.Component {
componentDidMount() {
// To disabled 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

组件功能分析:

  • 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 - 搭建基础代码

  •  import React, { Component } from 'react'
    
     class MForm extends Component {
    render() {
    return (
    <div>
    用户名:<input type='text' />
    密码:<input type='password' />
    <button>Log in</button>
    </div>
    )
    }
    } export default MForm
  • step2 - 用高阶组件FormCreate对最后导出的MForm组件进行能力扩充;通过表单项组件FormItem展示校验错误信息
  •  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
    },
    () => {
    // TODO: 处理状态变化后的校验
    // 由于setState是异步的,所以这里需要在回调函数中处理后续操作
    // 保证状态已经完成改变
    }
    )
    }; 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} />
    )
    }
    }
    } @FormCreate
    class MForm extends Component {
    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>Log in</button>
    </div>
    )
    }
    } 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组件就编写完成了!
  •  import React, { Component } from 'react'
    import { Icon } from 'antd' // 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事件处理
    onFocus: this.handleFocus
    })}
    </div>
    )
    }; // 控件获取焦点事件
    handleFocus = e => {
    const field = e.target.name
    this.setState({
    [field + 'Focus']: true
    })
    } // 判断控件是否被点击过
    isFieldTouched = field => !!this.state[field + 'Focus'] // 获取控件错误提示信息
    getFieldError = field => this.state[field + 'Message'] render() {
    return (
    <Comp
    {...this.props}
    getFieldDecorator={this.getFieldDecorator}
    validate={this.validate}
    isFieldTouched = {this.isFieldTouched}
    getFieldError = {this.getFieldError}
    />
    )
    }
    }
    } class FormItem extends Component {
    render() {
    return (
    <div className='formItem'>
    { this.props.children }
    { this.props.validateStatus === 'error' && (
    <p style={ { color: 'red' } }>{ this.props.help}</p>
    )}
    </div>
    )
    }
    } class Input extends Component {
    render() {
    return (
    <div>
    {/* 前缀图标 */}
    {this.props.prefix}
    <input {...this.props} />
    </div>
    )
    }
    } @FormCreate
    class MForm extends Component {
    onSubmit = () => {
    this.props.validate(isValid => {
    if (isValid) {
    alert('校验成功,可以登录了')
    console.log(this.props.value)
    } else {
    alert('校验失败')
    }
    })
    };
    render() {
    const { getFieldDecorator, isFieldTouched, getFieldError } = this.props
    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' prefix={<Icon type='user' />} />)}
    </FormItem>
    <FormItem
    validateStatus={ passwordError ? 'error' : '' }
    help={passwordError || ''}
    >
    密码:{getFieldDecorator('password', {
    rules: [{ required: true, message: '请填写密码' }]
    })(<Input type='password' prefix={<Icon type='lock' />} />)}
    </FormItem>
    <button onClick={this.onSubmit}>Log in</button>
    </div>
    )
    }
    } 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开发组件的原则是:把逻辑控制往上层提,低层级的组件尽量做成傻瓜组件,不接触业务逻辑。

如何实现Ant design表单组件封装?的更多相关文章

  1. 文档驱动 —— 表单组件(五):基于Ant Design Vue 的表单控件的demo,再也不需要写代码了。

    源码 https://github.com/naturefwvue/nf-vue3-ant 特点 只需要更改meta,既可以切换表单 可以统一修改样式,统一升级,以最小的代价,应对UI的升级.切换,应 ...

  2. 文档驱动 —— 表单组件(六):基于AntDV的Form表单的封装,目标还是不写代码

    开源代码 https://github.com/naturefwvue/nf-vue3-ant 也不知道大家是怎么写代码的,这里全当抛砖引玉 为何封装? AntDV非常强大,效果也非常漂亮,功能强大, ...

  3. ReactJS实用技巧(2):从新人大坑——表单组件来看State

    不太清楚有多少初学React的同学和博主当时一样,在看完React的生命周期.数据流之后觉得已经上手了,甩开文档啪啪啪的开始敲了起来.结果...居然被一个input标签给教做人了. 故事是这样的:首先 ...

  4. 使用iview 的表单组件验证 Upload 组件

    使用iview 的表单组件验证 Upload 组件 结果: 点击提交按钮, 没有填的form 项, 提示错误, 当填入数据后提示验证成功 代码: <template> <div id ...

  5. Ant Design 表单中getFieldDecorator、getFieldValue、setFieldValue用法

    Ant Design 表单中getFieldDecorator.getFieldValue.setFieldValue用法 一.getFieldDecorator getFieldDecorator是 ...

  6. reactjs入门到实战(八)----表单组件的使用

    表单组件支持几个受用户交互影响的属性: value,用于 <input>.<textarea> 组件. checked,用于类型为 checkbox 或者 radio 的 &l ...

  7. 【form】 表单组件说明

    form表单组件 1)将form组件内的用户输入的<switch/> <input/> <checkbox/> <slider/> <radio/ ...

  8. 通过html()的方法获取文本内容, form表单组件显示的值与获取到的值不一致的问题

    我在通过 html()获取对应节点的内容,发现一个问题,获取到的 form表单组件的内容值是初始加载的值,而不是经过用户修改后的值.例如页面加载时组件<input type="text ...

  9. django基础之day09,创建一个forms表单组件进行表单校验,知识点:error_messages,label,required,invalid,局部钩子函数,全局钩子函数, forms_obj.cleaned_data,forms_obj.errors,locals(), {{ forms.label }}:{{ forms }},{{ forms.errors.0 }}

    利用forms表单组件进行表单校验,完成用户名,密码,确认密码,邮箱功能的校验 该作业包含了下面的知识点: error_messages,label,required,invalid,局部钩子函数,全 ...

随机推荐

  1. JavaScript读书笔记(3)-操作符、语句和函数

    1.  操作符 (1)       一元操作符 前置递增和递减操作符,变量的值都是在语句被求值以前改变的:后置相反 (2)       位操作符 在ECMAScript中,对数值进行位操作时,会发生以 ...

  2. 用js判断文本框中的是不是空,是否有空格

    <script type="text/javascript"> function checkRoleName(){ var userName=document.getE ...

  3. dsoframer注冊说明及在VC2010使用

    一.dsoframer在XP.win7和win8中的注冊方法. 从微软站点下载DsoFrmaer_KB311765_x86.exe,双击解开后得到的dsoframer.ocx等文件. (一)XP注冊 ...

  4. SICP 习题 (1.38)解题总结

    SICP 习题1.38 紧跟着习题1.37的方向,要求我们用习题1.37中定义的cont-frac过程计算数学家欧拉大师在论文De Fractionibus Continuis 中提到的e-2的连分式 ...

  5. MVC入门——编辑页

    添加Action  EditUserInfo using System; using System.Collections.Generic; using System.Linq; using Syst ...

  6. 面试题三:设计包括 min 函数的栈。

    3.设计包括 min 函数的栈. 定义栈的数据结构,要求加入一个 min 函数.可以得到栈的最小元素. 要求函数 min.push 以及 pop 的时间复杂度都是 O(1). 思路分析: a.要想一个 ...

  7. 基于EasyDarwin EasyPusher实现Android手机直播推送功能

    EasyPusher直播推送在之前就已经稳定支持了Windows.Linux.ARM上的RTSP直播推送功能,配合EasyDarwin开源流媒体服务器,延时基本在1s以内,这个技术方案经过一年多时间, ...

  8. Struts2中的redirect参数传递

    在Action之间传递参数有如下方法: (方法一): Xml代码 1. <result name="topic" type="redirect">f ...

  9. escape() VS encodeURI() VS encodeURIComponent()

    JavaScript中有三个可以对字符串编码的函数,分别是: escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decod ...

  10. MVC vs MVP vs MVVM

    一.MVC MVC模式的意思是,软件可以分成三个部分. 视图(View):用户界面. 控制器(Controller):业务逻辑 模型(Model):数据保存 各部分之间的通信方式如下. View 传送 ...