如何在使用async时递归更新React useState状态?
问题原因
你遇到的问题核心是闭包捕获了旧的state值:组件第一次渲染时,btnClick和increase函数会捕获当时的value(初始值-1)。当递归调用btnClick时,虽然setValue已经更新了state触发重渲染,但递归调用的是第一次渲染时创建的btnClick函数,里面的value还是旧的-1,所以每次increase计算的都是-1+1=0,重复设置同一个值,看起来就只增长了一次。
解决方案
方案1:使用函数式更新state
React的setValue支持传入函数,函数的参数是当前最新的state值,这样就能绕过闭包的问题,每次都获取到最新的value:
export const AsyncTest = ()=>{ const [value, setValue] = useState(-1); const [limit] = useState(5); const btnClick = async ()=>{ const test = await increase(); if (!test) { btnClick(); } else { // 完成后的逻辑 } } const increase = ()=>{ return new Promise (resolve =>{ window.setTimeout(()=>{ // 使用函数式更新,获取最新的value setValue(prevValue => { const valueNew = prevValue + 1; if (valueNew <= limit) { resolve(false); return valueNew; } else { resolve(true); return prevValue; } }); }, 1000); }) } return ( <button id="asyncTestBtn" onClick={btnClick}>{value}</button> ) }
方案2:用useRef保存最新值
如果不想修改setValue的方式,可以用useRef存储value的最新值,因为ref的.current属性是可变的,不会被闭包捕获旧值:
export const AsyncTest = ()=>{ const [value, setValue] = useState(-1); const valueRef = useRef(value); const [limit] = useState(5); // 每次state更新时同步ref的值 useEffect(() => { valueRef.current = value; }, [value]); const btnClick = async ()=>{ const test = await increase(); if (!test) { btnClick(); } else { // 完成后的逻辑 } } const increase = ()=>{ return new Promise (resolve =>{ window.setTimeout(()=>{ let valueNew = valueRef.current + 1; if (valueNew <= limit){ setValue(valueNew); resolve(false); } else { resolve(true); } }, 1000); }) } return ( <button id="asyncTestBtn" onClick={btnClick}>{value}</button> ) }
手动点击正常的原因
手动点击时,每次点击都会触发最新渲染生成的btnClick函数(state更新后组件重渲染,会重新创建btnClick),所以里面的value是最新的,自然能正常增长到上限。
内容的提问来源于stack exchange,提问作者Adam Shaw
相关产品推荐
相关产品推荐

