React useState调用set方法后状态未立即更新问题排查
问题根因
- React函数组件的state属于对应渲染周期的闭包常量:调用setState不会修改当前执行上下文中已有的state变量,只会调度一次组件重渲染,新的state值只会在下一次渲染生成的闭包中生效。因此在
setAccessKeys调用后立刻打印当前作用域的accessKeys,拿到的永远是本次渲染的旧值,不可能是刚设置的新值。 - 你的useEffect依赖配置错误:依赖数组中加入了
accessKeys,会触发无限执行循环——effect内修改accessKeys触发重渲染,重渲染后因accessKeys变化再次触发effect执行,再次发起请求、修改state,这就是你观察到组件多次重复渲染的直接原因。 - 现有set逻辑使用追加写法
[...prevKeys, ...data]配合错误的依赖,还会导致每次effect执行都重复往数组里追加接口返回的数据,最终出现数据重复冗余。
修复方案
- 移除useEffect依赖数组中的
accessKeys,数据拉取逻辑仅在组件挂载时执行一次即可,不需要随accessKeys变化重复触发 - 首次拉取全量数据时直接用接口返回值替换初始state即可,不需要基于prev追加,避免数据重复
- 不要在setState的同步调用后立刻读取当前作用域的旧state值,要观察最新state可以直接在组件顶层打印,或单独写监听accessKeys变化的useEffect。
修复后的完整代码:
import { useState, useEffect } from "react"; import axios from "axios"; import AccessKey from "./AccessKey"; import { toast } from "react-toastify"; import { useNavigate } from "react-router-dom"; import "./AccessKey.module.css"; const AccessKeys = () => { const [accessKeys, setAccessKeys] = useState([]); const navigate = useNavigate(); useEffect(() => { const getAccessKeys = async () => { try { const token = localStorage.getItem("auth"); const response = await axios.get( "http://localhost:5000/api/keys/user", { headers: { authorization: `Bearer ${token}`, }, } ); const data = response.data; // 首次拉取直接替换初始值即可 setAccessKeys(data); } catch (error) { navigate("/"); toast.error(error.response.data.message); } }; getAccessKeys(); // navigate是路由返回的稳定引用,不会触发重复执行 }, [navigate]); // 组件顶层打印可以拿到每次渲染对应的最新state console.log(accessKeys); return ( <> {accessKeys.length > 0 ? ( <main> {accessKeys.map((accessKey) => ( <AccessKey key={accessKey._id} acesskey={accessKey} /> ))} </main> ) : ( <h4>You do not have any Access Keys at the moment</h4> )} </> ); }; export default AccessKeys;
修复后渲染流程说明:
- 首次渲染:accessKeys为初始空数组,展示空状态提示,同时触发useEffect执行接口请求
- 接口返回后调用setAccessKeys调度重渲染,第二次渲染时accessKeys即为接口返回的全量数据,正常渲染列表
- 因依赖中没有accessKeys,effect不会重复执行,不会产生多余渲染和重复请求。
内容的提问来源于stack exchange,提问作者Theophilus Gordon
相关产品推荐
相关产品推荐

