函数组件:如何在useEffect及setState中获取最新state且不新增依赖
问题分析与解决方案
你的核心问题是React状态更新的异步性:调用setPreviousMovieList后,previousMovieList不会立刻更新,后续setMoviesList(previousMovieList.results)拿到的还是旧值(甚至初始的undefined),导致状态设为undefined引发报错。同时你的代码还有几个细节问题需要修正:
- 在
setPreviousMovieList的函数式更新回调中调用setNextPage,违反React最佳实践(状态更新回调应仅负责计算新状态); - else分支里
setPreviousMovieList(previousMovieList)是无效操作,应该设置为初始的[data.data]; - 重复的API请求逻辑可以合并,减少冗余。
以下是几种可落地的修复方案:
方案一:先计算最新列表,同步更新状态
先算出previousMovieList的最新值,再直接用这个变量更新两个状态,完全规避异步状态更新的问题:
const [movies, setMoviesList] = useState([]); const [currentGenre, setcurrentGenre] = useState(); const [page, setPage] = useState(1); const [genreList, setGenreList] = useState(); const [nextPage, setNextPage] = useState(false); const [previousMovieList, setPreviousMovieList] = useState([]); // 给初始空数组避免undefined useEffect(() => { async function getMovies(currentGenre, page) { // 合并API请求地址逻辑 let baseUrl = `https://api.themoviedb.org/3/discover/movie?api_key=f4872214e631fc876cb43e6e30b7e731&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}`; const fullUrl = currentGenre ? `${baseUrl}&with_genres=${currentGenre}` : baseUrl; const data = await rawAxios.get(fullUrl); let newPreviousMovieList; if (nextPage) { // 加载下一页:追加新数据 newPreviousMovieList = [...previousMovieList, data.data]; setNextPage(false); } else { // 初始加载/切换分类/重置页面:替换为新数据 newPreviousMovieList = [data.data]; } // 同步更新两个状态 setPreviousMovieList(newPreviousMovieList); // 根据需求选择:合并所有页的results / 只取最后一页的results setMoviesList(newPreviousMovieList.flatMap(item => item.results)); // setMoviesList(newPreviousMovieList.at(-1).results); } getMovies(currentGenre, page); }, [currentGenre, page, nextPage, previousMovieList]);
方案二:用useEffect监听状态变化自动同步
如果不想大幅改动现有逻辑,可以将setMoviesList移到专门监听previousMovieList的effect中,确保每次previousMovieList更新后自动同步:
const [movies, setMoviesList] = useState([]); const [currentGenre, setcurrentGenre] = useState(); const [page, setPage] = useState(1); const [genreList, setGenreList] = useState(); const [nextPage, setNextPage] = useState(false); const [previousMovieList, setPreviousMovieList] = useState([]); // 原effect只负责更新previousMovieList useEffect(() => { async function getMovies(currentGenre, page) { let baseUrl = `https://api.themoviedb.org/3/discover/movie?api_key=f4872214e631fc876cb43e6e30b7e731&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}`; const fullUrl = currentGenre ? `${baseUrl}&with_genres=${currentGenre}` : baseUrl; const data = await rawAxios.get(fullUrl); setPreviousMovieList(prev => { if (nextPage) { return [...prev, data.data]; } else { return [data.data]; } }); if (nextPage) setNextPage(false); } getMovies(currentGenre, page); }, [currentGenre, page, nextPage]); // 监听previousMovieList变化,自动更新movies useEffect(() => { if (previousMovieList.length > 0) { setMoviesList(previousMovieList.flatMap(item => item.results)); // setMoviesList(previousMovieList.at(-1).results); } }, [previousMovieList]);
方案三:移除冗余状态,直接派生数据
其实movies完全不需要单独存储,可直接从previousMovieList在渲染时派生,彻底避免状态不同步问题:
// 移除movies状态:const [movies, setMoviesList] = useState(); const [currentGenre, setcurrentGenre] = useState(); const [page, setPage] = useState(1); const [genreList, setGenreList] = useState(); const [nextPage, setNextPage] = useState(false); const [previousMovieList, setPreviousMovieList] = useState([]); // 渲染时直接计算movies const movies = previousMovieList.flatMap(item => item.results); // 若只需展示最后一页:const movies = previousMovieList.at(-1)?.results || []; // 原effect仅需维护previousMovieList即可 useEffect(() => { async function getMovies(currentGenre, page) { let baseUrl = `https://api.themoviedb.org/3/discover/movie?api_key=f4872214e631fc876cb43e6e30b7e731&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}`; const fullUrl = currentGenre ? `${baseUrl}&with_genres=${currentGenre}` : baseUrl; const data = await rawAxios.get(fullUrl); setPreviousMovieList(prev => { if (nextPage) { setNextPage(false); return [...prev, data.data]; } else { return [data.data]; } }); } getMovies(currentGenre, page); }, [currentGenre, page, nextPage]);
额外注意事项
- 给状态设置合理的初始值(比如空数组
[]),避免undefined引发的报错; - 前端硬编码API Key存在泄露风险,建议通过后端代理转发API请求。
内容的提问来源于stack exchange,提问作者xAdvitya
相关产品推荐
相关产品推荐

