React中如何用useState设置对象数组的状态?(TMDB电影应用场景)
问题1解答:如何生成符合要求的初始结构
拿到TMDB返回的genres数组后,不要直接赋值给state,先做一次结构转换即可,把原有代码里的setMovieData(data.genres)替换为如下逻辑:
const formattedGenres = data.genres.map(item => ({ genreId: item.id, // 保留分类id用于后续请求对应电影 genre: item.name, movies: [] // 初始化空电影数组 })) setMovieData(formattedGenres)
转换后的movieData就是你需要的目标结构。
问题2解答:遍历拉取分类电影并更新状态
推荐两种常用方案,可按需选择:
- 方案1:等待所有分类电影拉取完成后一次性更新,逻辑更简单
用Promise.all并发发起所有分类的电影请求,全部返回后一次性更新state,避免频繁触发组件渲染,代码示例:
const [movieData, setMovieData] = useState([]); const fetchGenres = async () => { const url = `https://api.themoviedb.org/3/genre/movie/list?api_key=xxx&language=en-US`; try { const res = await fetch(url); const data = await res.json(); // 第一步:生成初始结构 const formattedGenres = data.genres.map(item => ({ genreId: item.id, genre: item.name, movies: [] })) // 第二步:并发请求所有分类的电影 const movieRequests = formattedGenres.map(async (genreItem) => { const movieUrl = `https://api.themoviedb.org/3/discover/movie?api_key=xxx&with_genres=${genreItem.genreId}&language=en-US&page=1` const movieRes = await fetch(movieUrl) const movieData = await movieRes.json() // 返回合并了电影数据的新对象 return { ...genreItem, movies: movieData.results.slice(0,20) // 取前20条电影 } }) // 等待所有请求完成 const finalMovieData = await Promise.all(movieRequests) // 一次性更新state setMovieData(finalMovieData) } catch (err) { console.error(err); } }
- 方案2:渐进式更新,用户体验更好
不用等所有请求返回,每个分类的电影加载完成后就单独更新state,用户可以逐步看到内容,不会长时间白屏:
const fetchGenres = async () => { const url = `https://api.themoviedb.org/3/genre/movie/list?api_key=xxx&language=en-US`; try { const res = await fetch(url); const data = await res.json(); const formattedGenres = data.genres.map(item => ({ genreId: item.id, genre: item.name, movies: [] })) // 先设置初始结构,页面可以先渲染分类标题 setMovieData(formattedGenres) // 逐个发起请求,每个请求完成后单独更新 formattedGenres.forEach(async (genreItem, index) => { try { const movieUrl = `https://api.themoviedb.org/3/discover/movie?api_key=xxx&with_genres=${genreItem.genreId}&language=en-US&page=1` const movieRes = await fetch(movieUrl) const movieData = await movieRes.json() // 用函数式更新避免闭包问题 setMovieData(prev => { const newData = [...prev] newData[index] = { ...newData[index], movies: movieData.results.slice(0,20) } return newData }) } catch (e) { console.error(`加载${genreItem.genre}分类电影失败`, e) } }) } catch (err) { console.error(err); } }
问题3解答:更简单高效的实现方案
上面给出的Promise.all并发请求是目前最常用的高效方案,除此之外还有几个优化方向:
- 如果分类数量超过10个,可以做分批请求,避免触发TMDB的接口限流
- 可以给分类电影数据加缓存,下次进入页面不用重复请求相同数据
- 不想手动处理请求逻辑的话,可以用React Query/SWR这类请求库,自动帮你处理缓存、并发、loading状态,代码会更简洁。
内容的提问来源于stack exchange,提问作者ramuk
相关产品推荐
相关产品推荐

