闭包陷阱
约 418 字大约 1 分钟
布欧-Lewyon
2026-05-15
问题:陈旧的闭包
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => {
console.log(count) // 始终是 0(闭包捕获了初始值)
setCount(count + 1) // 始终 setCount(0 + 1) = 1
}, 1000)
return () => clearInterval(id)
}, []) // 空依赖只执行一次
}解决方案
// 方案一:函数式更新
useEffect(() => {
const id = setInterval(() => {
setCount(prev => prev + 1) // prev 永远是最新值
}, 1000)
return () => clearInterval(id)
}, [])
// 方案二:在依赖中跟踪
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => clearInterval(id)
}, [count]) // count 变化时重建定时器
// 方案三:useRef 保存最新值
const countRef = useRef(count)
countRef.current = count // 每次都更新 ref
useEffect(() => {
const id = setInterval(() => {
console.log(countRef.current) // 读取 ref 的最新值
}, 1000)
return () => clearInterval(id)
}, [])useEffect 依赖遗漏
function SearchPage({ userId }: { userId: string }) {
const [query, setQuery] = useState('')
// ❌ 闭包陷阱:userId 变化时不会重新搜索
useEffect(() => {
fetchResults(userId, query) // eslint: missing deps
}, [])
// ✅ 正确
useEffect(() => {
fetchResults(userId, query)
}, [userId, query])
}useCallback 闭包陷阱
function Parent() {
const [count, setCount] = useState(0)
// ❌ 闭包陷阱:count 被冻结在创建时
const logCount = useCallback(() => {
console.log(count) // 始终显示创建时的值
}, []) // 空依赖
// ✅ 正确
const logCount = useCallback(() => {
console.log(count)
}, [count])
// ✅ 使用 ref 避免依赖
const countRef = useRef(count)
countRef.current = count
const logCount = useCallback(() => {
console.log(countRef.current) // 永远是最新值
}, [])
}小结
- 闭包捕获的是创建时的作用域变量值,而非最新值。
- 函数式更新
setCount(prev => prev + 1)解决 setState 闭包问题。 useRef保存最新值,跨越闭包边界读取。eslint-plugin-react-hooks的exhaustive-deps规则帮发现闭包陷阱。- useCallback 空依赖 + ref 是最优的"稳定回调"方案。
上一节:自定义 Hook 下一节:Context + useReducer
