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);
}, []);
相关推荐
两只羊ovo13 小时前
Vite+React+TS+Tailwind搭建WebGPU本地大模型项目,拆解前端核心知识点
前端·react.js
acheding13 小时前
把 CodeMirror 6 调教成 Markdown 编辑器:扩展、装饰与门面
javascript·vue.js·编辑器·markdown
hoLzwEge14 小时前
团队协作的隐藏利器:.vscode 完全指南
前端·前端框架
labixiong14 小时前
TypeScript 7.0 编译器用 Go 重写,速度暴增10倍——背后到底做了什么?
前端·javascript·go
WaywardOne14 小时前
Flutter组件化方案(AI总结)
前端·flutter·ai编程
玉宇夕落14 小时前
原子化编程”:Tailwind CSS 核心原理
前端
是小李呀14 小时前
解决“Error: The project seems to require yarn but it‘s not installed”报错
前端
weedsfly14 小时前
一个电商价格计算案例,带你学会前端开发中的责任链模式
前端·javascript·面试
__sjfzllv___14 小时前
在职前端Leader学习/转行 AI Agent -DAY10
前端
沉迷学习日益消瘦14 小时前
8 UI 组件库与设计系统:Token 驱动 + 暗色模式 + 4 种主题色
前端