React/JavaScript嵌套API调用:map()内二次请求数据异常问题
问题原因
你当前所有电影卡片共用同一个movieInfo全局状态,无论触发哪部电影的详情请求,返回的数据都会覆盖这个唯一的状态值;列表渲染时所有卡片读取的都是这同一个状态值,自然会全部显示同一份详情。
不建议在map遍历过程中直接批量发起详情请求,会产生不必要的请求风暴,拖慢页面加载速度,按需点击加载是更合理的实现方式
修复步骤
- 把原本存单个详情的
movieInfo状态,改为以电影imdbID为键的对象结构,用来存储所有已经加载过的电影详情,不同电影的详情数据互不干扰 - 调整详情请求逻辑,拿到单部电影详情后,把数据存入上述对象对应的ID键下
- 列表渲染时,每部电影只读取自身ID对应的详情数据展示,不再全局共用同一份数据
- 可选优化:增加加载状态判断,已经加载过详情的电影不再重复发请求
修改后完整代码
import { useState } from "react"; const Home = () => { const key = 'eee0805f'; const baseUrl = 'https://www.omdbapi.com/?apikey=' + key; // 搜索输入相关状态 const [message, setMessage] = useState(''); const [searchTerm , setSearchTerm] = useState(''); const [movies, setMovies] = useState(null); // 修改:用对象存储所有已加载的电影详情,key为imdbID,value为对应详情 const [movieInfoMap, setMovieInfoMap] = useState({}); // 存加载中的电影ID,做loading提示 const [loadingId, setLoadingId] = useState(null); const handleChange = event => { setMessage(event.target.value); }; const fetchData = async (url) => { try { const response = await fetch(url); const json = await response.json(); setMovies(json.Search); // 每次新搜索清空之前的详情缓存 setMovieInfoMap({}); } catch (error) { console.log("error", error); } }; const fetchMovieInfo = async (url, movieId) => { // 如果已经加载过该电影详情,直接返回不重复请求 if (movieInfoMap[movieId]) return; setLoadingId(movieId); try { const response = await fetch(url); const json = await response.json(); // 把新拿到的详情存入map,保留之前其他电影的详情数据 setMovieInfoMap(prev => ({ ...prev, [movieId]: json })); } catch (error) { console.log("error", error); } finally { setLoadingId(null); } }; const searchMovie = () => { let url = baseUrl + '&s=' + encodeURIComponent(message); fetchData(url); setSearchTerm(message); setMessage(""); } const moreInfo = (e, movieId) => { let url = baseUrl + '&i=' + movieId; fetchMovieInfo(url, movieId); } return ( <div className="home"> <div className="searchContainer"> <input type="text" id="message" name="message" onChange={handleChange} value={message} placeholder="Please Enter Movie" /> <span style={{ color: 'white', backgroundColor: '#f1356d', borderRadius: '8px' , padding: '2px 8px', cursor: 'pointer' }} onClick={searchMovie}>Search</span> </div> <h3>Title Searched: {searchTerm}</h3> {movies && <div> {movies.map(movie => { // 取当前电影对应的详情 const currentMovieInfo = movieInfoMap[movie.imdbID]; return ( <div className="movie-preview" key={movie.imdbID} > <div className="movie-content"> <div className="movie-premier-image-content"> <img className="movie-preview-image" src={movie.Poster} alt={movie.Title} /> </div> <div> <h2 className="movie-preview-headline">{ movie.Title }</h2> <p>{movie.Year}</p> {/* 只渲染当前电影对应的详情 */} {currentMovieInfo && <div> <p>Director(s): {currentMovieInfo.Director}</p> <p>Genre: { currentMovieInfo.Genre }</p> <p>Date released: {currentMovieInfo.Released}</p> </div>} {loadingId === movie.imdbID && <p>加载详情中...</p>} </div> </div> <div style={{ color: 'white', backgroundColor: '#f1356d', borderRadius: '8px' , padding: '2px 8px', height: '24px', width: '75px', cursor: 'pointer' }} onClick={(e) => moreInfo(e, movie.imdbID)}>More Info</div> </div> )})} </div>} </div> ); } export default Home;
改动说明
- 去掉了从DOM上取
data-key的写法,直接在点击回调里传入当前电影的ID,更符合React数据驱动的写法 - 每次发起新的搜索时会清空之前缓存的详情数据,避免新旧搜索结果数据混淆
- 增加了重复请求判断,已经加载过详情的电影不会重复发起接口请求
- 增加了简单的加载提示,交互更友好
内容的提问来源于stack exchange,提问作者user581733
相关产品推荐
相关产品推荐

