说说对高阶组件的理解?应用场景?

一、是什么

高阶组件(Higher-Order Component,简称 HOC)是 React 中复用组件逻辑的一种高级技巧。它本身不是 React API 的一部分,而是基于 React 组合特性衍生出的一种设计模式。

从定义上看,高阶组件是一个接受组件作为参数并返回新组件的函数

function withSomething(WrappedComponent: React.ComponentType) {
  return function EnhancedComponent(props: any) {
    // 增强逻辑
    return <WrappedComponent {...props} />;
  };
}

这与 JavaScript 中的高阶函数概念一致——函数接受函数作为参数或返回函数。

二、实现模式

属性代理(Props Proxy)

属性代理是最常见的 HOC 实现方式,通过包裹组件来操纵 props、抽象 state 或包装额外的 JSX。

interface WithLoadingProps {
  loading: boolean;
}

function withLoading<P extends object>(
  WrappedComponent: React.ComponentType<P>
) {
  return function WithLoadingComponent(props: P & WithLoadingProps) {
    const { loading, ...restProps } = props;

    if (loading) {
      return (
        <div className="loading-container">
          <div className="spinner" />
          <p>加载中...</p>
        </div>
      );
    }

    return <WrappedComponent {...(restProps as P)} />;
  };
}

function UserList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

const UserListWithLoading = withLoading(UserList);

// 使用
<UserListWithLoading loading={isLoading} users={users} />

注入额外 Props

function withUserInfo<P extends { user: User }>(
  WrappedComponent: React.ComponentType<P>
) {
  return function WithUserInfoComponent(props: Omit<P, 'user'>) {
    const [user, setUser] = useState<User | null>(null);

    useEffect(() => {
      fetchCurrentUser().then(setUser);
    }, []);

    if (!user) return <div>加载用户信息...</div>;

    return <WrappedComponent {...(props as any)} user={user} />;
  };
}

function Dashboard({ user }: { user: User }) {
  return <h1>欢迎回来,{user.name}</h1>;
}

const DashboardWithUser = withUserInfo(Dashboard);

// 使用时不需要传 user
<DashboardWithUser />

反向继承(Inheritance Inversion)

反向继承通过继承被包裹组件来实现,可以访问组件的 state、生命周期和 render 方法。这种模式主要用于类组件,在函数组件时代较少使用。

function withRenderTracking(WrappedComponent: typeof React.Component) {
  return class extends WrappedComponent {
    render() {
      console.log(`${WrappedComponent.name} 正在渲染`);
      return super.render();
    }
  };
}

由于反向继承与类组件深度耦合,且存在较多陷阱,现代 React 开发中不推荐使用

三、常见应用场景

权限控制

interface AuthConfig {
  requiredRole?: string;
  redirectTo?: string;
}

function withAuth<P extends object>(
  WrappedComponent: React.ComponentType<P>,
  config: AuthConfig = {}
) {
  const { requiredRole, redirectTo = '/login' } = config;

  return function AuthenticatedComponent(props: P) {
    const { user, isAuthenticated } = useAuth();

    if (!isAuthenticated) {
      return <Navigate to={redirectTo} replace />;
    }

    if (requiredRole && user?.role !== requiredRole) {
      return (
        <div className="forbidden">
          <h2>403 - 权限不足</h2>
          <p>您没有访问此页面的权限</p>
        </div>
      );
    }

    return <WrappedComponent {...props} />;
  };
}

const AdminDashboard = withAuth(Dashboard, { requiredRole: 'admin' });
const UserProfile = withAuth(Profile);

日志记录与性能追踪

function withPerformanceTracking<P extends object>(
  WrappedComponent: React.ComponentType<P>,
  componentName?: string
) {
  const displayName = componentName || WrappedComponent.displayName || WrappedComponent.name;

  return function TrackedComponent(props: P) {
    const renderStart = useRef(performance.now());

    useEffect(() => {
      const renderTime = performance.now() - renderStart.current;
      console.log(`[性能] ${displayName} 渲染耗时: ${renderTime.toFixed(2)}ms`);

      if (renderTime > 16) {
        console.warn(`[性能警告] ${displayName} 渲染超过一帧 (${renderTime.toFixed(2)}ms)`);
      }
    });

    return <WrappedComponent {...props} />;
  };
}

const TrackedUserList = withPerformanceTracking(UserList, 'UserList');

主题注入

function withTheme<P extends { theme: Theme }>(
  WrappedComponent: React.ComponentType<P>
) {
  return function ThemedComponent(props: Omit<P, 'theme'>) {
    const theme = useContext(ThemeContext);
    return <WrappedComponent {...(props as any)} theme={theme} />;
  };
}

function StyledButton({ theme, children, ...rest }: { theme: Theme; children: React.ReactNode }) {
  return (
    <button
      style={{
        backgroundColor: theme.primaryColor,
        color: theme.textColor,
        borderRadius: theme.borderRadius,
      }}
      {...rest}
    >
      {children}
    </button>
  );
}

