This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/4. 进阶篇/12-HOC 与 Render Props.md
T
2026-04-29 22:19:51 +08:00

5.6 KiB
Raw Blame History

tags, create time
tags create time
React
HOC
Render Props
Pattern
Frontend
2026-04-29 22:11

HOC 与 Render Props

概述

Hooks 出现之前,HOC(高阶组件)和 Render Props 是 React 中复用它逻辑的两种主要模式。理解它们的原理、适用场景和局限,有助于阅读遗留代码并理解为什么 Hooks 成为更优解。

HOC(高阶组件)

概念

graph LR
    A["原始组件 Component"] -->|"注入 props"| B["HOC 函数"]
    B --> C["增强组件 EnhancedComponent"]
    C --> D["获得额外能力:日志/权限/数据"]
    
    style B fill:#F5A87D,color:#000

HOC 是一个函数,接收组件作为参数,返回增强后的新组件:

// 基础模式
function withAuth<P extends object>(
  WrappedComponent: React.ComponentType<P>
) {
  return function WithAuth(props: P) {
    const user = useAuth();
    if (!user) return <Navigate to="/login" replace />;
    return <WrappedComponent {...props} />;
  };
}

// 使用:装饰器风格
const ProtectedPage = withAuth(function Dashboard() {
  return <h1>Dashboard</h1>;
});

// TS 类型推导:保留原始 props
interface Props { title: string }
const StyledTitle = withTheme(function Title({ title }: Props) {
  return <h1>{title}</h1>;
});  // ✅ TS 推断 Props 不变

常见 HOC 模式

// 1. 日志/HUD
function withLogger<P extends object>(Comp: React.FC<P>): React.FC<P> {
  return function LoggedComponent(props: P) {
    useEffect(() => console.log(`${Comp.name} mounted`), []);
    return <Comp {...props} />;
  };
}

// 2. 加载态封装
function withLoading<P extends object>(
  Comp: React.FC<P>,
  fetchData: () => Promise<void>
): React.FC<P> {
  return function LoadingWrapper(props: P) {
    const [loading, setLoading] = useState(true);
    useEffect(() => { fetchData().then(() => setLoading(false)); }, []);
    return loading ? <Spinner /> : <Comp {...props} />;
  };
}

// 3. Props 转换(驼峰→kebab)
function withPropsTransformer<P extends object>(
  Comp: React.FC<P>,
  transform: (p: P) => Record<string, unknown>
): React.FC {
  return function Transformed(props: P) {
    const extra = transform(props);
    return <Comp {...props as any} {...extra} />;
  };
}

HOC 的局限性

问题 说明
Static 属性丢失 withAuth(Page) 返回的新组件没有 Page.getInitialProps
Wrapper Hell withAuth(withLogging(withData(Page)))——嵌套过深调试困难
Props 冲突 多个 HOC 都注入 data prop,后一个覆盖前一个
ref 丢失 直接传 ref 给增强组件会报错(需用 forwardRef 包装)

[!tip] HOC 兼容 ref

export function withAuth<P>(Wrapped: React.ComponentType<P>) {
  return React.forwardRef<HTMLDivElement, P>((props, ref) => {
    const { authenticated } = useAuth();
    if (!authenticated) return <Navigate to="/login" />;
    return <Wrapped {...props} ref={ref} />;
  });
}

Render Props

核心思想

通过 prop 传递一个函数,该函数返回 JSX——将 UI 渲染逻辑委托给调用方。

interface MouseTrackerProps {
  render: (position: { x: number; y: number }) => React.ReactNode;
}

function MouseTracker({ render }: MouseTrackerProps) {
  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);
  }, []);
  
  // 🎯 关键:render 函数返回什么就渲染什么
  return <div>{render(pos)}</div>;
}

// 使用
<MouseTracker render={({ x, y }) => (
  <div>Cursor at ({x}, {y})</div>
)} />;

等价于 children 的情况

// 当 render prop 只是把数据传给 children 时,可以用 children 替代
function DataProvider({ children }: { children: (data: Data) => React.ReactNode }) {
  const data = useDatabase();
  return <>{children(data)}</>;
}

<DataProvider>
  {(data) => <Table items={data.items} />}
</DataProvider>;

HOC vs Render Props vs Custom Hook

graph TB
    A["逻辑复用需求"] --> B["方案对比"]
    
    B --> C["HOC"]
    B --> D["Render Props"]
    B --> E["Custom Hook"]
    
    C --> F["⚠️ Wrapper 嵌套深"]
    C --> G["⚠️ 静态方法丢失"]
    C --> H["✅ 不修改原组件结构"]
    
    D --> I["⚠️ 回调地狱"]
    D --> J["⚠️ Prop 命名冲突风险"]
    D --> K["✅ 灵活的 UI 控制"]
    
    E --> L["✅ 简洁直观"]
    E --> M["✅ 可直接操作 state / effect"]
    E --> N["✅ 无 wrapper 嵌套"]
    E --> O["❌ 只能用于组件内部"]
    
    style L fill:#4FC08D,color:#fff
    style M fill:#4FC08D,color:#fff
    style N fill:#4FC08D,color:#fff

为什么 Hooks 取代了它们?

// ❌ HOC 方式
const ConnectedUserList = withAuth(withCache(withPagination(UserList)));
// 三层嵌套 → 调试困难、性能不可见、type 推导混乱

// ✅ Hook 方式
function UserList() {
  useAuth();                          // 身份验证
  const cache = useCache<User[]>();   // 数据缓存
  const pagination = usePagination(); // 分页管理
  
  return <table>{/* ... */}</table>;
}
// 扁平可读、天然共享 state、TS 完美推断

[!note] HOC 和 Render Props 真的被淘汰了吗?

  • HOC:在需要包裹组件但不修改其内部的场景仍有价值(如第三方库封装)
  • Render Props:当父组件需要完全控制子组件的渲染内容时仍然有用
  • 但 90%+ 的场景,Custom Hook 是更好的选择

关联笔记