高阶组件:formProvider

高阶组件就是返回组件的组件(函数)

为什么要通过一个组件去返回另一个组件?

使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能。

我们现在已经有了带有表单校验功能的添加用户的表单,这里的表单有3个字段:name、age、gender,并且每个字段都有它自己的校验规则和对应的错误信息。

要做一个添加图书的功能,图书的表单有name、price、owner_id三个字段,一样地,每个字段有它自己的校验规则和错误信息。

仔细想想,每当我们需要写一个表单的时候,都需要有一个地方来保存表单字段的值(state),有一个函数来处理表单值的更新和校验(handleValueChange),这些东西我们可以用高阶组件来封装。

而添加用户的表单和添加图书的表单之间的不同之处仅仅是表单字段以及字段的默认值、校验规则和错误信息

那么我们的高阶组件模型就出来了:

  1. function formProvider (fields) {
  2. return function (Comp) {
  3. constructor (props) {
  4. super(props);
  5. this.state = {
  6. form: {...},
  7. formValid: false // 加了一个formValid用来保存整个表单的校验状态
  8. };
  9. }
  10. handleValueChange (field, value) {...}
  11. class FormComponent extends React.Component {
  12. render () {
  13. const {form, formValid} = this.state;
  14. return (
  15. <Comp {...this.props} form={form} formValid={formValid} onFormChange={this.handleValueChange}/>
  16. );
  17. }
  18. }
  19.  
  20. return FormComponent;
  21. }
  22. }

formProvider接收一个fields参数,并返回一个函数,这个函数接收一个组件作为参数并返回一个组件,所以它的用法是这样的:

  1. UserAdd = formProvider(fields)(UserAdd);

经过formProvider处理后的UserAdd组件会得到额外的props:

  • form
  • formValid
  • onFormChange

/src下新建一个目录utils,新建formProvider.js文件,写入具体的代码实现:

  1. /**
  2. * 高阶组件 formProvider
  3. * 返回组件的组件(函数)
  4. * 使用高阶组件可以在不修改原组件代码的情况下,修改原组件的行为或增强功能
  5. */
  6. import React from 'react';
  7.  
  8. function formProvider (fields) { // fields 对象
  9. return function(Comp) { // Comp
  10. /**
  11. * 定义常量
  12. * 初始表单状态
  13. */
  14. const initialFormState = {};
  15. // 循环
  16. for(const key in fields){
  17. initialFormState[key] = {
  18. value: fields[key].defaultValue,
  19. error: ''
  20. };
  21. }
  22.  
  23. // 创建组件
  24. class FormComponent extends React.Component {
  25. // 构造器
  26. constructor(props) {
  27. super(props);
  28. // 定义初始状态
  29. this.state = {
  30. form: initialFormState,
  31. formValid: false // 加了一个formValid用来保存整个表单的校验状态
  32. };
  33. // 绑定this
  34. this.handleValueChange = this.handleValueChange.bind(this);
  35. }
  36. // 输入框改变事件
  37. handleValueChange(fieldName, value){
  38. // 定义常量
  39. const { form } = this.state;
  40.  
  41. const newFieldState = {value, valid: true, error: ''};
  42.  
  43. const fieldRules = fields[fieldName].rules;
  44. // 循环
  45. for(let i=0; i<fieldRules.length; i++){
  46. const {pattern, error} = fieldRules[i];
  47. let valid = false;
  48. if(typeof pattern === 'function'){
  49. valid = pattern(value);
  50. }else{
  51. valid = pattern.test(value);
  52. }
  53.  
  54. if(!valid){
  55. newFieldState.valid = false;
  56. newFieldState.error = error;
  57. break;
  58. }
  59. }
  60. /**
  61. * ... 扩展运算符
  62. * 将一个数组转为用逗号分隔的参数序列
  63. */
  64. const newForm = {...form, [fieldName]: newFieldState};
  65. /**
  66. * every
  67. * 对数组中的每个元素都执行一次指定的函数,直到此函数返回 false
  68. * 如果发现这个元素,every 将返回 false
  69. * 如果回调函数对每个元素执行后都返回 true,every 将返回 true
  70. */
  71. const formValid = Object.values(newForm).every(f => f.valid);
  72. // 设置状态
  73. this.setState({
  74. form: newForm,
  75. formValid
  76. });
  77. }
  78. render(){
  79. const { form, formValid } = this.state;
  80. return (
  81. <Comp
  82. {...this.props}
  83. form={form}
  84. formValid={formValid}
  85. onFormChange={this.handleValueChange} />
  86. );
  87. }
  88. }
  89. // 返回组件
  90. return FormComponent;
  91. }
  92. }
  93.  
  94. export default formProvider;

