高阶组件: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. ubuntu下pycharm无法使用pip安装python包的修复方案

    1. 在pycharm 中安装python包会报错“pycharm ModuleNotFoundError: No module named 'distutils.core'”: 2. 可能原因:in ...

  2. POI原生导入读取EXCEL

    好久没用 最近项目有冲突 所以又用到了这个 谁知道以后还会不会用 先记下来吧 直接扔项目里 调方法就OK 了. 记录一下....不想再写类似这样的东西了 import org.apache.poi.h ...

  3. Vue beaforeCreate时获取data中的数据

    异步获取即:通过    $this.$nextTick或者settimeout,这连dom都可以拿出来 beforeCreate() { this.$nextTick(function() { con ...

  4. asp 读取 另外一个带参数的asp文件(服务器不支持!放弃吧!骚年!)

    (服务器不支持!放弃吧!骚年!) 主要作用是为了分离数据库,灵感是这样的:收到json影响,把asp里的数据,用一个页面输出,然后用另外一个页面读取,这样就不用有数据库位置的烦恼了 代码 网上有很多, ...

  5. caffe和图像一些基础知识

    1.卷积层的参数放置在convoluytion_param{}中,pad默认是0,stride默认是1,如果在convoluytion_param中没有写pad = 什么,或者stride = 什么, ...

  6. webpack遇见的坑:Please install 'webpack-cli' in addition to webpack itself to use the CLI.

    webpack-cli没被找到: 在webpack4.0之后,需要全局安装webpack-cli, 在全局安装webpack之后,cnpm i webpack-cli -g 在局部使用webpack时 ...

  7. JavaSE-05 数组

    学习要点 数组的基本用法 数组的典型应用 数组相关概念 问题 Java考试结束后,老师给小强分配了一项任务,让他计算全班(30人)的平均分,按照目前的知识结构,如何实现? 问题分析 数组 定义:数组是 ...

  8. note for git

    1.download https://git-for-windows.github.io/ 2.command add file to git: git add filename & git ...

  9. jquery data属性 attr vs data

    html5的自定义data属性相信大家都不会陌生,有了它你可以绑定所需的数据到指定元素上.然后通过jquery设置.获取数据,简直开心的不行啊.想到设置.获取元素属性值,大家一定首先想到了jquery ...

  10. 转:Centos7安装zabbix3.4超详细步骤解析

    安装前准备: 1.1 安装依赖包: yum -y install wget net-snmp-devel OpenIPMI-devel httpd openssl-devel java lrzsz f ...