如何不使用useState在循环中存储API请求响应到全局变量?
解决方案
核心问题分析
- 异步请求时序问题:
fetch是异步操作,循环中同步执行rec.push()时,API响应还未返回,导致imdbHolder始终是初始值或被后续请求覆盖。 - 全局变量覆盖:全局变量会被所有异步回调依次赋值,最终仅保留最后一次请求的结果。
- 重复请求触发:直接在渲染相关函数(如
formatRec)中调用API,会因组件重复渲染触发多次请求,耗尽API配额。
修复方案
将API请求逻辑从渲染函数中剥离,在获取到Flask返回的电影数据后,一次性批量请求第三方API,通过Promise.all等待所有请求完成后,将关联好的数据存入状态,避免重复请求和时序问题。
修改后的完整代码
import { useState } from 'react'; const App = () => { const [prediction, setPrediction] = useState(null); // 新增状态存储关联后的推荐数据 const [recommendationsWithDetails, setRecommendationsWithDetails] = useState([]); const onSubmit = async e => { e.preventDefault(); const formData = new FormData(); formData.append("target_user", document.getElementById("target_user").value) formData.append("no_of_highest", document.getElementById("no_of_highest").value) formData.append("no_of_similar_users", document.getElementById("no_of_similar_users").value) formData.append("no_of_movies", document.getElementById("no_of_movies").value) fetch('http://localhost:5000/upload', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { setPrediction(data); // 获取电影数据后,批量请求第三方API fetchRecommendationDetails(data); }); }; // 批量请求第三方API并关联电影数据 const fetchRecommendationDetails = async (predictionData) => { const noMovies = parseInt(document.getElementById("no_of_movies").value); const movieList = predictionData.data.Recommendatons[0]; const fetchTasks = []; for (let i = 1; i <= noMovies; i++) { const movieName = movieList[i]; // 替换为实际IMDb API请求 const task = fetch(`https://catfact.ninja/fact`) .then(res => res.json()) .then(apiRes => ({ movie: movieName, detail: apiRes.fact // 实际项目中替换为IMDb返回的所需字段(如电影ID) })); fetchTasks.push(task); } // 等待所有请求完成,获取关联后的结果 const results = await Promise.all(fetchTasks); setRecommendationsWithDetails(results); }; const formatTop = () => { let top = []; const count = parseInt(document.getElementById("no_of_highest").value); for (let i = 0; i <= count; i++) { top.push(<p key={i}>{prediction.data.Users_Top_Movies[0][i]}</p>); } return top; }; const formatSim = () => { let sim = []; const count = parseInt(document.getElementById("no_of_similar_users").value); for (let i = 0; i <= count - 1; i++) { sim.push(<p key={i}>User #{prediction.data.Similar_Users[i]}, separated by a distance of {prediction.data.Sim_User_distances[i]}</p>); } return sim; }; // 直接使用已关联好的状态数据渲染 const formatRec = () => { return recommendationsWithDetails.map((item, index) => ( <p key={index}>{item.movie} - {item.detail}</p> {/* 实际IMDb链接写法: <p key={index}> {item.movie} - <a href={`https://www.imdb.com/title/${item.detail}`}>IMDb link</a> </p> */} )); }; return ( <div className='App'> <form onSubmit={onSubmit}> <div className='custom-file'> <label for="target_user">target_user</label> <input type="number" id="target_user" name="target_user" /> <br /> <br /> <label for="no_of_highest">no_of_highest_rated_movies_by_target_user</label> <input type="number" id="no_of_highest" name="no_of_highest" /> <br /> <br /> <label for="no_of_similar_users">no_of_similar_users</label> <input type="number" id="no_of_similar_users" name="no_of_similar_users" /> <br /> <br /> <label for="no_of_movies">no_of_movies_to_recommend</label> <input type="number" id="no_of_movies" name="no_of_movies" /> </div> <input type='submit' value='Submit' className='btn btn-primary btm-block mt-4' /> </form> <h1>Users Top Movies</h1> { prediction && formatTop() } <br /> <h1>Users Most Similar to Target User</h1> { prediction && formatSim() } <br /> <h1>Recommendations Based on Similar Users</h1> { prediction && recommendationsWithDetails.length > 0 && formatRec() } </div> ); }; export default App;
关键优化点
- 批量异步处理:用
Promise.all统一管理多个API请求,确保所有数据返回后再更新状态,避免时序问题。 - 状态驱动渲染:将关联后的电影数据存入
recommendationsWithDetails状态,渲染时直接读取状态,避免重复触发API请求。 - 避免全局变量:每个请求结果直接与对应电影绑定,不存在变量覆盖问题。
内容的提问来源于stack exchange,提问作者Marcus
相关产品推荐
相关产品推荐

