React useEffect中异步请求getUserInfo返回数据未即时渲染问题
你遇到的是React Hooks典型的闭包陈旧状态(stale state)陷阱,和effect执行时机、组件挂载速度没有任何关系,换useLayoutEffect当然解决不了问题。
核心错误出在getUserInfo的状态更新逻辑:
你调用setUserAccountInfo(response?.data?.message)之后,紧接着读取当前作用域下的userAccountInfo状态做判断,这时候拿到的永远是函数定义时捕获到的初始状态值(初次渲染时的空值/undefined),根本不是你刚set进去的最新数据。
React的状态更新是批处理异步执行的,同一个函数作用域内调用setState不会立刻同步更新当前作用域的state变量,所以你下面这三行判断第一次执行的时候永远不成立,对应的setUserLogin/setUserName/setUserRole根本不会触发:
// 这里读到的永远是旧值,第一次执行时三个判断全为false typeof userAccountInfo.userlogin !== 'undefined' && setUserLogin(userAccountInfo.userlogin) typeof userAccountInfo.username !== 'undefined' && setUserName(userAccountInfo.username) typeof userAccountInfo.userrole !== 'undefined' && setUserRole(userAccountInfo.userrole)
页面要等后续组件重渲染(也就是你观察到的componentDidUpdate触发时机)之后,相关状态被其他逻辑/重渲染后重新执行的代码补上,才会展示对应数据。
对比正常运行的loadAllSessions就能发现差异:这个函数里所有状态更新需要的数据,全是直接从接口返回的response对象里读取的,全程没有依赖组件state变量,不存在闭包捕获旧值的问题,拿到接口返回就能一次性完成所有状态更新,首次渲染就能正常展示数据。
不要在setState之后读取当前作用域的state拿新值,直接用接口返回的原始数据做后续判断和赋值即可,和loadAllSessions的写法保持一致:
const getUserInfo = async () => { console.log('ThisShitGonnaWork') const requestData = { "client": requestClient, "key": requestKey, "data":{ "action":"getInfo", "token": accessTokenCookie['access_token'], } } try{ const response = await axios.post( backendUrl + 'user' , requestData ) // 先把接口返回的用户信息存到临时变量,后续直接用这个变量 const userInfo = response?.data?.message if (!userInfo) return setUserAccountInfo(userInfo) // 直接从临时变量取值判断,不要读取userAccountInfo这个state typeof userInfo.userlogin !== 'undefined' && setUserLogin(userInfo.userlogin) typeof userInfo.username !== 'undefined' && setUserName(userInfo.username) typeof userInfo.userrole !== 'undefined' && setUserRole(userInfo.userrole) }catch (err){ console.log(err) } }
改完之后接口返回就能一次性触发所有相关状态更新,首次挂载完成就能正常展示数据,不需要等后续重渲染。
内容的提问来源于stack exchange,提问作者Reboot

