React 高阶组件HOC用法归纳


Posted in Javascript onJune 13, 2021

一句话介绍HOC

何为高阶组件(HOC),根据官方文档的解释:“高阶组件是react中复用组件逻辑的一项高级技术。它不属于react API的组成部分,它是从react自身组合性质中抽离出来的一种模式。具体来说,高阶组件是函数,它接受一个组件作为参数,然后返回一个新的组件

使用场景

将几个功能相似的组件里面的方法和react特性(如生命周期里面的副作用)提取到HOC中,然后向HOC传入需要封装的组件。最后将公用的方法传给组件。

优势

使代码简洁优雅、代码量更少

HOC(高阶组件)

/*
  HOC(高阶组件): 接收一个组件,返回包装后的组件(增强组件)
    - 不是React API
    - 是一种设计模式,类似于装饰器模式
    - ≈ Mixin && > Minxin

  const 包装后的组件 = 高阶组件(被包装的组件);
  // e.g. const Wrapper = withRouter(NavBar);


  高阶组件会把所有接收到的props,传递给被包装的组件(透传)
  ref 和 key 类似,不是一个prop,所以不会透传,ref会绑定到外层的包装容器上 | 解决方法可以参考下面的 <<处理ref>>
* */

怎样包装组件?

/*
  怎样包装组件?

  第一种: 普通包装
    export时就包装
      import React from 'react';
      import Hoc from './Hoc';

      class Header extends React.Component {
        render() {
          return <span>{ this.props.count }</span>
        }
      };

      export default Hoc(Header);

    ==========

    import后再包装:
      import Header from './header';
      import Hoc from './Hoc';

      const EnhanceHeader = Hoc(Header);

      const Home = () => {
        return (
          <div>
             <EnhanceHeader count={1} />
          </div>
        )
      }

  第二种: 装饰器包装,只能在类组件中使用
    import React from 'react';
    import Hoc from './Hoc';

    @Hoc
    export default class Header extends React.Component {
      render() {
        return <span>{ this.props.count }</span>
      }
    };

    =======

    @Hoc
    class Header extends React.Component {
      render() {
        return <span>{ this.props.count }</span>
      }
    };

    export default Header;
* */

定义一个简单的HOC

/*
  定义一个简单的HOC,接收一个组件,返回一个组件

  import React from 'react';

  // 返回类组件
  export default function Hoc(WrappedComponent) {
    /*
      return class extends React.Component {}
        - 在 React Developer Tools 中展示的名字是 Component

      return class Wrapper extends React.Component {}
        - 在 React Developer Tools 中展示的名字是 Wrapper
    *\
    return class extends React.Component {
      render() {
        return <WrappedComponent {...this.props} />;
      }
    };
  }

  // 返回函数式组件
  export default function Hoc(WrappedComponent) {
    /*
      return function(props) {}
        - 在 React Developer Tools 中展示的名字是 Anonymous

      return function Wrapper(props) {}
        - 在 React Developer Tools 中展示的名字是 Wrapper
    *\
    return function Wrapper(props) {
      return <WrappedComponent {...props} />;
    };
  }
* */

给Hoc传参

/*
  给Hoc传参

   // Hoc,可以接受任意参数
    export default function Hoc(WrappedComponent, title, user, data) {
      return class Wrapper extends React.Component {
        render() {
          return <WrappedComponent {...this.props} />
        }
      };
    };

    // 包装时传参
    const EnhanceHeader = Hoc(Header, 'title', { name: '霖'}, [1, 2, 3]);
* */

Hoc嵌套

/*
  Hoc嵌套,函数柯里化的原理

  // Hoc1: 给组件添加title属性
  export default function Hoc1(WrappedComponent, title) {
    return class extends React.Component {
      render() {
        return <WrappedComponent title={title} {...this.props} />
      }
    };
  };

  // Hoc2: 修改组件的显示内容
  export default function Hoc2(WrappedComponent, content) {
    return class extends WrappedComponent { // 这里用了反向继承
      render() {
        const elementTree = super.render(); // React用Js对象来模拟Dom树结构,可以通过修改Js对象的属性来操纵数据

        console.log(elementTree); // 不太了解里面的结构可以打印出来 + 官网cloneElement() 了解一下

        const newElementTree = React.cloneElement(elementTree, { children: `你的内容已被劫持: ${content}` });

        return newElementTree;
      }
    };
  };

  // 被包裹的组件
  export default class Header extends React.Component {
    render() {
      const { title } = this.props;

      return (
          <span title={title}>
            默认内容
          </span>
        )
    }
  };

  // 使用
  import Hoc1 from './Hoc1';
  import Hoc2 from './Hoc2';

  /*
    包装过程
    1. const Wrapper = Hoc2(Header, '内容');
    2. Hoc1(Wrapper)
  **
  const EnhanceHeader = Hoc1(Hoc2(Header, '内容'), '标题');

  export default function Home() {
    return (
      <div>
        <EnhanceHeader />
      </div>
    );
  };

* */

