React useState数组不更新:API数据添加仅显最后一条的原因及解决方案
React中调用API更新数组state仅显示最后一条数据的问题解决
这个问题我之前也碰到过,核心是React useState的异步更新特性导致的,我来帮你拆解原因并给出可行的解决方案。
问题重现
你尝试通过API获取多个城市的天气数据,想把每条返回的数据添加到weatherData数组state中,但最终只显示最后一条数据,你的代码如下:
import React, {useEffect, useState} from 'react'; import './App.css'; const data = require('../src/data/Step1'); //导入JSON文件 const axios = require("axios"); function App() { //用于存储天气数据的React Hook const [weatherData, setWeatherData] = useState([]); //调用getData函数的useEffect useEffect(() => { //逐个调用getData传入城市编码 data.List.map(city => getData(city.CityCode)); }, []); //getData函数 async function getData(cityCode) { //Axios发送HTTP GET请求 const response = await axios.get('http://api.openweathermap.org/data/2.5/group?id=' + cityCode + '&units=metric&appid=24e343673cb58392072a12e4705b1260'); //将每个天气对象添加至weatherData数组 setWeatherData([...weatherData,response.data.list[0]]); } if (weatherData.length > 1) { return ( <div> { <div className="container mt-5"> {weatherData.map(x => ( <div className="row mt-2 "> Id : {x.data.id} {" | "} Name :{x.data.name} {" | "} Description : {x.data.weather[0].description} {" | "} Temperature : {x.data.main.temp} </div> ))} </div> } </div> ); } else { return ( <div> <h1>Loading</h1> </div> ) } } export default App;
问题原因
这个问题的根源是useState的异步更新与闭包特性:
- React的useState更新是异步的,而且每次更新都会捕获当前作用域中的state快照。
- 你的
data.List.map会同时触发多个异步的getData请求,这些请求几乎是并行发起的。当每个请求完成调用setWeatherData时,它们捕获到的weatherData都是初始的空数组[],所以每次更新都会生成一个只包含当前请求数据的新数组([...[], newData])。最后所有更新完成后,只有最后一次的更新会生效,导致weatherData中只有最后一条数据。
解决方案
我给你提供两种可行的解决方案,你可以根据需求选择:
方案1:使用函数式更新(推荐,实时添加数据)
React允许给setState传递一个函数,这个函数会接收当前最新的state作为参数,返回新的状态。这样就能确保每次更新都是基于最新的数组状态,而不是旧的快照。
修改getData函数中的setWeatherData部分:
async function getData(cityCode) { const response = await axios.get('http://api.openweathermap.org/data/2.5/group?id=' + cityCode + '&units=metric&appid=24e343673cb58392072a12e4705b1260'); // 使用函数式更新,基于最新的prevState来添加新数据 setWeatherData(prevWeatherData => [...prevWeatherData, response.data.list[0]]); }
方案2:批量请求后一次性更新(适合不需要实时渲染的场景)
如果不需要每条数据返回就实时渲染,而是等所有请求完成后统一展示,可以使用Promise.all来等待所有API请求完成,然后一次性更新state,这样还能减少state更新的次数,提升性能。
修改useEffect和getData部分:
useEffect(() => { // 定义异步函数处理所有请求 const fetchAllWeatherData = async () => { // 生成所有API请求的Promise数组 const weatherPromises = data.List.map(city => axios.get(`http://api.openweathermap.org/data/2.5/group?id=${city.CityCode}&units=metric&appid=24e343673cb58392072a12e4705b1260`) .then(response => response.data.list[0]) // 提取需要的天气数据 ); // 等待所有请求完成,获取完整的数据数组 const allWeatherData = await Promise.all(weatherPromises); // 一次性更新state setWeatherData(allWeatherData); }; fetchAllWeatherData(); }, []); // 可以删除原来的getData函数
额外修复:渲染代码的小问题
你的渲染部分还有一个错误:x.data.id应该改成x.id,因为response.data.list[0]已经是天气对象本身,不需要再取data属性。修改后的渲染代码:
{weatherData.map(x => ( <div className="row mt-2 "> Id : {x.id} {" | "} Name :{x.name} {" | "} Description : {x.weather[0].description} {" | "} Temperature : {x.main.temp} </div> ))}
内容的提问来源于stack exchange,提问作者Rashmika Gamage
相关产品推荐
相关产品推荐

