跳到主要内容

1 篇博文 含有标签「Hooks」

查看所有标签

useEffectEvent:把"非响应式逻辑"从 Effect 里抽出来

· 阅读需 20 分钟

在 React 里,你迟早会遇到这么个两头堵的问题:Effect 里要读某个最新的 props / state,但又不想它一变就让 Effect 重跑。加进依赖数组吧,副作用会过度执行(反复重连、反复重置定时器);不加吧,闭包读到的永远是"过期的旧值",而且 ESLint 还会红字报警。

React 19.2(2025 年 10 月转正)带来了专门解决这个问题的 useEffectEvent。但要真正理解它为什么长这样、限制为什么那么多,得从 React 最底层的心智模型——不可变(Immutable)快照——说起。


1. 从 Immutable 说起:每次渲染都是一份"照片"

React 的 props 和 state 是不可变的。这句话的意思是:更新 state 不是"把旧值改掉",而是丢弃旧快照、生成一个新快照、然后带着新快照重新渲染一次

这个模型带来一个极其关键的推论:组件体里的每一个闭包,捕获的都是"这一次渲染"的快照值。

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
const id = setTimeout(() => {
console.log(count); // 打印的是"创建 effect 时"的 count,不是最新的
}, 1000);
return () => clearTimeout(id);
}, []);

return <button onClick={() => setCount(c => c + 1)}>+1</button>;
}

上面这个例子里,不管点多少次按钮,setTimeout 回调里打印的 count 都是第一次渲染时的值(0)。因为 useEffect 的回调是在"渲染 #1"里创建的,它捕获的 count 就是渲染 #1 的快照——之后 state 变了,组件重新渲染,但旧闭包不会"自动看到"新值。

对比 Vue 的响应式模型会更容易看清这个差异:Vue 的 ref / reactive可变的代理,effect 里读 .value 永远是"运行时的当前值"(依赖收集 + 追踪);而 React 的值在渲染那一刻就是定死的,不存在"活的引用"这种东西。

所以 React 里"在 effect 里读最新值"天然是个难题——这就是 useEffectEvent 存在的全部理由。