formProvider的第一个参数fields是一个对象,其结构为:

  1. // 表示表单中有name、age、gender3个字段
  2. const fields = {
  3. name: {
  4. defaultValue: '',
  5. rules: [
  6. {
  7. // pattern用于对值进行校验,可以为方法或一个RegExp对象
  8. // 若方法的返回值为一个真值或RegExp.test(value)返回true则校验通过
  9. pattern: function (value) {
  10. return value.length > 0;
  11. },
  12. // 每个pattern对应一个error信息
  13. error: '请输入用户名'
  14. },
  15. {
  16. pattern: /^.{1,4}$/,
  17. error: '用户名最多4个字符'
  18. }
  19. ]
  20. },
  21. age: {...},
  22. gender: {...}
  23. }

然后UserAdd.js就可以改成这个样子了:

  1. import React from 'react';
  2. // 高阶组件 formProvider表单验证
  3. import formProvider from '../utils/formProvider';
  4.  
  5. // 添加用户组件
  6. class UserAdd extends React.Component {
  7. // 按钮提交事件
  8. handleSubmit(e){
  9. // 阻止表单submit事件自动跳转页面的动作
  10. e.preventDefault();
  11. // 定义常量
  12. const { form: { name, age, gender }, formValid} = this.props; // 组件传值
  13. // 验证
  14. if(!formValid){
  15. alert('请填写正确的信息后重试');
  16. return;
  17. }
  18. // 发送请求
  19. fetch('http://localhost:8000/user', {
  20. method: 'post',
  21. // 使用fetch提交的json数据需要使用JSON.stringify转换为字符串
  22. body: JSON.stringify({
  23. name: name.value,
  24. age: age.value,
  25. gender: gender.value
  26. }),
  27. headers: {
  28. 'Content-Type': 'application/json'
  29. }
  30. })
  31. // 强制回调的数据格式为json
  32. .then((res) => res.json())
  33. // 成功的回调
  34. .then((res) => {
  35. // 当添加成功时,返回的json对象中应包含一个有效的id字段
  36. // 所以可以使用res.id来判断添加是否成功
  37. if(res.id){
  38. alert('添加用户成功!');
  39. }else{
  40. alert('添加用户失败!');
  41. }
  42. })
  43. // 失败的回调
  44. .catch((err) => console.error(err));
  45. }
  46.  
  47. render() {
  48. // 定义常量
  49. const {form: {name, age, gender}, onFormChange} = this.props;
  50. return (
  51. <div>
  52. <header>
  53. <div>添加用户</div>
  54. </header>
  55.  
  56. <main>
  57. <form onSubmit={(e) => this.handleSubmit(e)}>
  58. <label>用户名:</label>
  59. <input
  60. type="text"
  61. value={name.value}
  62. onChange={(e) => onFormChange('name', e.target.value)} />
  63. {!name.valid && <span>{name.error}</span>}
  64. <br />
  65. <label>年龄:</label>
  66. <input
  67. type="number"
  68. value={age.value || ''}
  69. onChange={(e) => onFormChange('age', e.target.value)} />
  70. {!age.valid && <span>{age.error}</span>}
  71. <br />
  72. <label>性别:</label>
  73. <select
  74. value={gender.value}
  75. onChange={(e) => onFormChange('gender', e.target.value)}>
  76. <option value="">请选择</option>
  77. <option value="male">男</option>
  78. <option value="female">女</option>
  79. </select>
  80. {!gender.valid && <span>{gender.error}</span>}
  81. <br />
  82. <br />
  83. <input type="submit" value="提交" />
  84. </form>
  85. </main>
  86. </div>
  87. );
  88. }
  89. }
  90.  
  91. // 实例化
  92. UserAdd = formProvider({ // field 对象
  93. // 姓名
  94. name: {
  95. defaultValue: '',
  96. rules: [
  97. {
  98. pattern: function (value) {
  99. return value.length > 0;
  100. },
  101. error: '请输入用户名'
  102. },
  103. {
  104. pattern: /^.{1,4}$/,
  105. error: '用户名最多4个字符'
  106. }
  107. ]
  108. },
  109. // 年龄
  110. age: {
  111. defaultValue: 0,
  112. rules: [
  113. {
  114. pattern: function(value){
  115. return value >= 1 && value <= 100;
  116. },
  117. error: '请输入1~100的年龄'
  118. }
  119. ]
  120. },
  121. // 性别
  122. gender: {
  123. defaultValue: '',
  124. rules: [
  125. {
  126. pattern: function(value) {
  127. return !!value;
  128. },
  129. error: '请选择性别'
  130. }
  131. ]
  132. }
  133. })(UserAdd);
  134.  
  135. export default UserAdd;

