5.6 KiB
5.6 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
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 是更好的选择