React闭包陷阱

闭包

外部访问函数内部变量,导致内部变量不被释放,一直保留

javascript 复制代码
function createCounter() {
  let count = 0;
  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 输出 1
counter(); // 输出 2

hooks闭包陷阱

是什么

在 React 函数组件中,每一次 重新渲染(re-render) 都会:

  • 重新执行整个函数组件;
  • 创建新的变量、函数、作用域;
  • 旧的闭包(closure)仍然保留旧的状态值

⚠️ 当在异步回调、事件回调或定时器中引用旧的状态值时,就会掉进闭包陷阱

举个🌰

javascript 复制代码
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Current count:', count);
      setCount(count + 1); // ⚠️ 闭包陷阱:count 永远是初始值 0
    }, 1000);
    return () => clearInterval(timer);
  }, []); // 空依赖数组,只绑定初始 count
  // ⛔ useEffect 内部的函数引用了旧的 count

  return <h2>{count}</h2>;
}

这个时候每次打印的count均为0 展示的count值保持不变

直接赋值

javascript 复制代码
import React, { useState, useEffect } from 'react';

function Counter() {
  const [id, setId] = useState(1);
  const [data, setData] = useState([]);

  const fetchData = id => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ id, title: `Title ${id}`, content: `Content ${id}` });
      }, 2000);
    });
  };

  useEffect(() => {
    fetchData(id).then(newData => {
      setData(data.concat(newData));
    });
  }, [id]);


  return (
    <>
      <ul>
        {data.map(item => (
          <li key={item.id}>data is {item.title}</li>
        ))}
      </ul>
      <button onClick={() => setId(id + 1)}>set ID </button>
    </>
  );
}

使用函数式

javascript 复制代码
import React, { useState, useEffect } from 'react';

function Counter() {
  const [id, setId] = useState(1);
  const [data, setData] = useState([]);

  const fetchData = id => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ id, title: `Title ${id}`, content: `Content ${id}` });
      }, 2000);
    });
  };

  useEffect(() => {
    fetchData(id).then(newData => {
      setData((prevData) => [...prevData, newData]);
    });
  }, [id]);


  return (
    <>
      <ul>
        {data.map(item => (
          <li key={item.id}>data is {item.title}</li>
        ))}
      </ul>
      <button onClick={() => setId(id + 1)}>set ID </button>
    </>
  );
}

为什么

React 函数组件不是持续运行的函数,而是 每次渲染都会重新执行。 所以每次渲染时:

  • count是当前那次渲染的值;

  • useEffect 回调引用的变量,取决于它创建时的那次渲染环境

  • 如果依赖数组为空 [],那 effect 只运行一次 → 永远绑定初始闭包。

对于上述例子来讲

javascript 复制代码
setCount(count + 1);

这行代码不是"立即执行 +1",而是:

  • 当前渲染时的 count 值取出来;
  • 计算 count + 1;
  • 通知 React "请在下一次渲染时,把状态设置为这个新值"。

也就是说,这句代码依赖的是当前闭包里的 count。相当于一直在执行setCount(1);所以渲染的count不会发生变化。

怎么做

函数式更新

上述情况虽然打印的一直是0,但是实际渲染的count发生了变化

javascript 复制代码
import React, { useState, useEffect } from 'react';
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Current count:', count);
       setCount(prev => prev + 1); // ✅ 使用最新状态
    }, 1000);
    return () => clearInterval(timer);
  }, []); 

  return <h2>{count}</h2>;
}
为什么这种方法可以正常渲染count

React中setState有两种写法

  • 直接赋值

    javascript 复制代码
    setCount(count + 1);
  • 函数式更新

    javascript 复制代码
    setCount(prev => prev + 1);

这两种的差异在于执行时机

  • 直接赋值

    • 表达式调用时立即执行
    • 立即计算出结果(0 + 1 = 1)并把结果交给React
    • 在闭包中count一直为0 那么用于是setCount(1)
  • 函数式更新

    • 不会立即执行

    • React将函数存储起来,稍后执行时传入最新的state,不依赖闭包中的值,每次都拿最新的

    • 个人理解为 函数式取值跳出了闭包

增加依赖

打印的count和渲染的count均会发生变化,但会导致频繁清除重建effect

javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('Current count:', count);
    setCount(count + 1);
  }, 1000);

  return () => clearInterval(timer);
}, [count]); // ✅ 每次 count 改变时重新注册 effect

useRef

javascript 复制代码
const [count, setCount] = useState(0);
const countRef = React.useRef(count);

useEffect(() => {
  countRef.current = count; // 更新 ref
}, [count]);

useEffect(() => {
  const timer = setInterval(() => {
    console.log('Current count:', countRef.current); // ✅ 读最新值
  }, 1000);
  return () => clearInterval(timer);
}, []);
相关推荐
恋猫de小郭11 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端