奋发图强学 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 模式。

相关推荐
Aaaaaaaaaaayou7 分钟前
浅玩一下 Mobile Use
前端·llm
这个昵称也不能用吗?8 分钟前
react-native搭建开发环境过程记录
前端·react native·cocoapods
hy_花花8 分钟前
Vue3.4之defineModel的用法
前端·vue.js
DataFunTalk22 分钟前
Foundation Agent:深度赋能AI4DATA
前端·后端·算法
hboot24 分钟前
rust 全栈应用框架dioxus
前端·rust·全栈
我是仙女你信不信29 分钟前
生成pdf并下载
前端·javascript·vue.js
少糖研究所29 分钟前
记一次Web Worker的使用
前端·性能优化
乔乔不姓乔呀32 分钟前
pc 和大屏如何适配
前端
speedoooo42 分钟前
新晋前端框架技术:小程序容器与SuperApp构建
前端·小程序·前端框架·web app
vvilkim1 小时前
React 组件类型详解:类组件 vs. 函数组件
前端·javascript·react.js