React 设计模式 / HOC 模式

发布: (2025年12月26日 GMT+8 09:05)
7 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文。

高阶组件 (HOC) 概述

高阶组件 (HOC) 是一个函数,它接受一个组件作为参数,添加一些逻辑,并返回一个包含该逻辑的新组件。HOC 让你能够在多个组件之间复用行为,而无需重复代码。

1️⃣ 使用 HOC 添加样式

与其在每个组件中都创建 style 对象,不如创建一个 HOC,将相同的样式注入到传入的任意组件中。

// withStyles.tsx
function withStyles(Component: React.ComponentType) {
  return (props: T) => {
    const style = { padding: '0.6rem', margin: '4rem' };
    return ;
  };
}

用法

const Button = () => Click me!;
const Text   = () => 
Hello World!
;

const StyledButton = withStyles(Button);
const StyledText   = withStyles(Text);

StyledButtonStyledText 是原始组件,已通过 withStyles 提供的 style 属性进行增强。

2️⃣ 使用 HOC 添加加载指示器

在获取数据时,我们通常希望在请求完成之前显示一个 Loading…(加载中)屏幕。
与其在众多组件中散布加载逻辑,我们可以将其封装在名为 withLoader 的 HOC 中。

2.1 最简骨架

function withLoader(
  Element: React.ComponentType,
  url: string
) {
  return (props: Omit) => {
    // implementation will go here
    return ;
  };
}

2.2 完整实现

import { useEffect, useState } from 'react';

function withLoader(
  Element: React.ComponentType,
  url: string
) {
  return (props: Omit) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      fetch(url)
        .then((res) => res.json())
        .then((json) => {
          setData(json);
          setLoading(false);
        })
        .catch((err) => {
          console.error(err);
          setLoading(false);
        });
    }, [url]);

    if (loading) {
      return Loading…;
    }

    // Pass the fetched data as a `data` prop to the wrapped component
    return ;
  };
}

2.3 将 HOC 应用于组件

// CatImages.tsx
type CatImagesProps = {
  data: any; // replace `any` with a proper type if you have one
};

function CatImages({ data }: CatImagesProps) {
  return (
    

      {data.map((cat: any) => (
        
          
        
      ))}
    

  );
}

// Export the wrapped component
export default withLoader(CatImages, 'https://api.thecatapi.com/v1/images/search?limit=10');

现在 CatImages 会自动接收一个 data 属性,在请求进行期间,用户会看到 Loading…(加载中)信息。

3️⃣ 组合多个 HOC

您可以堆叠 HOC 来组合行为。
下面我们创建一个提供 hovering 属性的 withHover HOC,然后将其与 withLoader 组合使用。

3.1 withHover HOC

import { useState, useCallback } from 'react';

function withHover(Component: React.ComponentType) {
  return (props: T) => {
    const [hovering, setHovering] = useState(false);

    const onMouseEnter = useCallback(() => setHovering(true), []);
    const onMouseLeave = useCallback(() => setHovering(false), []);

    return (
      
        
      
    );
  };
}

3.2 组合 withHover + withLoader

// WrappedCatImages.tsx
export default withHover(
  withLoader(CatImages, 'https://api.thecatapi.com/v1/images/search?limit=10')
);

CatImages 中,您现在可以同时使用 data(来自 withLoader)和 hovering(来自 withHover)来渲染条件性的 “Hovering” 框。

4️⃣ 用 Hook(useHover)替代 HOC

Hook 通常可以实现与 HOC 相同的效果,但嵌套更少。

4.1 useHover hook

import { useRef, useState, useEffect, RefObject } from 'react';

function useHover(): [RefObject, boolean] {
  const ref = useRef(null);
  const [hovering, setHovering] = useState(false);

  useEffect(() => {
    const node = ref.current;
    if (!node) return;

    const handleMouseEnter = () => setHovering(true);
    const handleMouseLeave = () => setHovering(false);

    node.addEventListener('mouseenter', handleMouseEnter);
    node.addEventListener('mouseleave', handleMouseLeave);

    return () => {
      node.removeEventListener('mouseenter', handleMouseEnter);
      node.removeEventListener('mouseleave', handleMouseLeave);
    };
  }, []);

  return [ref, hovering];
}

4.2 在 CatImages 中使用该 Hook

// CatImagesWithHook.tsx
import { useLoader } from './useLoader'; // a hook version of the loader logic
import { useHover } from './useHover';

function CatImagesWithHook() {
  const [ref, hovering] = useHover();
  const { data, loading } = useLoader('https://api.thecatapi.com/v1/images/search?limit=10');

  if (loading) return Loading…;

  return (
    
      {hovering && Hovering}
      

        {data.map((cat: any) => (
          
            
          
        ))}
      

    
  );
}

使用 Hook 的方式消除了 HOC 引入的额外组件层级,使组件树更加扁平。

5️⃣ 何时更倾向使用 HOC 与 Hook

  • HOC(高阶组件)非常适合处理 横切关注点,需要兼容 任何 组件类型(类组件或函数组件),并且希望保持被包装组件的 API 不变的场景。
  • Hook 通常是处理 有状态逻辑 的首选现代方案,因为它们可以避免因大量嵌套 HOC 而产生的 “包装地狱”。

React Docs: “在大多数情况下,Hook 足以满足需求,并且可以帮助减少树中的嵌套。”

6️⃣ 示例:深度嵌套的 HOC(用于说明)


  
    
      
    
  

相同的行为通常可以通过组合 hooks 和 context 提供者更简洁地表达。

TL;DR

  • HOCs 让你向组件注入 props/逻辑(例如 withStyleswithLoaderwithHover)。
  • 你可以 组合 多个 HOC 来构建复杂的行为。
  • HooksuseHoveruseLoader)为大多数情况提供了现代的、层级更少的替代方案。

请随意将上述代码片段复制到你的项目中,并根据需要调整类型!

布局示例

Hook 与 高阶组件 (HOC) 的比较

直接在组件中添加 Hook 意味着我们不再需要将组件包装在 HOC 中。
使用 HOC 可以让相同的逻辑提供给多个组件,同时将该逻辑集中在一个地方。然而,Hook 允许我们在组件内部添加自定义行为,这在多个组件依赖该行为时,可能会增加引入错误的风险,相较于 HOC 模式。

高阶组件 的最佳使用场景

  • 组件可以独立运行,无需额外的自定义逻辑。

Hook 的最佳使用场景

  • 该行为并未在整个应用中广泛传播;只有一个或少数几个组件使用它。
  • 该行为为组件添加了多个属性。
Back to Blog

相关文章

阅读更多 »

React 峰会 2026

React Summit 2026 的封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.a...