处理ref

/*
  处理ref
  e.g. Hoc1(Hoc2(Content))

  <Content ref={myRef} /> 给Content绑定的ref会绑定到Hoc1上,且不会继续向下传递

  第一种方法 React.forwardRef ===============

      在 Hoc1外面 用React.forwardRef()对ref做处理,用props来传递ref
      0. 在高阶组件外面包裹forwardRef,拦截获取ref,增加一个props(xxx={ref}),真实组件通过props.xxx获取
      1. 使用时传 ref={XXXX}  // 和第二种方法不同的地方
      2. 用forwardRef的第二个参数获取 ref
      3. 增加一个新的props,用来向下转发ref  e.g. forwardedRef={ref}
      4. 真实组件中绑定 ref={props.forwardedRef}

      const Home = (props) => {
        const connectRef = useRef(null);

        return (
          <div>
            <Content ref={connectRef} />
          </div>
        );
      };

      // 被包装组件
      const Content = (props) => {
        return (
          <div>
            <input type="password" ref={props.forwardedRef} />
          </div>
        );
      };


      // forwardRef的第二个入参可以接收ref,在Hoc外层对ref做处理
      export default React.forwardRef((props, ref) => {
        const Wrapper = React.memo(Content);  // Hoc

        // forwardRef包裹的是Wrapper
        // 需要在Wrapper中把ref向下传递给真实组件
        // Wrapper中增加一个props属性,把ref对象作为props传给子组件
        return <Wrapper {...props} forwardedRef={ref} />;
      });

  第二种方法 ==========

  0. 使用时就用一个props来保存ref
  1. 使用时传 xxx={ref}  // 和第一种方法的不同点
  2. 真实组件中绑定 ref={props.xxx}

  const Home = (props) => {
    const connectRef = useRef(null);

    return (
      <div>
        <Content forwardedRef={connectRef} />
      </div>
    );
  };

  // 定义高阶组件
  export const Hoc = (WrappedComponent) => {
    class Wrapper extends React.Component {
      render() {
        return <WrappedComponent {...props} />
      }
    }
  }

  // 被包装的组件
  const Content = (props) => {
    return (
      <div>
        <input type="password" ref={props.forwardedRef} />
      </div>
    );
  };

  // 包装过程
  export default Hoc(Content);

* */

使用被包装组件的静态方法

/*
  使用被包装组件的静态方法

  // 被包装组件,增加静态属性和方法
  export default class Header extends React.Component {
    static displayName = 'header';
    static showName = () => {
      console.log(this.displayName);
    };

    render() {
      return <span>header</span>
    }
  };

  // HOC
  export default function Hoc(WrappedComponent) {
    return class Wrapper extends React.Component {
      render() {
        return <WrappedComponent {...this.props} />
      }
    };
  };

  ===========

  // Hoc包装后的组件拿不到静态方法
    import Header from './header';
    import Hoc from './Hoc';

    const EnhanceHeader = Hoc(Header);

    export default function Home() {
      console.log(EnhanceHeader.displayName);   // undefined
      EnhanceHeader.showName();                 // undefined

      return <EnhanceHeader />
    }

  =============

  // 解决方法1:拷贝静态方法到HOC上
    export default function Hoc(WrappedComponent) {
      return class Wrapper extends React.Component {
        static displayName = WrappedComponent.displayName;  // 必须知道被包装组件中有什么静态方法
        static showName = WrappedComponent.showName;

        render() {
          return <WrappedComponent {...this.props} />
        }
      };
    };

  ==============

  // 解决方法2:自动拷贝所有静态属性和方法
    import React from 'react';
    import hoistNonReactStatic from 'hoist-non-react-statics';

    export default function Hoc(WrappedComponent) {

      class Wrapper extends React.Component {
        render() {
          return <WrappedComponent {...this.props} />
        }
      };

      hoistNonReactStatic(Wrapper, WrappedComponent);
      return Wrapper;
    };

  ==============

    // 解决方法3:导出组件时,额外导入静态属性和方法
      class Header extends React.Component {
        render() {
          return <span>header</span>
        }
      };

      const displayName = 'header';

      function showName() {
        console.log(Header.displayName);
      };

      Header.displayName =displayName;
      Header.showName = showName;

      export default Header
      export { displayName, showName }

    // 导入时
      import Header, { displayName, showName } from './header';
      import Hoc from './Hoc';

      const EnhanceHeader = Hoc(Header);

      export default function Home() {
        console.log(displayName);   // header
        showName();                 // header

        return <EnhanceHeader />
      }
* */

