欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

React 高阶组件HOC用法归纳

程序员文章站 2022-04-05 14:07:29
一句话介绍hoc何为高阶组件(hoc),根据官方文档的解释:“高阶组件是react中复用组件逻辑的一项高级技术。它不属于react api的组成部分,它是从react自身组合性质中抽离出来的一种模式。...

一句话介绍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的资料请关注其它相关文章!