返回专题首页

React 专题

useEffect、useRef 与副作用管理:避免把 Hook 用成黑盒

React 学习里最容易走偏的一个点,就是把 `useEffect` 和 `useRef` 当成“遇到问题就能兜底的工具”。一旦组件逻辑复杂,很多人会本能地:

React 专题第 06 篇 / 26 篇5 分钟

React 学习里最容易走偏的一个点,就是把 useEffectuseRef 当成“遇到问题就能兜底的工具”。一旦组件逻辑复杂,很多人会本能地:

  • 需要同步一点东西就写 useEffect
  • 需要记点值就写 useRef
  • 页面行为不稳定了再补几个依赖项。

结果代码能跑,但没人说得清副作用为什么在这里、什么时候执行、什么时候应该清理。这一篇真正要讲的,不是 Hook 语法,而是 React 里的副作用治理。

什么是副作用?

从 React 项目视角看,凡是“不是单纯根据 state 和 props 计算 UI”的动作,都可以视为副作用,比如:

  • 发请求;
  • 操作 DOM;
  • 订阅事件;
  • 设置定时器;
  • 与外部系统同步;
  • 手动写日志、埋点或本地缓存。

副作用本身不可避免,问题从来不是“有没有副作用”,而是副作用是否出现在正确的边界里。

useEffect 真正适合做什么?

useEffect 更适合承接那些“在渲染结果确认之后,再和外部世界发生协作”的逻辑。它的关键不是“组件挂载后执行”,而是:

  • 这段逻辑是不是副作用;
  • 它依赖哪些输入;
  • 输入变化后是否真的应该重新执行;
  • 是否需要清理。

如果这些问题不先想清楚,Effect 很快就会从“副作用边界”退化成“逻辑垃圾桶”。

为什么很多人会把 useEffect 用成黑盒?

因为它看起来很万能:能发请求、能监听、能同步、能清理。于是很多本来不该出现在 Effect 里的逻辑也被塞进去,比如:

  • 本该是派生值的内容;
  • 只是为了把两个 state 硬同步起来;
  • 某些明明可以在事件里直接完成的动作;
  • 某些只是因为没想清楚状态归属而出现的补丁逻辑。

这也是为什么 React 项目里最典型的坏味道之一,就是 Effect 越写越多、依赖项越补越乱。

useRef 真正适合什么?

useRef 的核心价值,不是“一个不会触发重渲的变量”,而是:

  • 保存 DOM 引用;
  • 保存某些不参与渲染、但需要跨渲染周期保留的值;
  • 在副作用逻辑里保持稳定引用;
  • 避免不必要的状态更新。

它适合那些“组件内部需要记住,但变化后不应该推动界面更新”的东西。比如:

  • 某个定时器 ID;
  • 某个上一次值;
  • 某个第三方实例引用;
  • 某个 DOM 节点句柄。

如果你把所有“懒得理状态”的值都放进 useRef,那只是把问题藏起来,不是真正解决边界问题。

为什么副作用管理一定要和清理一起看?

因为很多 React 页面的问题不在于副作用没执行,而在于它执行完了却没结束。典型问题包括:

  • 页面离开后事件监听还在;
  • 请求返回时组件早已不在当前上下文;
  • 定时器还在继续跑;
  • 某些外部实例没有销毁;
  • 旧的同步逻辑继续影响新状态。

所以 Effect 的真正难点,不是写那一段逻辑,而是同时负责它的生命周期。

依赖项为什么总让人头疼?

因为依赖项本质上是在回答:这段副作用到底受哪些输入控制。也就是说,它不是“让 ESLint 别报错”的列表,而是在表达逻辑边界。

一旦依赖项没有想清楚,常见后果就是:

  • 该更新时没更新;
  • 不该重跑时频繁重跑;
  • 闭包拿到旧值;
  • 为了压错误提示去手动跳过依赖,最后留下更隐蔽的问题。

所以依赖项不是写法细节,而是副作用语义的一部分。

把 Effect 和 Ref 放回项目里,最重要的判断是什么?

更稳的顺序通常是:

1. 先问这段逻辑是不是副作用;2. 如果不是副作用,优先别进 Effect;3. 如果是副作用,再明确它依赖什么输入;4. 看看它是否需要清理;5. 只有当某个值不该触发重渲时,再考虑用 useRef

这个顺序非常重要。很多 React 代码混乱,不是因为 Hook 太难,而是因为顺序反了,先写 Effect,再补意义。

最常见的几个误区

1. 用 useEffect 同步本地派生状态

很多时候这类逻辑其实应该直接计算。

2. 为了绕开重渲,把状态偷塞进 useRef

这样只是在制造不可见状态。

3. 依赖项看着烦就压掉

会留下闭包和时序问题。

4. 副作用只管执行,不管清理

页面切换和复杂交互里非常容易出问题。

总结

useEffectuseRef 真正重要的,不是会不会写,而是你是否能用它们把副作用边界表达清楚。Effect 负责和外部世界协作,Ref 负责保存不应触发渲染的稳定引用,二者都必须围绕“依赖、时机、清理、可解释性”来使用。只要你始终先判断逻辑性质,再决定是否上 Hook,副作用管理就会稳很多。