拦截传给被包装组件的props,对props进行增删改

/*
  拦截传给被包装组件的props,对props进行增删改
  export default function Hoc(WrappedComponent) {

    return class Wrapper extends React.Component {
      render() {
        // 过滤一些仅在当前Hoc中使用的props,不进行不必要的透传
        const { forMeProps, forOtherProps } = this.props;

        // 在该HOC内部定义,需要注入到被包装组件的额外的属性或方法
        const injectProps = some-state-or-method;         // 通常是state或实例方法

        // 为被包装组件传递上层的props + 额外的props
        return (
          <WrappedComponent
            injectProps={injectProps}    // 传递需要注入的额外props
            {...forOtherProps}           // 透传与后续相关的props
          />
        )
      }
    }
  }

  e.g.
    Hoc接收一个额外的props 'dealUpper',如果为true,将data转换成大写
    dealUpper只在该Hoc中使用,所以没必要传给被包装的组件

  // HOC
  export default function Hoc(WrappedComponent) {
    return class Wrapper extends React.Component {
      render() {
        const { dealUpper, ...forOtherProps } = this.props;
        const { data } = forOtherProps;

        if (dealUpper) {
          Object.assign(forOtherProps, {data: data.toUpperCase()})
        }

        return <WrappedComponent {...forOtherProps} />
      }
    };
  };

  // 导出Hoc包装后的增强组件
  import React from 'react';
  import Hoc from './Hoc1';

  class Header extends React.Component {
    render() {
      console.log(this.props); // { data: 'ABC' }

      return <span>{this.props.data}</span>
    }
  };

  export default Hoc(Header); // 导出包装后的增强组件

  // 导入使用
  import Header from './header';

  const Home = () => {
    return <Header data={'abc'} dealUpper />
  }
* */

用HOC提取一些复杂的公共逻辑,在不同组件中扩展不同的功能

/*
  用HOC提取一些复杂的公共逻辑,在不同组件中扩展不同的功能
  import React from 'react';

  export const Hoc = (WrappedComponent, namespace) => {
    class Wrapper extends React.Component {
      state = {
        data: []
      }

      // 抽离的相同请求方法
      componentDidMount = () => {
        const { dispatch } = this.props;

        dispatch({
          type: `${namespace}/queryData`, // 动态请求不同的store
          payload: {},
          callback: res => {
            if (res) {
              this.setState({
                data: res.data
              })
            }
          }
        })
      }

      render() {
        return <WrappedComponent { ...this.props } data={this.state.data} />
      }
    }
  }

  // 包装A组件
  import Hoc from './Hoc';

  const A = ({ data }) => {
    ... 省略请求数据的逻辑

    return (data.map(item => item));
  }

  export default MyHoc(A, 'a');

  // 包装B组件
  import Hoc from './Hoc';

  const B = ({ data }) => {
    ... 省略请求数据的逻辑

    return (
      <ul>
        {
          data.map((item, index) => {
            return <li key={index}><{item}/li>
          }
        }
      </ul>
    )
  }

   export default Hoc(B, 'b');
* */

让不受控组件变成受控组件

/*
  让不受控组件变成受控组件

  // Hoc组件
  export default function Hoc(WrappedComponent) {
    return class Wrapper extends React.Component {
      state = {
        value: ''
      };

      onChange = (e) => {
        this.setState({
          value: e.target.value
        })
      };

      render() {
        const newProps = {
          value: this.state.value,
          onChange: this.onChange
        };

        return <WrappedComponent {...this.props} {...newProps} />
      }
    };
  };

  // 普通组件
  class InputComponent extends React.Component {
    render() {
      return <input {...this.props} />
    }
  }

  // 包装
  export default Hoc(InputComponent);
* */

反向继承

