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 相关文章推荐
JavaScript 嵌套函数指向this对象错误的解决方法
Mar 15 Javascript
关于JavaScript中原型继承中的一点思考
Jul 25 Javascript
js获取网页高度(详细整理)
Dec 28 Javascript
Js制作简单弹出层DIV在页面居中 中间显示遮罩的具体方法
Aug 08 Javascript
node.js中的fs.fstatSync方法使用说明
Dec 15 Javascript
EasyUi datagrid 实现表格分页
Feb 10 Javascript
Javascript使用uploadify来实现多文件上传
Nov 16 Javascript
Easyui笔记2:实现datagrid多行删除的示例代码
Jan 14 Javascript
vue+element实现表格新增、编辑、删除功能
May 28 Javascript
详细分析Node.js 模块系统
Jun 28 Javascript
JS如何判断对象是否包含某个属性
Aug 29 Javascript
javascript Number 与 Math对象的介绍
Nov 17 Javascript
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
React中的Context应用场景分析
Jun 11 #Javascript
详解JVM系列之内存模型
使用Vue3+Vant组件实现App搜索历史记录功能(示例代码)
一文搞懂redux在react中的初步用法
Jun 09 #Javascript
You might like
阿拉伯的咖啡与水烟
2021/03/03 咖啡文化
PHP中通过语义URL防止网站被攻击的方法分享
2011/09/08 PHP
php curl中gzip的压缩性能测试实例分析
2016/11/08 PHP
JavaScript 撑出页面文字换行
2009/06/15 Javascript
js 函数的执行环境和作用域链的深入解析
2009/11/01 Javascript
jquery.Jwin.js 基于jquery的弹出层插件代码
2012/05/23 Javascript
基于jQuery实现的文字按钮表单特效整理
2014/12/07 Javascript
详解JavaScript中的自定义事件编写
2016/05/10 Javascript
深入理解JavaScript内置函数
2016/06/03 Javascript
jQuery获取table行数并输出单元格内容的实现方法
2016/06/30 Javascript
微信小程序开发之toast等弹框提示使用教程
2017/06/08 Javascript
mint-ui 时间插件使用及获取选择值的方法
2018/02/09 Javascript
JS实现移动端触屏拖拽功能
2018/07/31 Javascript
使用RN Animated做一个“添加购物车”动画的方法
2018/09/12 Javascript
vue下拉菜单组件(含搜索)的实现代码
2018/11/25 Javascript
WebGL three.js学习笔记之阴影与实现物体的动画效果
2019/04/25 Javascript
Vue实现购物车详情页面的方法
2019/08/20 Javascript
JS箭头函数和常规函数之间的区别实例分析【 5 个区别】
2020/05/27 Javascript
[00:43]DOTA2小紫本全民票选福利PA至宝全方位展示
2014/11/25 DOTA
python求crc32值的方法
2014/10/05 Python
python端口扫描系统实现方法
2014/11/19 Python
python运行其他程序的实现方法
2017/07/14 Python
Python实现按中文排序的方法示例
2018/04/25 Python
如何安装并使用conda指令管理python环境
2019/07/10 Python
Python中常见的数制转换有哪些
2020/05/27 Python
详解Python Celery和RabbitMQ实战教程
2021/01/20 Python
英国手机零售商:Carphone Warehouse
2018/06/06 全球购物
Python是如何进行类型转换的
2013/06/09 面试题
自荐信怎么写好
2013/11/11 职场文书
葡萄牙语专业个人求职信
2013/12/10 职场文书
打架检讨书50字
2014/01/11 职场文书
初中升旗仪式演讲稿
2014/05/08 职场文书
法定代表人资格证明书
2015/06/18 职场文书
《鸟的天堂》教学反思
2016/02/19 职场文书
MySQL令人咋舌的隐式转换
2021/04/05 MySQL
CSS实现鼠标悬浮动画特效
2023/05/07 HTML / CSS