闭包
外部访问函数内部变量,导致内部变量不被释放,一直保留
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有两种写法
-
直接赋值
javascriptsetCount(count + 1); -
函数式更新
javascriptsetCount(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);
}, []);
