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);
}, []);
相关推荐
ℋᙚᵐⁱᒻᵉ鲸落2 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
默_笙3 小时前
🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城
前端·javascript
前端snow4 小时前
ai agent --- 实现 openclaw 定时间效果
前端
码云之上4 小时前
换模型之后,Chatbot 为什么要自己做 compact?
前端·agent·前端工程化
এ慕ོ冬℘゜4 小时前
navigator 导航对象 BOM制作方法
javascript
星栈5 小时前
自定义 UI-UX-Pro-Max 的 CSV 知识库,把 AI 生成的后台拉回行业该有的样子
前端·agent·weui
研☆香5 小时前
简单的图片上传 删除 预览
前端
ITresearchGuest5 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·人工智能·信息可视化
慧一居士6 小时前
Vite项目中使用Less步骤,详细使用示例
前端·css
花间相见6 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain