奋发图强学 React 系列 (二):React 高阶组件(HOC)

高阶组件:是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API的一部分,它是一种基于 React 的组合特性而形成的一种设计模式

HOC 的作用之一就是解决大量的代码复用、逻辑复用的问题,还有一个重要的作用就是让 props 中放入一些开发者需要的东西

HOC 是以组件为参数,返回组件的函数。返回的组件把传进去的组件进行功能强化

常用的 HOC 有:属性代理和反向继承两种

高阶组件之属性代理

属性代理就是用代理组件包裹原始组件,在代理组件上,可以做一些对原始组件的强化操作,这里要注意代理返回的是一个新组件,被包裹包裹的原始组件将在新的组件内被挂在

javascript 复制代码
import React from 'react';
function HOC(WrapComponent) {
  return class advance extends React.Component {
    state = {
      name: 'my name is Vera',
    };
    render() {
      return <WrapComponent {...this.props} name={this.state.name} />;
    }
  };
}

function Test(props) {
  console.log(props);
  return (
    <div {...props}>
      <h1>{props.name}</h1>
    </div>
  );
}

export default HOC(Test);

高阶组件之反向继承

反向继承和属性代理有一定的区别,在于包装后的组件继承了原始组件,所以此时无需再去挂载业务组件

scala 复制代码
class Index extends React.Component {
  render() {
    return <div>Hello, Vera</div>;
  }
}

function HOC2(Component) {
  // 直接继承需要包装的组件
  return class wrapComponent extends Component {
    // ...
  };
}

export default HOC2(Index);

高阶组件的功能:

功能之强化 props,

就是在原始组件的 props 上,加入一些其他的 props,强化原始组件功能。举个例子,为了让组件也可以获取路由对象,进行路由跳转等操作,React-Router 提供了像 WithRouter 这样的 HOC

功能之控制渲染

①渲染错误边界

比如我们使用变量 list 存储一个数组,使用数组的 map 方法来渲染列表,但假如这个list 被置为 null(比如可能从接口中拿到一个 null 并且没有处理这个数据),那么 map 方法就会报错,我们可以用一个 HOC 来处理渲染错误,如果出现错误,就展示 Error

当我们人为点击测试按钮时,就会触发 componentDidCatch 生命周期,来展示托底 UI

请注意:属性代理的 HOC,在挂载原始组件的时候,需要把 props 进行转发,当 Component 父组件绑定给它的 props 时,会被绑定到地里组件 WrapComponent 上,所以在挂载 Component 的时候,需要将 this.props 上的所有属性传递下去

②修改渲染树
javascript 复制代码
class Index6 extends React.Component {
  render() {
    return (
      <div>
        <ul>
          <li>React</li>
          <li>Vue</li>
          <li>Angular</li>
        </ul>
      </div>
    );
  }
}

function HOC6(Component) {
  return class Advance extends Component {
    render() {
      const element = super.render();
      debugger;
      const otherProps = {
        name: 'HOC',
      };
      const appendElement = React.createElement('li', {}, `hello, i am ${otherProps.name}`);
      const newChild = React.Children.map(element.props.children.props.children, (child, index) => {
        console.log(child, index);
        if (index === 2) {
          return appendElement;
        }
        return child;
      });

      console.log(newChild);

      return React.cloneElement(element, element.props, newChild);
    }
  };
}

export default HOC6(Index6);

通过 jsx 方式修改渲染树,改变渲染的结构

功能之组件赋能

给组件增加一些额外功能,比如组件状态监控,埋点监控等

javascript 复制代码
/ 对组件内的点击事件进行监听
function ClickHoc(Component) {
  return function Wrap(props) {
    const dom = useRef(null);
    useEffect(() => {
      const handleClick = () => console.log('点击了');
      dom.current.addEventListener('click', handleClick);

      return () => {
        dom.current.removeEventListener('click', handleClick);
      };
    }, []);
    return (
      <div ref={dom}>
        <Component {...props} />
      </div>
    );
  };
}

@ClickHoc
class Index7 extends React.Component {
  render() {
    return (
      <div className="index">
        <p>Hello, world</p>
        <button>组件内部点击</button>
      </div>
    );
  }
}

export default () => {
  return (
    <div className="box">
      <Index7 />
      <button>组件外部点击</button>
    </div>
  );
};

高阶组件实践:渲染分片

javascript 复制代码
/**
 * 高阶组件事件:渲染分片
 */

