添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
栏目: 开发技术

这篇文章主要介绍了React高阶组件HOC怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

一句话介绍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 (
             <EnhanceHeader count={1} />
  第二种: 装饰器包装,只能在类组件中使用
    import React from 'react';
    import Hoc from './Hoc';
    export default class Header extends React.Component {
      render() {
        return <span>{ this.props.count }</span>
    =======
    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 (
        <EnhanceHeader />
* */

处理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 (
            <Content ref={connectRef} />
      // 被包装组件
      const Content = (props) => {
        return (
            <input type="password" ref={props.forwardedRef} />
      // 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 (
        <Content forwardedRef={connectRef} />
  // 定义高阶组件
  export const Hoc = (WrappedComponent) => {
    class Wrapper extends React.Component {
      render() {
        return <WrappedComponent {...props} />
  // 被包装的组件
  const Content = (props) => {
    return (
        <input type="password" ref={props.forwardedRef} />
  // 包装过程
  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
    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 (
          data.map((item, index) => {
            return <li key={index}><{item}/li>
   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
  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
     - 属于函数式编程思想
     - 被包裹组件感知不到高阶组件的存在
     - 高阶组件返回的组件会在原来的基础上的到增强
    Mixin
    - 混入模式,会在被包装组件上不断增加新的属性和方法
    - 被包裹组件可感知
    - 需要做处理(命名冲突、状态维护)
* */

感谢你能够认真阅读完这篇文章,希望小编分享的“React高阶组件HOC怎么用”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!

推荐阅读:
React总结篇之六_React高阶组件 React中高阶组件的介绍