你可能遇過這樣的場景:在 useEffect
、setTimeout
、或 setInterval
的 callback 裡,讀到的 state / props 好像老是某種舊值,更新後還是那個值沒變。這不是 React 壞,而是因為在那次 render 裡,callback 已經「捕捉」了那時的值。今天我們來釐清這個捕捉機制,以及怎麼避免拿到舊值。
在 React 中,每次 render 執行時,component 的 props 和 state 在那一輪裡是被固定使用的。當你在該輪 render 裡定義 callback(例如在 useEffect
、setInterval
、setTimeout
、事件處理器內部),那個 callback 會“捕捉”當時那一輪的 props / state 值。即使之後 component rerender,那個 callback 裡看到的值仍是最初被捕捉的那一輪所用的值。
換句話說:callback 定義時讀到的 props / state,是那一次 render 的值,不會自動跟後續更新同步。
setInterval
捕捉舊值function Counter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => {
// 這裡的 callback 捕捉了 count = 0(初始 render 的值)
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []); // 空依賴
return <div>Count: {count}</div>;
}
即使 count
持續更新,那個 interval callback 還是用最初被捕捉的 count = 0
去計算。
function Counter() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => {
// 使用 functional updater,不依賴 closure 捕捉的 count
setCount(c => c + 1);
}, 1000);
return () => clearInterval(id);
}, []);
return <div>Count: {count}</div>;
}
這裡 callback 裡的邏輯不是使用 closure 裡的那個 count
,而是 React 每次提供的最新值 c
。所以即使 callback 捕捉舊的 closure,也能正確更新。
另一種變體是用 ref
保存 callback:
function Counter() {
const [count, setCount] = React.useState(0);
const savedCallback = React.useRef();
React.useEffect(() => {
savedCallback.current = () => {
setCount(c => c + 1);
};
});
React.useEffect(() => {
function tick() {
savedCallback.current();
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
return <div>Count: {count}</div>;
}
這樣就把 callback 的定義與 interval 執行分開,避免捕捉舊值的問題。
useEffect(fn, [])
就自動同步最新 state → 事實上 callback 捕捉的是該次 render 的值。function Foo() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => {
console.log("Later:", count);
}, 2000);
return () => clearInterval(id);
}, []);
return <button onClick={() => setCount(c => c + 1)}>+1</button>;
}
若你點按讓 count
變成 5,那麼幾秒後 console.log("Later:", count)
印出的值是什麼?為什麼?
答案: 它會印出那次定時器 callback 被創建時讀到的值,例如 0,而不是後續更新後的值,因為 callback 捕捉的是當時那輪 render 的 props / state。
中文版
在 React 裡,每次 render 執行時,props 和 state 都會被 callback(像是在
useEffect
或setInterval
裡定義的函式)讀取並「捕捉(capture)」當下的值。那個 callback 後續永遠都是用被捕捉的值,不會自動看到後續更新的 state 或 props。使用 functional updater 或 refs 可以讓 callback 能拿到最新的狀態,而不是用舊捕捉到的值。
英文版
In React, when a render runs, props and state are read and “captured” by callbacks (like in
useEffect
orsetInterval
) at that moment. That callback will always refer to those captured values — it doesn’t automatically see later updates. Using functional updater or refs helps ensure the callback works with the current state, not old captured values.