表单控件组件

上面我们抽离了表单的状态的维护和更新逻辑,但这并不够完美。

UserAdd.js里的render方法中,我们可以看到还存在着一些重复的代码:

  1. ...
  2. <label>用户名:</label>
  3. <input
  4. type="text"
  5. value={name.value}
  6. onChange={(e) => onFormChange('name', e.target.value)}
  7. />
  8. {!name.valid && <span>{name.error}</span>}
  9. <br/>
  10. <label>年龄:</label>
  11. <input
  12. type="number"
  13. value={age.value || ''}
  14. onChange={(e) => onFormChange('age', +e.target.value)}
  15. />
  16. {!age.valid && <span>{age.error}</span>}
  17. <br/>
  18. <label>性别:</label>
  19. <select
  20. value={gender.value}
  21. onChange={(e) => onFormChange('gender', e.target.value)}
  22. >
  23. <option value="">请选择</option>
  24. <option value="male">男</option>
  25. <option value="female">女</option>
  26. </select>
  27. {!gender.valid && <span>{gender.error}</span>}
  28. <br/>
  29. ...

每一个表单控件都包含一个label、一个具体的控件元素、一个根据valid来控制显示的span元素。

我们可以将其封装成一个FormItem组件,新建/src/components目录和FormItem.js文件,写入以下代码:

  1. import React from 'react';
  2.  
  3. class FormItem extends React.Component {
  4. render () {
  5. const {label, children, valid, error} = this.props;
  6. return (
  7. <div>
  8. <label>{label}</label>
  9. {children}
  10. {!valid && <span>{error}</span>}
  11. </div>
  12. );
  13. }
  14. }
  15.  
  16. export default FormItem;

