React异步函数中调用setState如何正确更新并获取最新状态?
解决方案
现有方案评估
你给出的基于额外状态+useEffect的方案不合理,属于完全冗余的实现:
- 额外引入了不必要的状态与副作用,会多触发一次组件重渲染,性能更差
- 没有解决核心的闭包旧值问题:如果需要基于当前最新的
objects状态做合并处理,你在useEffect里拿到的依旧可能是旧值,只是把问题转移了位置,没有实质收益。
最优实现方案
React的状态更新函数原生支持函数式更新语法,该语法的入参就是当前最新的状态值,专门用于解决异步回调场景下闭包捕获旧状态的问题,不需要额外引入其他状态或副作用。
基础实现(无竞态处理)
直接修改你的异步请求逻辑即可:
const example = () => { const [objects, setObjects] = useState([]) const asyncFunction = async() => { // 此处会触发API请求,假设运行耗时约10秒 const result = await api.fetchObjects() // 函数式更新,prev参数为当前最新的objects状态 setObjects(prev => { // 按业务需求处理:如果需要合并用户新增内容和接口返回结果,返回 [...prev, ...result] // 如果需要全量覆盖接口结果,可直接返回result,也可基于prev做自定义判断 return result }) } }
进阶实现(处理竞态问题)
如果存在多次触发请求的场景,建议加请求取消逻辑,避免旧请求晚返回覆盖新请求的结果:
import { useState, useRef, useEffect } from 'react' const example = () => { const [objects, setObjects] = useState([]) // 存储请求取消控制器 const abortControllerRef = useRef(null) const asyncFunction = async() => { // 先取消上一次未完成的请求 if (abortControllerRef.current) { abortControllerRef.current.abort() } const controller = new AbortController() abortControllerRef.current = controller try { // 把signal传给请求,用于取消 const result = await api.fetchObjects({ signal: controller.signal }) setObjects(prev => { // 自定义业务逻辑 return [...prev, ...result] }) } catch (err) { // 忽略主动取消的请求错误 if (err.name !== 'AbortError') { console.error('请求失败', err) } } } // 组件卸载时取消未完成的请求,避免内存泄漏 useEffect(() => { return () => abortControllerRef.current?.abort() }, []) }
内容的提问来源于stack exchange,提问作者Cristian Nicolae Perjescu
相关产品推荐
相关产品推荐

