5月28日 03:32
React 组件抽离公共逻辑代码有哪些方式?
React 逻辑复用经历了三代方案的演进:Mixin → HOC / Render Props → Hooks。Mixin 已随 Class 组件淘汰,当前面试重点在后面三种。
HOC(高阶组件)
函数接受一个组件,返回增强后的新组件:
jsxfunction withAuth(WrappedComponent) { return function AuthComponent(props) { const isAuthenticated = checkAuth(); return isAuthenticated ? <WrappedComponent {...props} /> : <Navigate to="/login" />; }; } // 使用 const ProtectedPage = withAuth(Dashboard);
核心问题:
- Wrapper Hell:多层 HOC 嵌套后,DevTools 里组件树极深,调试困难
- Props 来源不透明:
<WrappedComponent {...props} />透传的 props 来自哪里不直观,容易命名冲突 - 静态方法丢失:HOC 返回新组件,原组件的静态方法不会自动复制,需要
hoist-non-react-statics手动提升 - Ref 丢失:ref 不属于 props,会被绑定到外层 HOC 组件而非原组件,需配合
React.forwardRef转发
Render Props
组件接受一个返回 React 元素的函数 prop,由该函数决定渲染内容:
jsxfunction Mouse({ render }) { const [pos, setPos] = useState({ x: 0, y: 0 }); useEffect(() => { const handler = (e) => setPos({ x: e.clientX, y: e.clientY }); window.addEventListener(mousemove, handler); return () => window.removeEventListener(mousemove, handler); }, []); return render(pos); } // 使用 <Mouse render={pos => <Cursor pos={pos} />} />
核心问题:
- 嵌套地狱:多个 Render Props 嵌套时,回调层级极深,可读性急剧下降
- 性能隐患:每次父组件渲染,render 函数都会重新创建,导致子组件不必要的重渲染,需要额外做
useCallback优化
Hooks(推荐)
在函数组件内调用自定义 Hook,逻辑与 UI 完全分离,无组件层级嵌套:
jsxfunction useAuth() { const [user, setUser] = useState(null); useEffect(() => { const unsub = onAuthStateChanged(setUser); return unsub; }, []); return user; } // 使用 function Dashboard() { const user = useAuth(); if (!user) return <Navigate to="/login" />; return <main>...</main>; }
Hooks 的注意事项:
- 不能在条件语句、循环或嵌套函数中调用——React 依靠调用顺序匹配 Fiber 链表上的 Hook 节点
- 闭包陷阱:
useEffect内部如果引用了 state 但未加入依赖数组,回调中捕获的始终是旧值,需用useRef或函数式更新setState(prev => prev + 1)解决
三种方案对比
| 维度 | HOC | Render Props | Hooks |
|---|---|---|---|
| 组件嵌套 | 多层包裹 | 回调嵌套 | 无嵌套 |
| Props 透明度 | 来源不透明 | 显式传递 | 显式调用 |
| 类型推导 | 困难(泛型丢失) | 较好 | 好 |
| 适用场景 | 旧代码维护、Class 组件 | 旧代码维护 | 新代码首选 |
三种方式的核心思想一致——把可复用逻辑从 UI 中分离。Hooks 胜在零组件嵌套、逻辑内聚、类型友好,是当前最佳实践。
追问
为什么 Hooks 不能放在条件语句里?
React 用 Fiber 节点上的链表结构存储 Hook 状态。每次渲染时,Hook 按调用顺序依次匹配链表上的节点。如果某个 Hook 在某次渲染被跳过,后续 Hook 就会错位匹配到前一个 Hook 的状态节点,导致状态混乱。这是 React 内部实现机制决定的,而非 API 设计限制。
HOC 还用在哪些场景?
React.memo(性能优化,浅比较 props)connect(mapStateToProps, mapDispatchToProps)(Redux v5 以前)withRouter(React Router v5)- 权限控制:
withAuth(ProtectedComponent) - 日志/埋点:
withTracker(InteractiveComponent)
如何把 Class 组件中的 HOC 迁移到 Hooks?
| HOC 模式 | Hooks 替代 |
|---|---|
withRouter | useNavigate() + useLocation() + useParams() |
connect() | useSelector() + useDispatch() |
withAuth | 自定义 useAuth() |
withTracker | 自定义 useTracker() + useEffect |
| 通用 HOC | 自定义 Hook + 组件内直接调用 |
Hooks 有哪些常见陷阱?
- 闭包陷阱:
useEffect中引用了 state 但依赖数组遗漏,回调拿到旧值。用useRef存最新值或函数式更新解决 - 无限渲染:
useEffect依赖项传入每次新建的对象/数组引用,用useMemo稳定引用 - 依赖缺失:遗漏依赖导致 effect 不按预期执行,启用
eslint-plugin-react-hooks的exhaustive-deps规则自动检查