/*
  反向继承(在Hoc中使用被包装组件内部的状态和方法)
    - 反向继承的组件要是类组件,函数组件不行

  export const Hoc = (WrappedComponent) => {
    class Wrapper extends WrappedComponent { // super ≈ WrappedComponent里面的this
      render() {
        if (!this.props.data) {
            return <span>loading....</span>
        } else {
            return super.render() // 调用被包装组件的render()方法
        }
      }
    }
  }

  ====

  export default function Hoc(WrappedComponent) {
    return class extends WrappedComponent {
      render() {
        const elementTree = super.render(); // React用Js对象来模拟Dom树结构,可以通过修改Js对象的属性来操纵数据

        console.log(elementTree); // 不太了解里面的结构可以打印出来 + 官网cloneElement() 了解一下

        const newElementTree = React.cloneElement(elementTree, { children: `你的内容已被劫持` });

        return newElementTree;
      }
    };
  };
* */

渲染劫持

/*
  渲染劫持

  e.g. 控制组件是否渲染(可以做全局的loading效果,没有数据时显示loading...)

    // 基本的实现
    export const LoadingHoc = (WrappedComponent) => {
      class Wrapper extends React.Component {
        render() {
          if (!this.props.data) {
            return <span>loading....</span>
          } else {
            return <WrappedComponent {...this.props} />
          }
        }
      }
    }

    // 用反向继承实现
    export const LoadingHoc = (WrappedComponent) => {
      class Wrapper extends WrappedComponent { // super ≈ WrappedComponent里面的this
        render() {
          if (!this.props.data) {
            return <span>loading....</span>
       	  } else {
            return super.render() // 调用被包装组件的render()方法
          }
        }
      }
    }

  ======

  e.g. 劫持渲染的内容

    export default function Hoc2(WrappedComponent) {
      return class extends WrappedComponent { // 这里用了反向继承
        render() {
          const elementTree = super.render(); // React用Js对象来模拟Dom树结构,可以通过修改Js对象的属性来操纵数据

          console.log(elementTree); // 不太了解里面的结构可以打印出来 + 官网cloneElement() 了解一下

          const newElementTree = React.cloneElement(elementTree, { children: `你的内容已被劫持` });

          return newElementTree;
        }
      };
    };
* */

配置包装名

/*
  配置包装名:在调试工具 React Developer Tools 中更容易被找到
  e.g. 高阶组件为Hoc,被包装组件为WrappedComponent, 显示的名字应该是 Hoc(WrappedComponent)


    // 返回类组件
    export default function Hoc(WrappedComponent) {
      return class extends React.Component {
        /*
          没有在Hoc中定义 static displayName = 'XXX';
            - React Developer Tools 中展示的名字是 Anonymous

          没有在被包装组件中定义 static displayName = 'XXX';
            - React Developer Tools 中展示的名字是 undefined Hoc

          在被包装组件中定义 static displayName = 'header';
            - React Developer Tools 中展示的名字是 header Hoc
        *\
        static displayName = `Hoc(${WrappedComponent.displayName});

        render() {
          return <WrappedComponent {...this.props} />;
        }
      };
    }

    // 返回函数式组件
    export default function Hoc(WrappedComponent) {

      /*
        return function(props) {}
          - 在 React Developer Tools 中展示的名字是 Anonymous

        return function Wrapper(props) {}
          - 在 React Developer Tools 中展示的名字是 Wrapper
      *
      return function Wrapper(props) {
        return <WrappedComponent {...props} />;
      };
    }

    =======

    export default function Hoc(WrappedComponent) {
      const Wrapper = (props) => {
        return <WrappedComponent {...props} />;
      };

      /*
        没有在被包装组件中定义 static displayName = 'XXX';
          - React Developer Tools 中展示的名字是 undefined Hoc

        在被包装组件中定义 static displayName = 'header';
          - React Developer Tools 中展示的名字是 header Hoc
      *\
      Wrapper.displayName = `Hoc(${WrappedComponent.displayName})`;

      return Wrapper;
    }

    =====


    // 被包裹组件
    export default class Header extends React.Component {
      static displayName = 'header';

      render() {
        return <span>{ this.props.count }</span>
      }
    };

* */

不要在render中使用HOC

/*
  不要在render中使用HOC

  e.g.
  export default class Home extends React.Component {
    render() {
      // 每次render都会创建一个新的Wrapper
      // Wrapper1 !== Wrapper2
      // 导致高阶组件会卸载和重新挂载,状态会丢失(e.g. checkbox的选中丢失 | state被清空)
      × const Wrapper = Hoc(WrappedComponent);

      return <Wrapper />
    }
  }

 =========

  √ const Wrapper = myHoc(WrappedComponent);

  export default class Home extends React.Component {
    render() {
      return <Wrapper />
    }
  }
* */

Hoc的渲染顺序

