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);
}, []);
相关推荐
中工钱袋12 分钟前
前端请求到底是从哪里发出去的?
前端
じòぴé南冸じょうげん3 小时前
若依框架favicon.ico缓存更新问题解决方案:本地生效但线上未更新
前端·javascript·前端框架·html
狮子座的男孩3 小时前
js基础高级:01、数据类型(typeof、instanceof、===的使用)、数据与变量与内存(定义、赋值与内存关系、引用变量赋值、js调函数传参)
前端·javascript·经验分享·数据类型·数据与变量与内存·赋值与内存关系·引用变量赋值
Cyclo-6 小时前
PDFJS 在React中的引入 使用组件打开文件流PDF
前端·react.js·pdf
椒盐螺丝钉8 小时前
Vue Router应用:组件跳转
前端·javascript·vue.js
顾安r8 小时前
11.20 开源APP
服务器·前端·javascript·python·css3
敲敲了个代码9 小时前
CSS 像素≠物理像素:0.5px 效果的核心密码是什么?
前端·javascript·css·学习·面试
少云清9 小时前
【软件测试】5_基础知识 _CSS
前端·css·tensorflow
倔强青铜三10 小时前
AI编程革命:React + shadcn/ui 将终结前端框架之战
前端·人工智能·ai编程
二川bro10 小时前
第57节:Three.js企业级应用架构
开发语言·javascript·架构