当前位置:编程学习 > > 正文

react高阶组件怎么用(React 高阶组件HOC用法归纳)

时间:2021-11-03 15:41:55类别:编程学习

react高阶组件怎么用

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 (
              <li>
                 <EnhanceHeader count={1} />
              </li>
            )
          }
    
      第二种: 装饰器包装,只能在类组件中使用
        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 (
          <li>
            <EnhanceHeader />
          </li>
        );
      };
    
    * */
    
  • 处理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 (
              <li>
                <Content ref={connectRef} />
              </li>
            );
          };
    
          // 被包装组件
          const Content = (props) => {
            return (
              <li>
                <input type="password" ref={props.forwardedRef} />
              </li>
            );
          };
    
    
          // 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 (
          <li>
            <Content forwardedRef={connectRef} />
          </li>
        );
      };
    
      // 定义高阶组件
      export const Hoc = (WrappedComponent) => {
        class Wrapper extends React.Component {
          render() {
            return <WrappedComponent {...props} />
          }
        }
      }
    
      // 被包装的组件
      const Content = (props) => {
        return (
          <li>
            <input type="password" ref={props.forwardedRef} />
          </li>
        );
      };
    
      // 包装过程
      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的资料请关注开心学习网其它相关文章!

    上一篇下一篇

    猜您喜欢

    热门推荐