UserAdd.js中使用FormItem组件:

  1. import React from 'react';
  2. import FormItem from '../components/FormItem';
  3. // 高阶组件 formProvider表单验证
  4. import formProvider from '../utils/formProvider';
  5.  
  6. // 添加用户组件
  7. class UserAdd extends React.Component {
  8. // 按钮提交事件
  9. handleSubmit(e){
  10. // 阻止表单submit事件自动跳转页面的动作
  11. e.preventDefault();
  12. // 定义常量
  13. const { form: { name, age, gender }, formValid} = this.props; // 组件传值
  14. // 验证
  15. if(!formValid){
  16. alert('请填写正确的信息后重试');
  17. return;
  18. }
  19. // 发送请求
  20. fetch('http://localhost:8000/user', {
  21. method: 'post',
  22. // 使用fetch提交的json数据需要使用JSON.stringify转换为字符串
  23. body: JSON.stringify({
  24. name: name.value,
  25. age: age.value,
  26. gender: gender.value
  27. }),
  28. headers: {
  29. 'Content-Type': 'application/json'
  30. }
  31. })
  32. // 强制回调的数据格式为json
  33. .then((res) => res.json())
  34. // 成功的回调
  35. .then((res) => {
  36. // 当添加成功时,返回的json对象中应包含一个有效的id字段
  37. // 所以可以使用res.id来判断添加是否成功
  38. if(res.id){
  39. alert('添加用户成功!');
  40. }else{
  41. alert('添加用户失败!');
  42. }
  43. })
  44. // 失败的回调
  45. .catch((err) => console.error(err));
  46. }
  47.  
  48. render() {
  49. // 定义常量
  50. const {form: {name, age, gender}, onFormChange} = this.props;
  51. return (
  52. <div>
  53. <header>
  54. <div>添加用户</div>
  55. </header>
  56.  
  57. <main>
  58. <form onSubmit={(e) => this.handleSubmit(e)}>
  59. <FormItem label="用户名:" valid={name.valid} error={name.error}>
  60. <input
  61. type="text"
  62. value={name.value}
  63. onChange={(e) => onFormChange('name', e.target.value)}/>
  64. </FormItem>
  65.  
  66. <FormItem label="年龄:" valid={age.valid} error={age.error}>
  67. <input
  68. type="number"
  69. value={age.value || ''}
  70. onChange={(e) => onFormChange('age', e.target.value)}/>
  71. </FormItem>
  72.  
  73. <FormItem label="性别:" valid={gender.valid} error={gender.error}>
  74. <select
  75. value={gender.value}
  76. onChange={(e) => onFormChange('gender', e.target.value)}>
  77. <option value="">请选择</option>
  78. <option value="male">男</option>
  79. <option value="female">女</option>
  80. </select>
  81. </FormItem>
  82. <br />
  83. <input type="submit" value="提交" />
  84. </form>
  85. </main>
  86. </div>
  87. );
  88. }
  89. }
  90.  
  91. // 实例化
  92. UserAdd = formProvider({ // field 对象
  93. // 姓名
  94. name: {
  95. defaultValue: '',
  96. rules: [
  97. {
  98. pattern: function (value) {
  99. return value.length > 0;
  100. },
  101. error: '请输入用户名'
  102. },
  103. {
  104. pattern: /^.{1,4}$/,
  105. error: '用户名最多4个字符'
  106. }
  107. ]
  108. },
  109. // 年龄
  110. age: {
  111. defaultValue: 0,
  112. rules: [
  113. {
  114. pattern: function(value){
  115. return value >= 1 && value <= 100;
  116. },
  117. error: '请输入1~100的年龄'
  118. }
  119. ]
  120. },
  121. // 性别
  122. gender: {
  123. defaultValue: '',
  124. rules: [
  125. {
  126. pattern: function(value) {
  127. return !!value;
  128. },
  129. error: '请选择性别'
  130. }
  131. ]
  132. }
  133. })(UserAdd);
  134.  
  135. 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. swift 与 NSObject

    以NSObject为基类,只是为了提供Objective-C API的使用入口: 经由@object修改的对象,是这些api的参量. NSObject是swift与oc特有机制沟通的桥梁. Subcl ...

  2. Java处理ZIP文件的解决方案——Zip4J(不解压直接通过InputStream形式读取其中的文件,解决中文乱码)

    一.JDK内置操作Zip文件其实,在JDK中已经存在操作ZIP的工具类:ZipInputStream. 基本使用: public static Map<String, String> re ...

  3. 解决vue项目eslint校验 Do not use 'new' for side effects 的两种方法

    import Vue from 'vue' import App from './App.vue' import router from './router' new Vue({ el: '#app' ...

  4. 【传智播客】Libevent学习笔记(一):简介和安装

    目录 00. 目录 01. libevent简介 02. Libevent的好处 03. Libevent的安装和测试 04. Libevent成功案例 00. 目录 @ 01. libevent简介 ...

  5. buf.compare()

    buf.compare(otherBuffer) otherBuffer {Buffer} 返回:{Number} 比较两个 Buffer 实例,无论 buf 在排序上靠前.靠后甚至与 otherBu ...

  6. Python之粘包

    Python之粘包 让我们基于tcp先制作一个远程执行命令的程序(1:执行错误命令 2:执行ls 3:执行ifconfig) 注意注意注意: res=subprocess.Popen(cmd.deco ...

  7. PAT 1077. 互评成绩计算

    PAT 1077. 互评成绩计算 在浙大的计算机专业课中,经常有互评分组报告这个环节.一个组上台介绍自己的工作,其他组在台下为其表现评分.最后这个组的互评成绩是这样计算的:所有其他组的评分中,去掉一个 ...

  8. java ssm框架 mapper文件里的#符号和$符号的区别

    Java SSM框架里面,Mapper.xml文件 (一)#符号生成的sql语句是作为传参的 <!-- 获得数据列表(包括课程相关信息) --> <select id="G ...

  9. npm 发包

    前几天封装了公用的locaStorage组件,当然封装后需要发布npm官网,于是摸索了一番终于搞定了,总结下来希望对大家有所帮助 npm安装的package一般支持下面几大类: 本地包 url远程包 ...

  10. 【Codeforces 1117C】Magic Ship

    [链接] 我是链接,点我呀:) [题意] 题意 [题解] 我们可以把这个行船的过程分解成两个过程 1.船经过时间t被风吹到了某个地方 2.船用这t时间尝试到达终点(x2,y2) 会发现如果时间t能最终 ...