902 字
5 分钟
useEffect

Effect 是干什么的#

渲染只负责算出 JSX。订阅事件、起计时器、直接操作 DOM、发请求,这些都不属于渲染,它们要与组件外部的系统保持同步,交给 Effect。

useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000)
return () => clearInterval(id)
}, [])

一次更新里的先后顺序#

渲染 → 提交 DOM → 浏览器绘制 → 执行 Effect。

顺序里最要紧的一段是「绘制之后」。useEffect 不阻塞浏览器画屏幕,所以它在 Effect 里改样式,用户可能看到中间那一帧。要在绘制前完成的事,用 useLayoutEffect。

清理函数在下一次 Effect 执行之前运行,组件卸载时也运行一次。它拿到的变量是那一次 Effect 运行时的值,这条在下面讲请求时用得上。

清理函数要和 Effect 成对写#

每一个「开始」都要有对应的「停止」:

// 订阅
useEffect(() => {
const connection = createConnection(serverUrl, roomId)
connection.connect()
return () => connection.disconnect()
}, [roomId])
// 事件监听
useEffect(() => {
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])

漏了清理,切换依赖时会留下没关掉的连接、没移除的监听,这在开发环境会被严格模式放大成明显的错误。

请求的竞态#

快速切换 userId 时,先发的请求可能后返回,把新数据覆盖成旧数据:

useEffect(() => {
let ignore = false
async function load() {
const res = await fetch(`/api/users/${userId}`)
const data = await res.json()
if (!ignore) setUser(data)
}
load()
return () => {
ignore = true
}
}, [userId])

ignore 属于那次 Effect 运行的闭包,旧请求回来时它已经是 true,于是不写入状态。要真的把请求取消掉(省流量),用 AbortController:在 Effect 里创建,把 signal 传给 fetch,清理函数里 abort()。

这里容易写错的一点是位置:AbortController 必须建在 Effect 里、abort 放在清理函数里。反过来(在清理函数里创建再立刻 abort)取消的是一个刚创建、根本没绑上请求的对象,等于没写。

依赖数组列的是「用到了什么」#

依赖数组不是控制时机的开关,它列的是这个 Effect 用到的响应式值:props、state、组件里定义的变量与函数。漏写会读到旧值,lint 也会报。

组件里定义的函数每次渲染都是新的,所以它出现在依赖里会让 Effect 每次都跑。两种处理方式:

  • 这个函数只被 Effect 用,就把它搬进 Effect 里。
  • 这个函数表达的是「某个动作」,而不是「同步逻辑」,就改用 useEffectEvent,它让你读到最新值而不必进依赖。

靠删依赖来「少跑几次」,通常说明这段逻辑本来就放错了地方。

有些事不该用 Effect#

官方有一篇的标题就叫「你可能不需要 Effect」。四类常见情况:

  • 能从 props 或 state 算出来的值,直接在渲染时算,不必存进 state 再用 Effect 同步。
  • 用户操作触发的逻辑,写在事件处理函数里。事件发生时你就在现场,不需要事后靠状态变化去推断。
  • props 变了想重置全部 state,用 key 让组件重建,比写一堆 Effect 干净。
  • 在 Effect 里发请求能做,但框架一般有更合适的取数方案,官方也建议优先用框架提供的能力。

严格模式会跑两遍#

开发环境下,React 会把新挂载的组件卸载再挂载一次,Effect 因此跑两遍,用来暴露「没清理干净」的写法。清理函数写对了就不受影响,生产环境不会这样。

第 17 篇,共 39 篇本文属于系列

useEffect
https://www.choria.top/posts/hook-use-effect/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时