高阶组件: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文件,写入具体的代码实现:

/**
* 高阶组件 formProvider
* 返回组件的组件(函数)
* 使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能
*/
import React from 'react'; function formProvider (fields) { // fields 对象
return function(Comp) { // 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 // 加了一个formValid用来保存整个表单的校验状态
};
// 绑定this
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};
/**
* every
* 对数组中的每个元素都执行一次指定的函数,直到此函数返回 false
* 如果发现这个元素,every 将返回 false
* 如果回调函数对每个元素执行后都返回 true,every 将返回 true
*/
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';
// 高阶组件 formProvider表单验证
import formProvider from '../utils/formProvider'; // 添加用户组件
class UserAdd extends React.Component {
// 按钮提交事件
handleSubmit(e){
// 阻止表单submit事件自动跳转页面的动作
e.preventDefault();
// 定义常量
const { form: { name, age, gender }, formValid} = this.props; // 组件传值
// 验证
if(!formValid){
alert('请填写正确的信息后重试');
return;
}
// 发送请求
fetch('http://localhost:8000/user', {
method: 'post',
// 使用fetch提交的json数据需要使用JSON.stringify转换为字符串
body: JSON.stringify({
name: name.value,
age: age.value,
gender: gender.value
}),
headers: {
'Content-Type': 'application/json'
}
})
// 强制回调的数据格式为json
.then((res) => res.json())
// 成功的回调
.then((res) => {
// 当添加成功时,返回的json对象中应包含一个有效的id字段
// 所以可以使用res.id来判断添加是否成功
if(res.id){
alert('添加用户成功!');
}else{
alert('添加用户失败!');
}
})
// 失败的回调
.catch((err) => console.error(err));
} render() {
// 定义常量
const {form: {name, age, gender}, onFormChange} = this.props;
return (
<div>
<header>
<div>添加用户</div>
</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({ // field 对象
// 姓名
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';
// 高阶组件 formProvider表单验证
import formProvider from '../utils/formProvider'; // 添加用户组件
class UserAdd extends React.Component {
// 按钮提交事件
handleSubmit(e){
// 阻止表单submit事件自动跳转页面的动作
e.preventDefault();
// 定义常量
const { form: { name, age, gender }, formValid} = this.props; // 组件传值
// 验证
if(!formValid){
alert('请填写正确的信息后重试');
return;
}
// 发送请求
fetch('http://localhost:8000/user', {
method: 'post',
// 使用fetch提交的json数据需要使用JSON.stringify转换为字符串
body: JSON.stringify({
name: name.value,
age: age.value,
gender: gender.value
}),
headers: {
'Content-Type': 'application/json'
}
})
// 强制回调的数据格式为json
.then((res) => res.json())
// 成功的回调
.then((res) => {
// 当添加成功时,返回的json对象中应包含一个有效的id字段
// 所以可以使用res.id来判断添加是否成功
if(res.id){
alert('添加用户成功!');
}else{
alert('添加用户失败!');
}
})
// 失败的回调
.catch((err) => console.error(err));
} render() {
// 定义常量
const {form: {name, age, gender}, onFormChange} = this.props;
return (
<div>
<header>
<div>添加用户</div>
</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({ // field 对象
// 姓名
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;

项目结构:

react 项目实战(四)组件化表单/表单控件 高阶组件的更多相关文章

  1. react第七单元(组件的高级用法-组件的组合(children的用法)-高阶组件-封装组件)

    第七单元(组件的高级用法-组件的组合(children的用法)-高阶组件-封装组件) #受控组件 简而言之,就是受到状态state控制的表单,表单的值改变则state值也改变,受控组件必须要搭配onc ...

  2. React文档(二十四)高阶组件

    高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...

  3. 聊聊React高阶组件(Higher-Order Components)

    使用 react已经有不短的时间了,最近看到关于 react高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡.一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低 ...

  4. React 精要面试题讲解(五) 高阶组件真解

    说明与目录 在学习本章内容之前,最好是具备react中'插槽(children)'及'组合与继承' 这两点的知识积累. 详情请参照React 精要面试题讲解(四) 组合与继承不得不说的秘密. 哦不好意 ...

  5. React躬行记(10)——高阶组件

    高阶组件(High Order Component,简称HOC)不是一个真的组件,而是一个没有副作用的纯函数,以组件作为参数,返回一个功能增强的新组件,在很多第三方库(例如Redux.Relay等)中 ...

  6. 利用 React 高阶组件实现一个面包屑导航

    什么是 React 高阶组件 React 高阶组件就是以高阶函数的方式包裹需要修饰的 React 组件,并返回处理完成后的 React 组件.React 高阶组件在 React 生态中使用的非常频繁, ...

  7. react高阶组件

    高阶组件 为了提高组件复用性,在react中就有了HOC(Higher-Order Component)的概念.所谓的高阶组件,其本质依旧是组件,只是它返回另外一个组件,产生新的组件可以对属性进行包装 ...

  8. React 高阶组件浅析

    高阶组件的这种写法的诞生来自于社区的实践,目的是解决一些交叉问题(Cross-Cutting Concerns).而最早时候 React 官方给出的解决方案是使用 mixin .而 React 也在官 ...

  9. react 高阶组件的 理解和应用

    高阶组件是什么东西 简单的理解是:一个包装了另一个基础组件的组件.(相对高阶组件来说,我习惯把被包装的组件称为基础组件) 注意:这里说的是包装,可以理解成包裹和组装: 具体的是高阶组件的两种形式吧: ...

随机推荐

  1. 几个net命令

    A.显示当前工作组服务器列表 net view,当不带选项使用本命令时,它就会显示当前域或网络上的计算机上的列表.  比如:查看这个IP上的共享资源,就可以  C:\\>net view 192 ...

  2. arp - Linux的ARP核心模块

    描述 这个核心协议模块实现RFC826中定义的 Address Resolution Protocol [译注:即TCP/IP的第三层到第一层的地址转换协议],用于在直接相连的网络中换第二层硬件地址和 ...

  3. 由于未清除缓存引发的bug

    在写页面的时候,首先引入了本地react.js和react-dom.js 16版本(cjs)的文件,出现如下错误 发现bug后,将本地的react.js和react-dom.js文件改成16.2(um ...

  4. 解决android的键盘弹出时,html页面的高度被压缩

    如果元素的高度是用100%表示,那么,安卓的键盘弹出时,高度会发生变化,导致布局混乱,所以最好给高度设置像素高度 $("html,body").height(window.inne ...

  5. 11-2 numpy/pandas/matplotlib模块

    目录 numpy模块 一维数组 二维数组 列表list和numpy的区别 获取多维数组的行和列 多维数组的索引 高级功能 多维数组的合并 通过函数方法创建多维数组 矩阵的运算 求最大值最小值 nump ...

  6. hdfs深入:10、hdfs的javaAPI操作

    /** * 递归遍历hdfs中所有的文件路径 */ @Test public void getAllHdfsFilePath() throws URISyntaxException, IOExcept ...

  7. 启动myeclipse弹窗Please allow Subclipse team to receive anonymous usage statistics for this Eclipse intance

    Please allow Subclipse team to receive anonymous usage statistics for this Eclipse intance(翻译:请允许Sub ...

  8. jQuery-图片的放大镜显示效果方法封装

    (function($){ $.fn.jqueryzoom = function(options){ var settings = { xzoom: 200, //zoomed width defau ...

  9. crm项目之stark组件前戏(二)

    stark组件的设计主要来源于django中admin的功能,在django admin中只需要将模型表进行注册,就可以在页面对该表进行curd的动作,那么django admin是如何做的呢? 在d ...

  10. JS 去除字符串空格

    $.trim()是jQuery提供的函数,用于去掉字符串首尾的空白字符. "abc 123 def".replace(/\s/g, "") 去除所有的空格