/*
  Hoc的渲染顺序
    Hoc(Header)

    componentDidMount: Header -> HOC
    componentWillUnMount: HOC -> Header
* */

HOC 和 Mixin

/*
  HOC 和 Mixin
    HOC
     - 属于函数式编程思想
     - 被包裹组件感知不到高阶组件的存在
     - 高阶组件返回的组件会在原来的基础上的到增强

    Mixin
    - 混入模式,会在被包装组件上不断增加新的属性和方法
    - 被包裹组件可感知
    - 需要做处理(命名冲突、状态维护)
* */

以上就是React 高阶组件HOC用法归纳的详细内容,更多关于React 高阶组件HOC的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
jQuery EasyUI API 中文文档 - Tabs标签页/选项卡
Oct 01 Javascript
jQuery中:last选择器用法实例
Dec 30 Javascript
深入理解js数组的sort排序
May 28 Javascript
JS对大量数据进行多重过滤的方法
Nov 04 Javascript
简单实现jQuery多选框功能
Jan 09 Javascript
JavaScript中数组的各种操作的总结(必看篇)
Feb 13 Javascript
JS实现求数组起始项到终止项之和的方法【基于数组扩展函数】
Jun 13 Javascript
js学习总结之DOM2兼容处理this问题的解决方法
Jul 27 Javascript
Angular4学习笔记之根模块与Ng模块
Sep 09 Javascript
基于webpack4搭建的react项目框架的方法
Jun 30 Javascript
Vue 使用iframe引用html页面实现vue和html页面方法的调用操作
Nov 16 Javascript
输入框跟随文字内容适配宽实现示例
Aug 14 Javascript
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
React中的Context应用场景分析
Jun 11 #Javascript
详解JVM系列之内存模型
使用Vue3+Vant组件实现App搜索历史记录功能(示例代码)
一文搞懂redux在react中的初步用法
Jun 09 #Javascript
You might like
php网站地图生成类示例
2014/01/13 PHP
PHP递归获取目录内所有文件的实现方法
2016/11/01 PHP
基础的prototype.js常用函数及其用法
2007/03/10 Javascript
JavaScript 的方法重载效果
2009/08/07 Javascript
javascript 操作select下拉列表框的一点小经验
2010/03/20 Javascript
js 多浏览器分别判断代码
2010/04/01 Javascript
jquery实现动态菜单的实例代码
2013/11/28 Javascript
JavaScript中按位“异或”运算符使用介绍
2014/03/14 Javascript
jquery图形密码实现方法
2015/03/11 Javascript
详谈JS中实现种子随机数及作用
2016/07/19 Javascript
微信小程序开发(一) 微信登录流程详解
2017/01/11 Javascript
初探nodeJS
2017/01/24 NodeJs
es6新特性之 class 基本用法解析
2018/05/05 Javascript
详解基于Vue,Nginx的前后端不分离部署教程
2018/12/04 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
vue中defineProperty和Proxy的区别详解
2020/11/30 Vue.js
[00:56]2014DOTA2国际邀请赛 DK、iG 赛前探访
2014/07/10 DOTA
Python验证码识别处理实例
2015/12/28 Python
浅谈Python2.6和Python3.0中八进制数字表示的区别
2017/04/28 Python
Python探索之修改Python搜索路径
2017/10/25 Python
python 集合 并集、交集 Series list set 转换的实例
2018/05/29 Python
Python中的几种矩阵乘法(小结)
2019/07/10 Python
python numpy数组中的复制知识解析
2020/02/03 Python
django列表筛选功能的实现代码
2020/03/27 Python
PyQt5 控件字体样式等设置的实现
2020/05/13 Python
使用Filters滤镜弥补CSS3的跨浏览器问题以及兼容低版本IE
2013/01/23 HTML / CSS
html5中如何将图片的绝对路径转换成文件对象
2018/01/11 HTML / CSS
StubHub新西兰:购买和出售你的门票
2019/04/22 全球购物
今冬明春火灾防控工作方案
2014/05/29 职场文书
反四风对照检查材料
2014/09/22 职场文书
Pytest实现setup和teardown的详细使用详解
2021/04/17 Python
Pytorch中Softmax和LogSoftmax的使用详解
2021/06/05 Python
CSS Transition通过改变Height实现展开收起元素
2021/08/07 HTML / CSS
国庆节到了,利用JS实现一个生成国庆风头像的小工具 详解实现过程
2021/10/05 Javascript
PyTorch device与cuda.device用法
2022/04/03 Python
html中两种获取标签内的值的方法
2022/06/16 jQuery