```jsx function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { console.log(count); // 始终输出 0 }, 1000); return () => clearInterval(id); }, []); // ❌ 依赖为空,count 被闭包锁定为初始值 return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>; } ``` **解决方案:** 1. **正确声明依赖:** 将 count 加入依赖数组,但需注意这会导致每次 count 变化时重新创建定时器: ```jsx useEffect(() => { const id = setInterval(() => { console.log(count); }, 1000); return () => clearInterval(id); }, [count]); // ✅ 但可能频繁重设定时器 ``` 2. **使用函数式更新或 ref 缓存最新值:** 利用 useRef 保存最新 count,避免重设定时器: ```jsx function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); countRef.current = count; // 每次 render 更新 ref useEffect(() => { const id = setInterval(() => { console.log(countRef.current); // 总是最新值 }, 1000); return () => clearInterval(id); }, []); // ✅ 仅挂载时执行一次 return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>; } ``` 3. **使用 useReducer 或自定义 Hook 封装复杂逻辑。** **核心原则:** useEffect 的依赖必须完整反映其内部使用的响应式值。若需长期运行的副作用(如 WebSocket、定时器)并访问最新状态,推荐使用 ref 同步状态,或重构逻辑避免在 effect 中直接依赖易变状态。
在使用 React 的 useEffect 时,为什么有时依赖数组为空([])会导致闭包陷阱(stale closure)问题?如何正确解决?
回答与解析:
当 useEffect 的依赖数组为空([])时,该 effect 仅在组件首次挂载时执行一次。此时 effect 内部捕获的 props 或 state 值是初始值,并在其整个生命周期内保持不变(形成“闭包”)。如果后续状态更新但 effect 没有重新运行(因为依赖未更新),effect 内部使用的状态就会“过时”(stale),导致逻辑错误。
示例问题:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count); // 始终输出 0
}, 1000);
return () => clearInterval(id);
}, []); // ❌ 依赖为空,count 被闭包锁定为初始值
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
解决方案:
-
正确声明依赖: 将 count 加入依赖数组,但需注意这会导致每次 count 变化时重新创建定时器:
useEffect(() => { const id = setInterval(() => { console.log(count); }, 1000); return () => clearInterval(id); }, [count]); // ✅ 但可能频繁重设定时器 -
使用函数式更新或 ref 缓存最新值: 利用 useRef 保存最新 count,避免重设定时器:
function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); countRef.current = count; // 每次 render 更新 ref useEffect(() => { const id = setInterval(() => { console.log(countRef.current); // 总是最新值 }, 1000); return () => clearInterval(id); }, []); // ✅ 仅挂载时执行一次 return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>; } -
使用 useReducer 或自定义 Hook 封装复杂逻辑。
核心原则: useEffect 的依赖必须完整反映其内部使用的响应式值。若需长期运行的副作用(如 WebSocket、定时器)并访问最新状态,推荐使用 ref 同步状态,或重构逻辑避免在 effect 中直接依赖易变状态。

发表评论 (审核通过后显示评论):