React组件渲染/重渲染困惑:局部变量i的异常行为解析
React局部变量的闭包陷阱:按钮点击数值异常解析
问题现象
点击「Change the alphabets」按钮时,第一次显示pqrts6,连续快速点击两次后显示pqrts7,等组件更新完成后再点击又回到pqrts6。原本误以为React会保留局部变量i的值,应该始终显示pqrts6,实际行为却不符合预期。
组件代码
import { useState, useReducer } from 'react' const countReducer = (state, newState) => ({ ...state, ...newState }) function Counter({ initialCount = 0, step = 1 }) { const [me, setMe] = useState('ABc'); const [state, setState] = useReducer(countReducer, { count: initialCount }) const count = state.count let i = 5; console.log('i->' + i) const changeVal = () => { console.log('curre val ' + me) console.log('i val inside**' + i) i = i + 1 setMe('pqrts' + i) } console.log('i->after ' + i) console.log('next val ' + me) const increment = () => setState({ count: count + step }) return ( <> <button onClick={changeVal}>Change the alphabets: {me}</button> <button onClick={increment}>{count}</button> </> ) }
问题根源
- 组件渲染时局部变量会重置:React组件每次状态更新(比如调用
setMe)都会重新执行整个组件函数,每次执行时let i=5都会重新声明并赋值为5,局部变量不会在不同渲染周期之间保留值。 - 闭包的捕获特性:
changeVal是组件每次渲染时创建的闭包函数,它会捕获当前渲染周期里i的引用:- 首次点击:调用初始渲染生成的
changeVal,i从5变为6,调用setMe触发组件重新渲染。如果此时快速连续点击第二次,组件还没完成重新渲染,仍然调用同一个changeVal,此时i已经是6,再加1变成7,setMe后组件渲染显示pqrts7。 - 组件完成重新渲染后,新的
changeVal会捕获新的i=5,此时点击就会把i变成6,显示pqrts6,如此循环。
- 首次点击:调用初始渲染生成的
解决方案
如果需要在渲染之间保留变量值,不能用普通局部变量,推荐用useState或useRef:
方案1:使用useState
通过状态管理变量,确保每次更新都基于最新值:
import { useState, useReducer } from 'react' const countReducer = (state, newState) => ({ ...state, ...newState }) function Counter({ initialCount = 0, step = 1 }) { const [me, setMe] = useState('ABc'); const [state, setState] = useReducer(countReducer, { count: initialCount }) const count = state.count // 用useState保存i的值 const [i, setI] = useState(5); const changeVal = () => { setI(prevI => { const newI = prevI + 1; setMe('pqrts' + newI); return newI; }); } const increment = () => setState({ count: count + step }) return ( <> <button onClick={changeVal}>Change the alphabets: {me}</button> <button onClick={increment}>{count}</button> </> ) }
方案2:使用useRef
useRef的current属性可以在渲染之间保留值,且修改它不会触发组件重新渲染:
import { useState, useReducer, useRef } from 'react' const countReducer = (state, newState) => ({ ...state, ...newState }) function Counter({ initialCount = 0, step = 1 }) { const [me, setMe] = useState('ABc'); const [state, setState] = useReducer(countReducer, { count: initialCount }) const count = state.count // 用useRef保存i的值 const iRef = useRef(5); const changeVal = () => { iRef.current += 1; setMe('pqrts' + iRef.current); } const increment = () => setState({ count: count + step }) return ( <> <button onClick={changeVal}>Change the alphabets: {me}</button> <button onClick={increment}>{count}</button> </> ) }
内容的提问来源于stack exchange,提问作者Rameshwar Mandavgade
相关产品推荐
相关产品推荐