const ThemedButton = withTheme(StyledButton);

数据获取抽象

function withDataFetching<P extends { data: any }, Q = {}>(
  WrappedComponent: React.ComponentType<P>,
  fetchFn: (params: Q) => Promise<any>
) {
  return function DataFetchingComponent(props: Omit<P, 'data'> & Q) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState<Error | null>(null);

    useEffect(() => {
      let cancelled = false;
      setLoading(true);

      fetchFn(props as any as Q)
        .then(result => {
          if (!cancelled) {
            setData(result);
            setLoading(false);
          }
        })
        .catch(err => {
          if (!cancelled) {
            setError(err);
            setLoading(false);
          }
        });

      return () => { cancelled = true; };
    }, []);

    if (loading) return <div>加载中...</div>;
    if (error) return <div>加载失败:{error.message}</div>;

    return <WrappedComponent {...(props as any)} data={data} />;
  };
}

const UserListWithData = withDataFetching(UserList, () => fetch('/api/users').then(r => r.json()));

四、编写 HOC 的约定与注意事项

1. 透传不相关的 Props

HOC 应该透传所有与自身无关的 props:

function withSomething<P extends object>(WrappedComponent: React.ComponentType<P>) {
  return function Enhanced({ extraProp, ...props }: P & { extraProp: string }) {
    // 只消费 extraProp,其余全部透传
    return <WrappedComponent {...(props as P)} />;
  };
}

2. 设置 displayName 方便调试

function withAuth<P extends object>(WrappedComponent: React.ComponentType<P>) {
  function WithAuth(props: P) {
    // ...
    return <WrappedComponent {...props} />;
  }

  const wrappedName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
  WithAuth.displayName = `withAuth(${wrappedName})`;

  return WithAuth;
}

3. 不要在 render 中使用 HOC

function App() {
  // ❌ 每次渲染都会创建新组件,导致整个子树卸载/重新挂载
  const EnhancedComponent = withSomething(MyComponent);
  return <EnhancedComponent />;
}

// ✅ 在组件外部使用
const EnhancedComponent = withSomething(MyComponent);

function App() {
  return <EnhancedComponent />;
}

4. 复制静态方法

HOC 默认不会复制被包裹组件的静态方法,可以使用 hoist-non-react-statics 库:

import hoistNonReactStatics from 'hoist-non-react-statics';

function withSomething<P extends object>(WrappedComponent: React.ComponentType<P>) {
  function Enhanced(props: P) {
    return <WrappedComponent {...props} />;
  }

  hoistNonReactStatics(Enhanced, WrappedComponent);
  return Enhanced;
}

5. 转发 Ref

HOC 默认不会转发 ref,需要使用 React.forwardRef

function withLogging<P extends object>(WrappedComponent: React.ComponentType<P>) {
  const WithLogging = forwardRef<any, P>((props, ref) => {
    useEffect(() => {
      console.log('组件已挂载');
    }, []);

    return <WrappedComponent {...props} ref={ref} />;
  });

  WithLogging.displayName = `withLogging(${WrappedComponent.displayName || WrappedComponent.name})`;
  return WithLogging;
}

五、HOC 的替代方案

自定义 Hooks(推荐)

自定义 Hooks 是目前 React 中最推荐的逻辑复用方式,相比 HOC 更加灵活和直观:

// HOC 方式
const EnhancedComponent = withAuth(withTheme(withLogging(MyComponent)));

// Hooks 方式
function MyComponent() {
  const { user, isAuthenticated } = useAuth();
  const theme = useTheme();
  useLogging('MyComponent');

  if (!isAuthenticated) return <Navigate to="/login" />;

  return <div style={{ color: theme.primaryColor }}>...</div>;
}

Render Props

Render Props 是另一种逻辑复用模式,通过函数 prop 来共享逻辑:

function MouseTracker({ render }: { render: (pos: { x: number; y: number }) => JSX.Element }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handler = (e: MouseEvent) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handler);
    return () => window.removeEventListener('mousemove', handler);
  }, []);

  return render(pos);
}

// 使用
<MouseTracker render={({ x, y }) => <p>鼠标位置:{x}, {y}</p>} />

三种方式对比

维度HOCRender Props自定义 Hooks
嵌套问题多层嵌套回调嵌套扁平调用
TypeScript 支持类型推导复杂较好最佳
调试体验组件层级深组件层级深无额外层级
灵活性静态组合动态组合最灵活
学习成本中等中等
适用场景第三方库封装动态渲染逻辑通用逻辑复用

六、总结

高阶组件是 React 中一种成熟的设计模式,在权限控制、日志记录、数据注入等场景仍然有其价值。但在现代 React 开发中,自定义 Hooks 因其更好的组合性、类型安全性和调试体验,已经成为逻辑复用的首选方案。理解 HOC 的原理和限制,有助于维护老项目代码,也有助于理解许多第三方库(如 React Router 的 withRouter、Redux 的 connect)的设计思路。