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的图片切换效果
Jul 06 Javascript
基于jquery的二级联动菜单实现代码
Apr 25 Javascript
通过javascript获取iframe里的值示例代码
Jun 24 Javascript
js实现动画特效的文字链接鼠标悬停提示的方法
Mar 02 Javascript
js显示当前日期时间和星期几
Oct 22 Javascript
JS获取地址栏参数的两种方法(简单实用)
Jun 14 Javascript
jquery实现图片轮播器
May 23 jQuery
详解如何将 Vue-cli 改造成支持多页面的 history 模式
Nov 20 Javascript
解决低版本的浏览器不支持es6的import问题
Mar 09 Javascript
理解 JavaScript EventEmitter
Mar 29 Javascript
Vue实现简单分页器
Dec 29 Javascript
Javascript和jquery在selenium的使用过程
Oct 31 jQuery
React forwardRef的使用方法及注意点
原生Javascript+HTML5一步步实现拖拽排序
JS代码编译器Monaco使用方法
React中的Context应用场景分析
Jun 11 #Javascript
详解JVM系列之内存模型
使用Vue3+Vant组件实现App搜索历史记录功能(示例代码)
一文搞懂redux在react中的初步用法
Jun 09 #Javascript
You might like
Cappuccino 卡布其诺咖啡之制作
2021/03/03 冲泡冲煮
php利用scws实现mysql全文搜索功能的方法
2014/12/25 PHP
PHP中的命名空间相关概念浅析
2015/01/22 PHP
使用PHP如何实现高效安全的ftp服务器(一)
2015/12/20 PHP
win平台安装配置Nginx+php+mysql 环境
2016/01/12 PHP
JS去除字符串的空格增强版(可以去除中间的空格)
2009/08/26 Javascript
分析Node.js connect ECONNREFUSED错误
2013/04/09 Javascript
jquery实现盒子下拉效果示例代码
2013/09/12 Javascript
js实现创建删除html元素小结
2015/09/30 Javascript
JavaScript代码实现禁止右键、禁选择、禁粘贴、禁shift、禁ctrl、禁alt
2015/11/17 Javascript
JS模态窗口返回值兼容问题的完美解决方法
2016/05/28 Javascript
BootStrap table使用方法分析
2016/11/08 Javascript
vue双向数据绑定原理探究(附demo)
2017/01/17 Javascript
详解Vue 动态添加模板的几种方法
2017/04/25 Javascript
实例讲解DataTables固定表格宽度(设置横向滚动条)
2017/07/11 Javascript
Vue波纹按钮组件制作
2018/04/30 Javascript
vue项目中跳转到外部链接的实例讲解
2018/09/20 Javascript
简单理解Python中的装饰器
2015/07/31 Python
全面了解Python环境配置及项目建立
2016/06/30 Python
Python3 加密(hashlib和hmac)模块的实现
2017/11/23 Python
解决python-docx打包之后找不到default.docx的问题
2020/02/13 Python
Python数据模型与Python对象模型的相关总结
2021/01/26 Python
怀俄明州飞钓:Platte River Fly Shop
2017/12/28 全球购物
End Clothing美国站:英国男士潮牌商城
2018/04/20 全球购物
英国在线照明超市:Castlegate Lights
2019/10/30 全球购物
sort命令的作用和用法
2012/11/04 面试题
中学教师师德师风演讲稿
2014/08/22 职场文书
采购内勤岗位职责
2015/04/13 职场文书
中学感恩教育活动总结
2015/05/05 职场文书
道歉短信大全
2015/05/12 职场文书
2015年村级财务管理制度
2015/08/04 职场文书
创业的9条正确思考方式
2019/08/26 职场文书
Redis IP地址的绑定的实现
2021/05/08 Redis
教你怎么用python爬取爱奇艺热门电影
2021/05/20 Python
MySQL CHAR和VARCHAR该如何选择
2021/05/31 MySQL
pytorch中的torch.nn.Conv2d()函数图文详解
2022/02/28 Python