使用Axios多GET请求时React setState仅添加最后一条剧集数据问题
问题描述
我尝试通过Rick and Morty API获取并列出选中角色出场的所有剧集,但角色的episode属性返回的是URL列表,因此我通过循环来获取这些剧集详情。
问题在于,请求发起后,console.log能打印所有剧集对象,但setState钩子的setEpisodes仅添加最后一条剧集。我尝试过其他循环方式,但结果始终相同。期望所有剧集都能被列出,但实际仅显示最后一条。
相关代码
http.js
import axios from "axios"; const http = axios.create({ baseURL: "https://rickandmortyapi.com/api/", timeout: 10000, }); export default http;
character-details 组件
import { useEffect, useState } from "react"; import http from "../../utils/http"; const CharacterDetails = () => { const [character, setCharacter] = useState(null); const [episodes, setEpisodes] = useState([]); useEffect(() => { http.get('/character/1').then((response) => { setCharacter(response.data); }); }, []); useEffect(() => { if (character) { character.episode.map((episodeUrl) => { const episodeId = getEpisodeIdFromEpisodeURL(episodeUrl); http.get(`/episode/${episodeId}`).then((response) => { console.log(response.data); setEpisodes([...episodes, response.data]); }); }); } }, [character]); function getEpisodeIdFromEpisodeURL(episode) { const lastIndex = episode.lastIndexOf("/"); const episodeId = episode.slice(lastIndex + 1); return episodeId; } return ( <> {character && ( <> <div> <img src={character.image} alt="Character photo" /> <h3>{character.name}</h3> </div> <p>{character.status}</p> <p>{character.species}</p> <p>{character.location.name}</p> {episodes.map((episode) => { return <p key={episode.id}>Episode: {episode.name}</p>; })} </> )} </> ); }; export default CharacterDetails;
解决方案
问题原因
问题出在React状态更新的闭包特性上:当你在循环里调用setEpisodes([...episodes, response.data])时,每次调用的episodes都是组件初始渲染时的空数组(因为useEffect的依赖是character,整个循环在character变化时执行一次,所有请求的回调里捕获的episodes都是同一个初始值)。所以每次更新都是把新数据加到空数组里,最终只会保留最后一次请求的结果。
解决方法1:使用函数式更新
把setEpisodes改成函数式更新,这样每次都会基于最新的状态来更新:
http.get(`/episode/${episodeId}`).then((response) => { console.log(response.data); setEpisodes(prevEpisodes => [...prevEpisodes, response.data]); });
解决方法2:一次性批量获取(更高效)
Rick and Morty API支持批量获取剧集,你可以把所有剧集ID拼接成一个请求,减少HTTP请求次数:
useEffect(() => { if (character) { // 提取所有剧集ID并用逗号拼接 const episodeIds = character.episode.map(url => url.split('/').pop()).join(','); http.get(`/episode/${episodeIds}`).then((response) => { // 批量请求返回的是数组,直接设置状态 setEpisodes(response.data); }); } }, [character]);
这种方式不仅解决了状态更新的问题,还大幅减少了请求数量,性能更好。
内容的提问来源于stack exchange,提问作者Lucas Macêdo
相关产品推荐
相关产品推荐

