---
tags: [React, HOC, Render Props, Pattern, Frontend]
create time: 2026-04-29 22:11
---
# HOC 与 Render Props
## 概述
Hooks 出现之前,HOC(高阶组件)和 Render Props 是 React 中复用它逻辑的两种主要模式。理解它们的原理、适用场景和局限,有助于阅读遗留代码并理解为什么 Hooks 成为更优解。
## HOC(高阶组件)
### 概念
```mermaid
graph LR
A["原始组件 Component"] -->|"注入 props"| B["HOC 函数"]
B --> C["增强组件 EnhancedComponent"]
C --> D["获得额外能力:日志/权限/数据"]
style B fill:#F5A87D,color:#000
```
HOC 是一个**函数**,接收组件作为参数,返回增强后的新组件:
```tsx
// 基础模式
function withAuth
(
WrappedComponent: React.ComponentType
) {
return function WithAuth(props: P) {
const user = useAuth();
if (!user) return ;
return ;
};
}
// 使用:装饰器风格
const ProtectedPage = withAuth(function Dashboard() {
return
Dashboard
;
});
// TS 类型推导:保留原始 props
interface Props { title: string }
const StyledTitle = withTheme(function Title({ title }: Props) {
return {title}
;
}); // ✅ TS 推断 Props 不变
```
### 常见 HOC 模式
```tsx
// 1. 日志/HUD
function withLogger(Comp: React.FC
): React.FC
{
return function LoggedComponent(props: P) {
useEffect(() => console.log(`${Comp.name} mounted`), []);
return ;
};
}
// 2. 加载态封装
function withLoading
(
Comp: React.FC
,
fetchData: () => Promise
): React.FC {
return function LoadingWrapper(props: P) {
const [loading, setLoading] = useState(true);
useEffect(() => { fetchData().then(() => setLoading(false)); }, []);
return loading ? : ;
};
}
// 3. Props 转换(驼峰→kebab)
function withPropsTransformer
(
Comp: React.FC
,
transform: (p: P) => Record
): React.FC {
return function Transformed(props: P) {
const extra = transform(props);
return ;
};
}
```
### HOC 的局限性
| 问题 | 说明 |
|------|------|
| **Static 属性丢失** | `withAuth(Page)` 返回的新组件没有 Page.getInitialProps |
| **Wrapper Hell** | `withAuth(withLogging(withData(Page)))`——嵌套过深调试困难 |
| **Props 冲突** | 多个 HOC 都注入 `data` prop,后一个覆盖前一个 |
| **ref 丢失** | 直接传 ref 给增强组件会报错(需用 forwardRef 包装) |
> [!tip] HOC 兼容 ref
> ```tsx
> export function withAuth(Wrapped: React.ComponentType
) {
> return React.forwardRef((props, ref) => {
> const { authenticated } = useAuth();
> if (!authenticated) return ;
> return ;
> });
> }
> ```
## Render Props
### 核心思想
通过 prop 传递一个**函数**,该函数返回 JSX——将 UI 渲染逻辑委托给调用方。
```tsx
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 {render(pos)}
;
}
// 使用
(
Cursor at ({x}, {y})
)} />;
```
### 等价于 children 的情况
```tsx
// 当 render prop 只是把数据传给 children 时,可以用 children 替代
function DataProvider({ children }: { children: (data: Data) => React.ReactNode }) {
const data = useDatabase();
return <>{children(data)}>;
}
{(data) => }
;
```
## HOC vs Render Props vs Custom Hook
```mermaid
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 取代了它们?
```tsx
// ❌ HOC 方式
const ConnectedUserList = withAuth(withCache(withPagination(UserList)));
// 三层嵌套 → 调试困难、性能不可见、type 推导混乱
// ✅ Hook 方式
function UserList() {
useAuth(); // 身份验证
const cache = useCache(); // 数据缓存
const pagination = usePagination(); // 分页管理
return ;
}
// 扁平可读、天然共享 state、TS 完美推断
```
> [!note] HOC 和 Render Props 真的被淘汰了吗?
> - HOC:在需要**包裹**组件但不修改其内部的场景仍有价值(如第三方库封装)
> - Render Props:当父组件需要**完全控制子组件的渲染内容**时仍然有用
> - 但 90%+ 的场景,Custom Hook 是更好的选择
## 关联笔记