React Hooks中Firebase数据获取、计算与展示异常问题排查
我尝试从Firebase Storage中获取多个「game」对象,计算相关统计数据后展示在表格中,现有代码框架如下:
function calculateTeamStatistics(team) { // 遍历所有游戏,在文件中查找队伍名称,计算统计数据 } useEffect(() => { async function prepareStatistics() { // 遍历所有游戏,识别队伍(没有现成的队伍列表) calculateTeamStatistics(team) } prepareStatistics(); }, []); useEffect(() => { async function getAllGames(tournament: string) { let gameFileReferences: any = []; let allGames: any[] = []; const storageRef = storage.ref(); let gameRef = storageRef.child(`${tournament}_results/`).listAll(); await gameRef.then((gameFiles) => { gameFileReferences = gameFiles.items; gameFileReferences.forEach((game: any) => { storageRef .child(`${tournament}_results/${game.name}`) .getDownloadURL() .then((url) => { axios .get(url, { headers: { Accept: "application/json", }, }) .then((response) => { allGames.push(response); if (lastModifiedText === "" && response.headers) { setLastModifiedText(response.headers["last-modified"]); } }) .catch((error) => console.log(error)); }); }); }); setTournamentGames(allGames); } getAllGames(tournamentId); }, []);
目前只有setLastModifiedText钩子能正常工作,刷新浏览器时会更新render中的文本,但统计计算结果无法展示,除非修改render代码并保存。尝试在setModifiedText所在的.then()调用内执行计算与迭代,会出现更多问题。推测是async或useEffect调用位置有误,但无法定位具体问题,已尝试调整各类async与useEffect调用、重构函数,均未解决问题。
1. 修复getAllGames中的异步并行问题
当前代码里forEach遍历文件时,内部的getDownloadURL和axios.get都是异步操作,但没有等待所有操作完成就调用了setTournamentGames(allGames),导致allGames为空或不完整,后续统计自然拿不到完整数据。
修改getAllGames,用Promise.all等待所有异步请求完成:
useEffect(() => { async function getAllGames(tournament: string) { const storageRef = storage.ref(); const gameFiles = await storageRef.child(`${tournament}_results/`).listAll(); // 用Promise.all批量处理所有异步请求 const allGamesPromises = gameFiles.items.map(async (game: any) => { const url = await storageRef.child(`${tournament}_results/${game.name}`).getDownloadURL(); const response = await axios.get(url, { headers: { Accept: "application/json" } }); // 仅在lastModifiedText为空时设置 if (!lastModifiedText && response.headers) { setLastModifiedText(response.headers["last-modified"]); } return response; }); const allGames = await Promise.all(allGamesPromises); setTournamentGames(allGames); } getAllGames(tournamentId); }, [tournamentId, lastModifiedText]); // 添加必要依赖项
2. 让统计计算依赖tournamentGames的更新
原prepareStatistics所在的useEffect依赖为空数组,只会执行一次,但此时tournamentGames还未加载完成。需要把tournamentGames作为依赖,确保数据加载完成后再计算统计:
// 假设用状态存储统计结果 const [teamStatistics, setTeamStatistics] = useState({}); useEffect(() => { // 游戏数据为空时直接返回 if (!tournamentGames.length) return; async function prepareStatistics() { // 从游戏数据中提取所有队伍 const teams = new Set(); tournamentGames.forEach(game => { // 根据实际game数据结构提取队伍,示例为game.data.team1/team2 teams.add(game.data.team1); teams.add(game.data.team2); }); // 计算每个队伍的统计数据 const stats = {}; teams.forEach(team => { stats[team] = calculateTeamStatistics(team, tournamentGames); }); setTeamStatistics(stats); } prepareStatistics(); }, [tournamentGames]); // 依赖游戏数据,数据更新时重新计算
3. 调整calculateTeamStatistics函数
让它接收完整的游戏数组,避免内部遍历未加载完成的数据:
function calculateTeamStatistics(team, allGames) { let wins = 0; let losses = 0; // 可添加其他统计字段,比如得分、净胜分等 allGames.forEach(game => { const gameData = game.data; // 根据实际游戏数据结构判断胜负 if (gameData.winner === team) { wins++; } else if (gameData.loser === team) { losses++; } }); return { wins, losses, totalGames: wins + losses }; }
4. 检查依赖项完整性
确保所有用到的状态、props都加入到useEffect的依赖数组中,避免陈旧闭包问题,比如setTournamentGames、setTeamStatistics等(如果使用ESLint,会自动提示缺失的依赖)。
内容的提问来源于stack exchange,提问作者codex12345

