React更新state后仍获取旧数据,API拉取数据展示异常求助
问题原因与解决方案
核心原因
更新state后拿到旧值的情况,通常是以下几种场景导致的:
- React的
setState是异步批量执行的,调用setRegion后立刻读取region变量,拿到的还是当前渲染周期的旧值,不会马上返回更新后的值 - 多次发起区域请求时存在竞态问题:先发起的请求如果后返回,会把后发起请求返回的新值覆盖为旧数据
- 子组件
Region没有正确响应props变化:如果子组件内部把传入的data存在自身state中且没有监听props变化,或者错误使用了React.memo的自定义比较函数,都会导致子组件拿到旧值
对应解决方法
- 如果是在
setRegion调用后马上读取region得到旧值:
不要直接读取state变量,直接使用接口返回的res.data做后续逻辑,或者通过useEffect监听region的变化,在副作用回调中拿到更新后的值:
useEffect(() => { // 此处的region为更新后的值 console.log(region) }, [region])
- 如果是子组件
Region拿到旧props:
- 优先直接在
Region组件中使用传入的props.data,不要将其复制到子组件自身的state中使用 - 如果必须在子组件内部存state,需要添加
useEffect监听data变化同步更新子组件state:
// Region组件内部示例 const [localData, setLocalData] = useState([]) useEffect(() => { setLocalData(props.data) }, [props.data])
- 若
Region使用了React.memo,检查是否错误编写了自定义比较函数,返回了错误的相等判定结果
- 解决请求竞态问题,避免旧请求覆盖新数据:
给axios请求添加取消逻辑,下次请求发起或者组件卸载时,取消上一次未完成的请求,优化后代码如下:
export const Options = () => { const [region, setRegion] = useState(null); const controllerRef = useRef(null) const selectRegionHandler = (e) => { let select = e.target.value; // 取消上一次未完成的请求 if (controllerRef.current) { controllerRef.current.abort() } if (select !== "Select Region") { // 新建取消控制器 const controller = new AbortController() controllerRef.current = controller axios(`https://restcountries.eu/rest/v2/region/${select}`, { signal: controller.signal }).then((res) => { setRegion(res.data) }).catch(err => { // 忽略请求取消的报错 if (axios.isCancel(err)) return console.error('请求失败', err) }) } }; // 组件卸载时取消未完成的请求 useEffect(() => { return () => { if (controllerRef.current) { controllerRef.current.abort() } } }, []) if (region !== null) { return <Region data={region} />; } return ( <select onChange={selectRegionHandler}> <option>Select Region</option> <option>Asia</option> <option>Africa</option> <option>Europe</option> </select> ); };
内容的提问来源于stack exchange,提问作者Kamlesh Sharma
相关产品推荐
相关产品推荐

