12k
All articles

理解 React 中的高阶组件及示例

React 高阶组件通过包裹现有组件来注入 props 并共享逻辑,本文对比 HOC 与 hooks 的差异,并介绍 ref 转发及实际代码示例。

OpenReplay Team
OpenReplay Team
理解 React 中的高阶组件及示例

高阶组件(HOCs)是 React 中的一种模式,初次接触可能会感到困惑,但一旦理解,它们就成为抽象和重用组件逻辑的有价值工具。在本指南中,您将了解什么是 HOCs,如何使用它们,以及何时使用它们是合理的——并附有清晰的示例。

要点

  • 理解 React 中高阶组件的概念
  • 通过清晰的代码示例学习如何编写和使用 HOCs
  • 了解何时使用 HOCs——以及何时不使用

什么是高阶组件?

高阶组件是一个函数,它接收一个组件并返回一个新组件。它为原始组件添加行为、属性或逻辑——而不直接修改原组件。

基本结构

const withExtraLogic = (Component) => {
  return function WrappedComponent(props) {
    // 在这里添加自定义行为
    return <Component {...props} />;
  };
};

可以将其视为装饰器:你包装某物并在传递之前增强它。

为什么使用 HOC?

  • 代码复用:抽象重复逻辑
  • 横切关注点:日志记录、认证、跟踪
  • 属性注入:基于逻辑注入属性,而不使组件内部变得混乱

基本示例:withLogger

const withLogger = (Component) => {
  return function WrappedComponent(props) {
    useEffect(() => {
      console.log('Mounted:', Component.name);
    }, []);
    return <Component {...props} />;
  };
};

const Hello = () => <h1>Hello</h1>;
const LoggedHello = withLogger(Hello);

// 使用
<LoggedHello />

这会在组件挂载时记录组件名称——而不触及原始的 Hello 组件。

另一个用例:withWindowWidth

这个 HOC 为任何组件添加窗口宽度作为属性:

const withWindowWidth = (Component) => {
  return function WrappedComponent(props) {
    const [width, setWidth] = useState(window.innerWidth);

    useEffect(() => {
      const handleResize = () => setWidth(window.innerWidth);
      window.addEventListener('resize', handleResize);
      return () => window.removeEventListener('resize', handleResize);
    }, []);

    return <Component {...props} windowWidth={width} />;
  };
};

const DisplayWidth = ({ windowWidth }) => <p>Width: {windowWidth}px</p>;

const ResponsiveDisplay = withWindowWidth(DisplayWidth);

这使您可以创建响应式组件,而无需复制调整大小的逻辑。

HOCs vs hooks vs render props

模式 描述 使用场景 HOC 返回新组件的函数 当你想为多个组件添加行为时 Hook 使用 use* 的可重用函数 当你想要可重用的状态逻辑时 Render props 将函数作为子元素传递 当你想通过函数实现动态渲染时

与 hooks 的深入比较

今天更推荐使用 hooks 来共享逻辑,但它们之间有差异:

  • HOCs 是外部包装器。Hooks 在组件内部运行。
  • Hooks 提供可组合性 — 你可以在一个组件中轻松调用多个 hooks。
  • Hooks 自然地与 refs 和本地状态配合,而 HOCs 需要特别注意 ref 的转发。

相同逻辑,不同实现

使用 hook 代替 withWindowWidth

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

const DisplayWidth = () => {
  const width = useWindowWidth();
  return <p>Width: {width}px</p>;
};

这个版本避免了额外的包装器,在现代 React 中更易读。

HOCs 的局限性

  • 包装器嵌套:太多 HOCs 会使组件树难以调试
  • 命名:匿名包装组件使 DevTools 更难阅读
  • Ref 转发:除非使用 forwardRef,否则标准 refs 不起作用

Ref 问题示例

const withWrapper = (Component) => (props) => <Component {...props} />;
const FancyInput = forwardRef((props, ref) => <input ref={ref} {...props} />);

没有 forwardRefref 将指向包装器,而不是实际的输入元素。

最佳实践

  • 始终传递 props:<Component {...props} />
  • 为调试命名包装组件:
WrappedComponent.displayName = `withLogger(${Component.displayName || Component.name})`;
  • 当 hooks 可以更清晰地完成工作时,避免使用 HOCs

结论

高阶组件是扩展和重用组件逻辑的灵活方式。虽然 hooks 在现代 React 中更常见,但 HOCs 仍有其用武之地——特别是当你想在不改变现有组件内部的情况下添加逻辑时。在它们使代码更清晰而非更复杂时使用它们。

常见问题

React 中还在使用高阶组件吗?

是的,尽管现在不那么常见。Hooks 已经取代了许多用例,但 HOCs 对于向多个组件注入行为仍然很有用。

高阶组件和 hook 有什么区别?

HOC 包装一个组件并返回一个新组件。Hook 是一个从内部向组件添加逻辑的函数。

我可以在 HOCs 中使用 refs 吗?

只有使用 `forwardRef` 转发 ref 才可以。否则,refs 将指向包装器而不是原始组件。

什么时候应该使用 HOC 而不是 hook?

当你想用相同的外部逻辑(如认证或日志记录)包装多个组件,而不重复编写该逻辑时,使用 HOCs。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.