React组件状态设置异常求助:useEffect取数后状态未更新
问题描述
在React组件里用useEffect钩子通过axios实例http从API拿数据,状态定义是:
const [inputsData, setInputsData] = useState(false);
原useEffect代码如下:
useEffect(() => { if (active.id) { http.get(`/stock/${title}/${active.id}`).then((response) => { setInputsData(response.data); console.log(inputsData); }); } }, [title, active.id, refresh]);
原本期望title(URL路由参数)、active.id(当前展示项的React状态)或者refresh变量变化时,数据会刷新重渲染。但实际情况是:API返回的数据没问题,调用setInputsData后,console.log打出来的是旧状态,页面渲染的也是旧数据,得重新导航或刷新页面才能看到正确内容。具体表现就是:点a显示a的数据,点b还是显示a的数据,点c又显示b的数据。
试过模拟重新导航、先把状态设为false再赋值、给setState加setTimeout这些办法,都没用。
后来把useEffect删掉,换成下面这段代码:
if (active.id && !inputsData) { http.get(`/stock/${title}/${active.id}`).then((response) => { setInputsData(response.data); console.log(inputsData); }); }
点击按钮时把inputsData设为false,组件重渲染的时候就会重新从API拿新值,问题就解决了。
问题原因
异步请求竞态是核心问题:当
title或active.id快速切换时,前一次的API请求可能比后一次请求晚完成。比如先点a发起请求A,紧接着点b发起请求B,如果请求A比请求B晚结束,那setInputsData就会被请求A的结果覆盖,导致页面显示的还是旧数据,这就出现了点b却显示a数据的情况。原
useEffect没处理请求取消:在依赖项变化时,前一次没完成的请求没有被取消,旧请求的结果会覆盖新请求的状态,这才是页面渲染旧数据的关键。而后来的写法,通过点击时重置inputsData为false,只有当inputsData为false时才发起新请求,相当于间接避免了旧请求结果覆盖新状态的问题。关于
console.log显示旧值:这是正常现象,因为setState是异步的,状态更新不会立刻生效,当前函数执行时拿到的还是旧的状态值,这个只是日志的问题,不是页面渲染旧数据的核心原因。
内容的提问来源于stack exchange,提问作者DoINeedANameTho

