704 字
4 分钟
useEffectEvent

要解决的问题#

计时器、订阅、事件监听的回调里经常要读最新的 props 或 state,但那些值一变,又不该把连接断开重来。

看官方那个计数器的例子:每秒把 count 加上当前的 increment。increment 会被用户改动,但它一变就重启计时器显然不对。

import { useState, useEffect, useEffectEvent } from 'react'
function Timer() {
const [count, setCount] = useState(0)
const [increment, setIncrement] = useState(1)
const onTick = useEffectEvent(() => {
setCount(count + increment)
})
useEffect(() => {
const id = setInterval(() => onTick(), 1000)
return () => clearInterval(id)
}, [])
return (
<>
<p>计数:{count}</p>
<button onClick={() => setIncrement(i => i + 1)}>每次加 {increment}</button>
</>
)
}

onTick 读到的是最新的 count 与 increment,但它不进依赖数组,所以计时器只在挂载时建一次,读取的值却始终是新的。

它是什么#

useEffectEvent(callback) 返回一个函数。它属于 Effect 的逻辑,但行为更像事件处理函数:调用时总能读到最近一次渲染提交的值。

19.2 起它转为稳定(React 19.2)。

四条规则#

只能在 Effect 里调用。useEffect、useLayoutEffect、useInsertionEffect,或者同一组件里另一个 Effect Event 内部。渲染期间调用、或者把它当回调传给别的组件,都会报错,lint 也会拦。

不要用它来少写依赖。这条最要紧。如果某个值变化时 Effect 本来就该重跑,就把它写进依赖数组;把它藏进 Effect Event 只是把 bug 藏得更深。官方的原话是:这个 Hook 只给「确实不该触发 Effect 重跑」的逻辑用。

它的函数身份每次渲染都会变,这是有意设计。所以永远不要把它放进依赖数组:稳定身份反而会掩盖问题,而不稳定的身份会让写错的地方立刻表现为「Effect 每次都重跑」。

只能在组件或自定义 Hook 的顶层调用它。

自定义 Hook 里也能用#

function useInterval(callback, delay) {
const onTick = useEffectEvent(callback)
useEffect(() => {
const id = setInterval(() => onTick(), delay)
return () => clearInterval(id)
}, [delay])
}

传进来的 callback 每次渲染都是新函数,但 onTick 不进依赖,计时器只在 delay 变化时重建。这是封装计时器、订阅类 Hook 的标准写法。

与 useCallback 的分工#

useCallback 稳住引用,用在「这个函数要传出去」的场合:给子组件当 props、给别的 Hook 当依赖。

useEffectEvent 反过来,它刻意不稳定,只在本组件的 Effect 里用。两者不能互换:把 Effect Event 传给子组件会报错,用它去当依赖数组里的项也会被 lint 拦下。

与过去那个 ref 土办法对比#

在它出现之前,常见做法是用 ref 存最新值,Effect 里读 ref.current:

const latest = useRef(count)
useEffect(() => {
latest.current = count
})

能用,但要多写一个 Effect、多一个 ref,还容易漏更新。useEffectEvent 把这件事收成一行。

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

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

分享文章

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

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

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

部分内容可能已过时