React Redux中GameSpot API请求已完成但组件数据为undefined
基于Vite搭建的React Redux应用,使用GameSpot API,Redux DevTools显示三个查询(games、reviews、releases)状态均为fulfilled且能看到数据,但组件中渲染时数据是undefined。目前在App.jsx的useEffect中合并三个端点的字段:
- games:获取genres、release date
- reviews:获取scores
- releases:获取platforms、publishers、developers
相关代码
App.jsx
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import { useDispatch } from 'react-redux' import { useEffect } from 'react' import './App.css' import { setGames } from './features/Games/gamesSlice' import { useGetGamesQuery, useGetReviewsQuery, useGetReleasesQuery } from './api/gamespotApiSlice' import { Header } from './features/Header/Header' import { GamesList } from './features/Games/GamesList/GamesList' import { GameDetails } from './features/Games/GameDetails/GameDetails' function App () { const dispatch = useDispatch(); const { data: gamesData } = useGetGamesQuery(); const { data: reviewsData } = useGetReviewsQuery(); const { data: releasesData } = useGetReleasesQuery(); useEffect(() => { if (gamesData && reviewsData && releasesData) { const mergedData = gamesData.results.map(game => { const review = reviewsData.results.find(review => review.game.id === game.id); const release = releasesData.results.find(release => release.game_api_url.includes(game.name)); const publisher = release && release.publishers.length > 0 ? release.publishers[0] : "Unknown"; const developer = release && release.developers.length > 0 ? release.developers[0] : "Unknown"; return { ...game, id: game.id, name: game.name, cover: game.image, platform: release ? release.platform : null, genres: game.genres || [], release_year: new Date(release ? release.release_date : null).getFullYear(), score: review ? review.score : null, publisher, developer }; }); const sortedGames = [...mergedData].sort((a, b) => b.score - a.score); dispatch(setGames(sortedGames)); } }, [dispatch, gamesData, reviewsData, releasesData]); return ( <Router> <Header /> <main> <section className='game-view'> <Routes> <Route path='/games/:id' element={<GameDetails />} /> <Route path='/' element={<GamesList />} /> </Routes> </section> </main> </Router> ); } export default App;
gamespotApiSlice.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; const API_KEY = '?api_key='+ import.meta.VITE_GAMESPOT_API_KEY; const jsonFormat = '&format=json'; export const gamespotApi = createApi({ reducerPath: 'gamespotApi', baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ getGames: builder.query({ query: () => `games/${API_KEY}${jsonFormat}`, }), getReviews: builder.query({ query: () => `reviews/${API_KEY}${jsonFormat}&sort=score:desc`, }), getReleases: builder.query({ query: () => `releases/${API_KEY}${jsonFormat}`, }) }) }) export const { useGetGamesQuery, useGetReviewsQuery, useGetReleasesQuery } = gamespotApi; export default gamespotApi.reducer;
排障与解决方案
1. 校验RTK Query返回的数据结构
GameSpot API的响应通常会嵌套在response字段下,而非直接返回results。先在useEffect中打印完整数据结构确认:
console.log('完整games数据:', gamesData); console.log('完整reviews数据:', reviewsData); console.log('完整releases数据:', releasesData);
如果实际数据路径是data.response.results,那之前的gamesData.results会直接返回undefined,导致合并逻辑失效。
2. 确认组件的Redux数据读取路径
检查GamesList/GameDetails组件是否正确通过useSelector读取store中的数据:
// 示例:GamesList组件的正确读取方式 import { useSelector } from 'react-redux'; const GamesList = () => { // 确认state路径与gamesSlice中的定义一致 const games = useSelector(state => state.games.games); // 数据未加载完成时显示加载状态 if (!games) return <div>加载中...</div>; return ( // 游戏列表渲染逻辑 ); };
如果store路径错误(比如写成state.games.data而非实际的state.games.games),组件会拿到undefined。
3. 修复跨端点数据匹配逻辑
(1)替换名称匹配为ID匹配
release.game_api_url.includes(game.name)极易因为名称大小写、副标题、空格差异匹配失败,改用游戏ID匹配更可靠:
// 从game_api_url中提取游戏ID(假设URL格式为"/api/games/12345") const release = releasesData.results.find(release => { const gameId = release.game_api_url.split('/').pop(); return parseInt(gameId) === game.id; });
(2)修复日期处理异常
当release.release_date为null或无效格式时,new Date(null).getFullYear()会返回1970,需添加判断:
release_year: release && release.release_date ? new Date(release.release_date).getFullYear() : "未知",
4. 用RTK Query自定义查询简化合并流程
可以跳过useEffect手动合并,直接在API slice中创建合并数据的自定义查询:
// 在gamespotApiSlice.js中添加 getMergedGames: builder.query({ async queryFn(_, _queryApi, _extraOptions, baseQuery) { // 并行请求三个接口 const [gamesRes, reviewsRes, releasesRes] = await Promise.all([ baseQuery(`games/${API_KEY}${jsonFormat}`), baseQuery(`reviews/${API_KEY}${jsonFormat}&sort=score:desc`), baseQuery(`releases/${API_KEY}${jsonFormat}`) ]); // 处理请求错误 if (gamesRes.error || reviewsRes.error || releasesRes.error) { return { error: '数据获取失败' }; } // 提取有效数据(注意路径要和实际响应匹配) const games = gamesRes.data.response.results; const reviews = reviewsRes.data.response.results; const releases = releasesRes.data.response.results; // 合并数据 const mergedData = games.map(game => { const review = reviews.find(r => r.game.id === game.id); const release = releases.find(r => { const gameId = r.game_api_url.split('/').pop(); return parseInt(gameId) === game.id; }); return { ...game, cover: game.image, platform: release?.platform || null, genres: game.genres || [], release_year: release?.release_date ? new Date(release.release_date).getFullYear() : "未知", score: review?.score || null, publisher: release?.publishers?.[0] || "未知", developer: release?.developers?.[0] || "未知" }; }); // 按评分排序 const sortedGames = mergedData.sort((a, b) => (b.score || 0) - (a.score || 0)); return { data: sortedGames }; } })
然后在App.jsx中直接使用该查询:
const { data: mergedGames } = useGetMergedGamesQuery(); useEffect(() => { if (mergedGames) { dispatch(setGames(mergedGames)); } }, [dispatch, mergedGames]);
5. 添加全局加载状态
在App.jsx中添加加载状态判断,避免数据未准备好时渲染undefined:
const { data: gamesData, isLoading: gamesLoading } = useGetGamesQuery(); const { data: reviewsData, isLoading: reviewsLoading } = useGetReviewsQuery(); const { data: releasesData, isLoading: releasesLoading } = useGetReleasesQuery(); const isLoading = gamesLoading || reviewsLoading || releasesLoading; // 加载中时显示占位 if (isLoading) return <div>加载游戏数据中...</div>;
内容的提问来源于stack exchange,提问作者mattsteen14

