Back

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

理解 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 仍有其用武之地——特别是当你想在不改变现有组件内部的情况下添加逻辑时。在它们使代码更清晰而非更复杂时使用它们。

常见问题

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

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

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

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

Listen to your bugs 🧘, with OpenReplay

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