```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>;
}

解决方案:

  1. 正确声明依赖: 将 count 加入依赖数组,但需注意这会导致每次 count 变化时重新创建定时器:

    useEffect(() => {
      const id = setInterval(() => {
        console.log(count);
      }, 1000);
      return () => clearInterval(id);
    }, [count]); // ✅ 但可能频繁重设定时器
    
  2. 使用函数式更新或 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>;
    }
    
  3. 使用 useReducer 或自定义 Hook 封装复杂逻辑。

核心原则: useEffect 的依赖必须完整反映其内部使用的响应式值。若需长期运行的副作用(如 WebSocket、定时器)并访问最新状态,推荐使用 ref 同步状态,或重构逻辑避免在 effect 中直接依赖易变状态。

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

昵称:
邮箱:
内容: