5月28日 03:32

React 组件抽离公共逻辑代码有哪些方式?

React 逻辑复用经历了三代方案的演进:Mixin → HOC / Render Props → Hooks。Mixin 已随 Class 组件淘汰,当前面试重点在后面三种。

HOC(高阶组件)

函数接受一个组件,返回增强后的新组件:

jsx
function 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,由该函数决定渲染内容:

jsx
function 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 完全分离,无组件层级嵌套:

jsx
function 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) 解决

三种方案对比

维度HOCRender PropsHooks
组件嵌套多层包裹回调嵌套无嵌套
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 替代
withRouteruseNavigate() + useLocation() + useParams()
connect()useSelector() + useDispatch()
withAuth自定义 useAuth()
withTracker自定义 useTracker() + useEffect
通用 HOC自定义 Hook + 组件内直接调用

Hooks 有哪些常见陷阱?

  1. 闭包陷阱useEffect 中引用了 state 但依赖数组遗漏,回调拿到旧值。用 useRef 存最新值或函数式更新解决
  2. 无限渲染useEffect 依赖项传入每次新建的对象/数组引用,用 useMemo 稳定引用
  3. 依赖缺失:遗漏依赖导致 effect 不按预期执行,启用 eslint-plugin-react-hooksexhaustive-deps 规则自动检查
标签:React前端