function createHoc() {
  const renderQueue = [];
  return function Hoc(Component) {
    // RenderController 用于真正挂在原始组件
    function RenderController(props) {
      const { renderNextComponent, ...otherProps } = props;
      useEffect(() => {
        renderNextComponent();
      }, []);
      return <Component {...otherProps} />;
    }

    return class Wrap extends React.Component {
      constructor() {
        super();
        this.state = {
          isRender: false,
        };
        const tryRender = () => {
          this.setState({
            isRender: true,
          });
        };
        if (renderQueue.length === 0) this.isFirstRender = true;
        renderQueue.push(tryRender);
      }

      isFirstRender = false;

      renderNextComponent = () => {
        if (renderQueue.length > 0) {
          console.log('挂载下一个组件');
          const nextRender = renderQueue.shift();
          setTimeout(() => {
            nextRender();
          }, 1000);
        }
      };

      componentDidMount() {
        this.isFirstRender && this.renderNextComponent();
      }
      render() {
        const { isRender } = this.state;
        return isRender ? (
          <RenderController {...this.props} renderNextComponent={this.renderNextComponent} />
        ) : (
          <div>...loading</div>
        );
      }
    };
  };
}

const loadingHoc = createHoc();
function CompA() {
  useEffect(() => {
    console.log('组件 A 挂载完成');
  });
  return <div>模块 A</div>;
}
function CompB() {
  useEffect(() => {
    console.log('组件 B 挂载完成');
  });
  return <div>模块 B</div>;
}
function CompC() {
  useEffect(() => {
    console.log('组件 C 挂载完成');
  });
  return <div>模块 C</div>;
}

const ComponentA = loadingHoc(CompA);
const ComponentB = loadingHoc(CompB);
const ComponentC = loadingHoc(CompC);

export default function Index8() {
  return (
    <div>
      <ComponentA />
      <ComponentB />
      <ComponentC />
    </div>
  );
}

解析:

这段代码实现了一个高阶组件(Higher-Order Component, HOC)工厂函数 createHoc,用于控制多个组件的顺序渲染。下面是对代码的详细解释:

1. 高阶组件工厂函数 ****createHoc

  • createHoc 是一个工厂函数,返回一个高阶组件 Hoc
  • 内部维护一个 renderQueue 数组,用于存储待渲染组件的渲染函数

2. 高阶组件 ****Hoc

  • 接收一个 Component 作为参数,返回一个新的包装组件 Wrap
  • 主要功能是控制组件的顺序渲染

3. ****RenderController ****组件

  • 这是实际渲染原始组件 Component 的控制器
  • 接收 renderNextComponent 方法作为 props,并在 useEffect 中调用它
  • 渲染原始组件 Component 并传递所有其他 props

4. ****Wrap ****类组件

这是高阶组件返回的包装组件,主要逻辑包括:

状态管理

  • isRender 状态控制是否渲染实际内容

渲染队列管理

  • 在构造函数中,将 tryRender 方法(用于设置渲染状态)推入 renderQueue
  • 如果是队列中的第一个组件(renderQueue.length === 0),标记为 isFirstRender

核心方法

  • renderNextComponent:从队列中取出下一个渲染函数并执行,触发下一个组件的渲染
  • componentDidMount:如果是第一个组件,立即触发渲染

渲染逻辑

  • 如果 isRender 为 true,渲染 RenderController(即实际组件)
  • 否则显示加载状态("...loading")

工作原理

  1. 当多个组件被这个 HOC 包装时,它们会被依次加入渲染队列

  2. 只有当前组件渲染完成后,才会触发下一个组件的渲染

  3. 每个组件会先显示加载状态,直到轮到它渲染时才显示实际内容

使用场景

这种模式适用于需要控制多个组件按顺序加载的场景,比如:

  • 资源密集型组件需要分批加载
  • 需要确保组件按特定顺序初始化
  • 避免同时渲染多个复杂组件导致的性能问题

注意事项

  • 这是一个类组件实现,在现代 React 中可以考虑用 hooks 重构
  • 需要确保组件卸载时正确处理队列,避免内存泄漏
  • 这种顺序渲染可能会影响用户体验,需要权衡利弊使用

这个高阶组件提供了一种优雅的方式来控制多个组件的渲染顺序,是一种高级的 React 模式。

相关推荐
环境工程笔记2 分钟前
浏览器自动化跑成功了,为什么结果还是不对?
前端
东风破_3 分钟前
一文搞懂 JavaScript 变量声明:var、let、const 到底有什么区别?
前端·javascript
问心无愧05136 分钟前
ctf show web入门261
android·前端·笔记
触底反弹8 分钟前
你真的理解 JavaScript 变量提升(Hoisting)吗?从 V8 引擎编译原理深入剖析
前端·面试
蜡台21 分钟前
Vue2 使用 typescript 教程
前端·vue.js·typescript
光影少年33 分钟前
Redux Toolkit 用法、解决原生Redux 冗余问题
开发语言·前端·javascript·react.js·中间件·前端框架·ecmascript
云水一下41 分钟前
JavaScript 从零基础到精通系列:DOM 操作与事件驱动编程
前端·javascript
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_32:(Web字体深度解析与实践指南)
前端·javascript·css·ui·html
砍材农夫1 小时前
物联网 基于netty核心实战-安全tls
java·开发语言·前端·物联网·安全