【React全家桶入门之五】组件化表单/表单控件
来源:互联网 发布:淳儿单纯知乎 编辑:程序博客网 时间:2024/05/18 03:35
问题
上一篇我们实现一个最基本的表单验证。
如果我们想要再写一个添加图书的功能,以同样的方式去写就会发现会出现很多重复的代码。因为所有的验证代码都是耦合在组件中的,没有办法实现复用。
重复代码是混乱的根源!
既然我们用了React,那我们是不是可以用组件化的方式去给表单验证这些逻辑解耦呢?
高阶组件:formProvider
什么是高阶组件?
高阶组件就是返回组件的组件(函数)
为什么要通过一个组件去返回另一个组件?
使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能。
我们现在已经有了带有表单校验功能的添加用户的表单,这里的表单有3个字段:name、age、gender,并且每个字段都有它自己的校验规则和对应的错误信息。
要做一个添加图书的功能,图书的表单有name、price、owner_id三个字段,一样地,每个字段有它自己的校验规则和错误信息。
仔细想想,每当我们需要写一个表单的时候,都需要有一个地方来保存表单字段的值(state),有一个函数来处理表单值的更新和校验(handleValueChange),这些东西我们可以用高阶组件来封装。
而添加用户的表单和添加图书的表单之间的不同之处仅仅是表单字段以及字段的默认值、校验规则和错误信息。
那么我们的高阶组件模型就出来了:
function formProvider (fields) { return function (Comp) { constructor (props) { super(props); this.state = { form: {...}, formValid: false // 加了一个formValid用来保存整个表单的校验状态 }; } handleValueChange (field, value) {...} class FormComponent extends React.Component { render () { const {form, formValid} = this.state; return ( <Comp {...this.props} form={form} formValid={formValid} onFormChange={this.handleValueChange}/> ); } } return FormComponent; }}
formProvider接收一个fields参数,并返回一个函数,这个函数接收一个组件作为参数并返回一个组件,所以它的用法是这样的:
UserAdd = formProvider(fields)(UserAdd);
经过formProvider处理后的UserAdd组件会得到额外的props:
- form
- formValid
- onFormChange
在/src
下新建一个目录utils
,新建formProvider.js
文件,写入具体的代码实现:
import React from 'react';function formProvider (fields) { return function (Comp) { const initialFormState = {}; for (const key in fields) { initialFormState[key] = { value: fields[key].defaultValue, error: '' }; } class FormComponent extends React.Component { constructor (props) { super(props); this.state = { form: initialFormState, formValid: false }; this.handleValueChange = this.handleValueChange.bind(this); } handleValueChange (fieldName, value) { const { form } = this.state; const newFieldState = {value, valid: true, error: ''}; const fieldRules = fields[fieldName].rules; for (let i = 0; i < fieldRules.length; i++) { const {pattern, error} = fieldRules[i]; let valid = false; if (typeof pattern === 'function') { valid = pattern(value); } else { valid = pattern.test(value); } if (!valid) { newFieldState.valid = false; newFieldState.error = error; break; } } const newForm = {...form, [fieldName]: newFieldState}; const formValid = Object.values(newForm).every(f => f.valid); this.setState({ form: newForm, formValid }); } render () { const {form, formValid} = this.state; return <Comp {...this.props} form={form} formValid={formValid} onFormChange={this.handleValueChange}/> } } return FormComponent; }}export default formProvider;
formProvider的第一个参数fields是一个对象,其结构为:
// 表示表单中有name、age、gender3个字段const fields = { name: { defaultValue: '', rules: [ { // pattern用于对值进行校验,可以为方法或一个RegExp对象 // 若方法的返回值为一个真值或RegExp.test(value)返回true则校验通过 pattern: function (value) { return value.length > 0; }, // 每个pattern对应一个error信息 error: '请输入用户名' }, { pattern: /^.{1,4}$/, error: '用户名最多4个字符' } ] }, age: {...}, gender: {...}}
然后UserAdd.js
就可以改成这个样子了:
import React from 'react';import formProvider from '../utils/formProvider';class UserAdd extends React.Component { handleSubmit (e) { e.preventDefault(); const {form: {name, age, gender}, formValid} = this.props; if (!formValid) { alert('请填写正确的信息后重试'); return; } fetch('http://localhost:3000/user', { method: 'post', body: JSON.stringify({ name: name.value, age: age.value, gender: gender.value }), headers: { 'Content-Type': 'application/json' } }) .then((res) => res.json()) .then((res) => { if (res.id) { alert('添加用户成功'); } else { alert('添加失败'); } }) .catch((err) => console.error(err)); } render () { const {form: {name, age, gender}, onFormChange} = this.props; return ( <div> <header> <h1>添加用户</h1> </header> <main> <form onSubmit={(e) => this.handleSubmit(e)}> <label>用户名:</label> <input type="text" value={name.value} onChange={(e) => onFormChange('name', e.target.value)} /> {!name.valid && <span>{name.error}</span>} <br/> <label>年龄:</label> <input type="number" value={age.value || ''} onChange={(e) => onFormChange('age', +e.target.value)} /> {!age.valid && <span>{age.error}</span>} <br/> <label>性别:</label> <select value={gender.value} onChange={(e) => onFormChange('gender', e.target.value)} > <option value="">请选择</option> <option value="male">男</option> <option value="female">女</option> </select> {!gender.valid && <span>{gender.error}</span>} <br/> <br/> <input type="submit" value="提交"/> </form> </main> </div> ); }}UserAdd = formProvider({ name: { defaultValue: '', rules: [ { pattern: function (value) { return value.length > 0; }, error: '请输入用户名' }, { pattern: /^.{1,4}$/, error: '用户名最多4个字符' } ] }, age: { defaultValue: 0, rules: [ { pattern: function (value) { return value >= 1 && value <= 100; }, error: '请输入1~100的年龄' } ] }, gender: { defaultValue: '', rules: [ { pattern: function (value) { return !!value; }, error: '请选择性别' } ] }})(UserAdd);export default UserAdd;
表单控件组件
上面我们抽离了表单的状态的维护和更新逻辑,但这并不够完美。
在UserAdd.js
里的render方法中,我们可以看到还存在着一些重复的代码:
... <label>用户名:</label> <input type="text" value={name.value} onChange={(e) => onFormChange('name', e.target.value)} /> {!name.valid && <span>{name.error}</span>} <br/> <label>年龄:</label> <input type="number" value={age.value || ''} onChange={(e) => onFormChange('age', +e.target.value)} /> {!age.valid && <span>{age.error}</span>} <br/> <label>性别:</label> <select value={gender.value} onChange={(e) => onFormChange('gender', e.target.value)} > <option value="">请选择</option> <option value="male">男</option> <option value="female">女</option> </select> {!gender.valid && <span>{gender.error}</span>} <br/> ...
每一个表单控件都包含一个label、一个具体的控件元素、一个根据valid来控制显示的span元素。
我们可以将其封装成一个FormItem组件,新建/src/components
目录和FormItem.js
文件,写入以下代码:
import React from 'react';class FormItem extends React.Component { render () { const {label, children, valid, error} = this.props; return ( <div> <label>{label}</label> {children} {!valid && <span>{error}</span>} </div> ); }}export default FormItem;
在UserAdd.js
中使用FormItem组件:
import React from 'react';import FormItem from '../components/FormItem';import formProvider from '../utils/formProvider';class UserAdd extends React.Component { ... render () { const {form: {name, age, gender}, onFormChange} = this.props; return ( <div> <header> <h1>添加用户</h1> </header> <main> <form onSubmit={(e) => this.handleSubmit(e)}> <FormItem label="用户名:" valid={name.valid} error={name.error}> <input type="text" value={name.value} onChange={(e) => onFormChange('name', e.target.value)} /> </FormItem> <FormItem label="年龄:" valid={age.valid} error={age.error}> <input type="number" value={age.value || ''} onChange={(e) => onFormChange('age', +e.target.value)} /> </FormItem> <FormItem label="性别:" valid={gender.valid} error={gender.error}> <select value={gender.value} onChange={(e) => onFormChange('gender', e.target.value)} > <option value="">请选择</option> <option value="male">男</option> <option value="female">女</option> </select> </FormItem> <br/> <input type="submit" value="提交"/> </form> </main> </div> ); }}UserAdd = formProvider({...})(UserAdd);export default UserAdd;
这下看起来就清爽多了~
- 【React全家桶入门之五】组件化表单/表单控件
- 【React全家桶入门之四】加入表单验证
- 【React全家桶入门之四】加入表单验证
- 【React全家桶入门之七】提取布局组件
- 【React全家桶入门之十一】引入AntDesign组件库
- 【React全家桶入门之十一】引入AntDesign组件库
- React全家桶之ES6(五)
- React入门笔记(五) — 表单详解
- React的表单组件
- react实现表单组件
- React中的表单组件
- 浅谈React表单组件
- [React网络整理]React之表单组件的学习笔记
- 【React全家桶入门之二】项目搭建
- 【React全家桶入门之三】基本的用户添加
- 【React全家桶入门之六】渲染用户列表
- 【React全家桶入门之CODE】项目代码与使用方法
- 【React全家桶入门之八】用户编辑与删除
- Docker启动一个Centos镜像
- python 列表和词典
- 1081. Rational Sum (20) PAT甲级
- 【BZOJ 3940】[Usaco2015 Feb]Censoring AC自动机
- 新安装的Centos 7系统怎么将网卡名称改为eth0
- 【React全家桶入门之五】组件化表单/表单控件
- 子串和
- Kubernetes1.2新功能介绍:DaemonSet
- 雅礼集训酱油记(2017-01-11~2017-01-23)
- Java中synchronized与lock的区别
- c#89课的主要内容
- 动态规划—取数字问题
- socketapi-listenaccept
- Android FrameLayout 帧布局