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

202 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<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 模式
```tsx
// 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
> ```tsx
> 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 渲染逻辑委托给调用方。
```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 <div>{render(pos)}</div>;
}
// 使用
<MouseTracker render={({ x, y }) => (
<div>Cursor at ({x}, {y})</div>
)} />;
```
### 等价于 children 的情况
```tsx
// 当 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
```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<User[]>(); // 数据缓存
const pagination = usePagination(); // 分页管理
return <table>{/* ... */}</table>;
}
// 扁平可读、天然共享 state、TS 完美推断
```
> [!note] HOC 和 Render Props 真的被淘汰了吗?
> - HOC:在需要**包裹**组件但不修改其内部的场景仍有价值(如第三方库封装)
> - Render Props:当父组件需要**完全控制子组件的渲染内容**时仍然有用
> - 但 90%+ 的场景,Custom Hook 是更好的选择
## 关联笔记