React中useEffect钩子的异步函数内如何正确更新数组?
问题根因
你当前代码的核心问题是:循环发起的单条item config请求都是异步操作,不会阻塞后续代码执行,你在发起所有请求后立刻执行了setItems,此时绝大多数config请求还未返回响应,空的或只有部分数据的数组被提前写入state,导致渲染不全。
解决方案
使用Promise.all批量等待所有异步config请求全部完成后,再统一更新state。推荐同时用async/await改写嵌套的.then回调,避免回调地狱,提升代码可读性。
修改后的组件代码如下:
const Dashboard = () => { const authUser = useContext(AuthUserContext); const firebase = useContext(FirebaseContext); const [error, setError] = useState<string|null>(null); const [firebaseToken, setFirebaseToken] = useState<string|null>(null); const [items, setItems] = useState<Array<ItemModel>>([]); useEffect(() => { if(!authUser || !firebase) return; const getItems = async () => { try { const userId = authUser.id; const token = await firebase.doGetIdToken(); setFirebaseToken(token); // 拉取用户item列表 const itemResults:Array<ItemModel> = await Client.getData(`items/${userId}`, token); // 把每个item转换成拉取config的promise const itemPromiseList = itemResults.map(async (item:ItemModel) => { const newConfig:ConfigModel = await Client.getData(`/items/config/${item.id}`, token); return { ...item, config: newConfig } }); // 等待所有config请求完成,拿到完整的带config的item列表再更新state const itemResultsWithConfigs = await Promise.all(itemPromiseList); setItems(itemResultsWithConfigs); } catch (err) { setError("Unable to connect to database"); } } getItems(); }, [authUser, firebase]) return ( <> <ul> { items.map((item:ItemModel) => { return <li key={item.id}>{item.name}</li> }) } </ul> </> ); } export default Dashboard;
注意事项
- 如果需要兼容单条config请求失败不影响整体列表渲染,可以给每个单条config请求添加catch逻辑,返回带默认config值的item对象即可,避免
Promise.all整体抛出异常。 - 原代码中
setFirebaseToken是异步更新state,如果你需要在其他地方使用该token,不要依赖本次更新后立即读取state,直接使用当前拿到的token变量即可。
内容的提问来源于stack exchange,提问作者Sharon
相关产品推荐
相关产品推荐

