React调用Firestore参数引发竞态条件导致第三方API加载失败故障排查
问题根因
- 核心错误:Apex、Chess两个API的请求回调中,错误使用了Overwatch请求对象
request_O的状态做判断,没有用自身请求的状态。你的Chess请求哪怕返回200正常,只要request_O的状态不符合200-400区间,就不会进入数据渲染逻辑,这就是你URL正确但渲染失败的直接原因。 - 次要问题:所有XMLHttpRequest请求直接写在组件顶层,只要组件状态更新(比如Firestore拉取到数据触发setState)就会重新执行组件逻辑,重复发送请求,这就是你遇到的无限执行、竞态问题的来源。
- 不规范写法:React中直接调用
document.getElementById操作DOM不符合声明式渲染的规范,全局声明的存储API数据的变量也会有跨实例污染的风险。
修复方案
首先把API请求逻辑封装到useEffect中,参数就绪后再发送请求,每个请求回调使用自身状态判断,数据存在组件state中,修复后核心代码如下:
// 新增各游戏数据state const [overwatchStats, setOverwatchStats] = useState({ mostPlayed: 'None', winRatio: 'None', compED: 'None', avgE: 'None' }) const [apexStats, setApexStats] = useState({ compRank: 'None', arenaRank: 'None', level: 'None', kills: 'None' }) const [chessStats, setChessStats] = useState({ wins: 'None', losses: 'None', winRate: 'None' }) // Overwatch请求 useEffect(() => { if (!O_name || !O_platform || !O_region) return const O_url = `https://owapi.io/stats/${O_platform}/${O_region}/${O_name}` const request_O = new XMLHttpRequest() request_O.open('GET', O_url, true) request_O.onload = function () { if (request_O.status >= 200 && request_O.status < 400) { const data = JSON.parse(this.response) setOverwatchStats({ mostPlayed: `Most Played: ${data.stats.top_heroes.competitive.played[0].hero}, ${data.stats.top_heroes.competitive.played[1].hero}, ${data.stats.top_heroes.competitive.played[2].hero}`, winRatio: `Win Ratio: ${(parseInt(data.stats.game.competitive[3].value) / parseInt(data.stats.game.competitive[1].value)).toFixed(3)}`, compED: `Competitive Elims/Deaths: ${(parseInt(data.stats.combat.competitive[4].value) / parseInt(data.stats.combat.competitive[3].value)).toFixed(3)}`, avgE: `Avg. Elims / 10 Minutes: ${data.stats.average.competitive[3].value}` }) } } request_O.send() }, [O_name, O_platform, O_region]) // Apex请求 useEffect(() => { if (!A_name || !A_platform) return const A_url = `https://api.mozambiquehe.re/bridge?version=5&platform=${A_platform}&player=${A_name}&auth=...` const request_A = new XMLHttpRequest() request_A.open('GET', A_url, true) request_A.onload = function () { // 用自身request_A的状态判断 if (request_A.status >= 200 && request_A.status < 400) { const data = JSON.parse(this.response) setApexStats({ compRank: `Competitive Rank : ${data.global.rank.rankName} -- ${data.global.rank.rankScore}`, arenaRank: `Arena Rank : ${data.global.arena.rankName} -- ${data.global.arena.rankScore}`, level: `Level : ${data.global.level}`, kills: `Kills : ${data.total.kills.value}` }) } } request_A.send() }, [A_name, A_platform]) // Chess请求 useEffect(() => { if (!chessName) return const C_url = `https://api.chess.com/pub/player/${chessName}/stats` const request_C = new XMLHttpRequest() request_C.open('GET', C_url, true) request_C.onload = function () { // 用自身request_C的状态判断 if (request_C.status >= 200 && request_C.status < 400) { const data = JSON.parse(this.response) setChessStats({ wins: `Wins : ${data.chess_rapid.record.win}`, losses: `Losses : ${data.chess_rapid.record.loss}`, winRate: `Win rate : ${(parseInt(data.chess_rapid.record.win) / parseInt(data.chess_rapid.record.loss)).toFixed(3)}` }) } } request_C.send() }, [chessName]) // 渲染部分直接读state即可,无需操作DOM return( <div className="apiDisplays"> <div id="nameDisplay"> Meet {name}!<br/><br/> </div> <div id="gamesPlayedDisplay"> {name} plays these games: <br/> {game1} <br/> {game2} <br/> {game3} <br/><br/> </div> <div id="apiDisplay"> Avaliable stats for {name} <br/><br/> </div> <div> <h1>Overwatch Stats</h1> <p>{overwatchStats.mostPlayed}</p> <p>{overwatchStats.winRatio}</p> <p>{overwatchStats.compED}</p> <p>{overwatchStats.avgE}</p> </div> <div> <h1>Apex Stats</h1> <p>{apexStats.compRank}</p> <p>{apexStats.arenaRank}</p> <p>{apexStats.level}</p> <p>{apexStats.kills}</p> </div> <div> <h1>Chess.com Stats</h1> <p>{chessStats.wins}</p> <p>{chessStats.losses}</p> <p>{chessStats.winRate}</p> </div> </div> )
删除组件顶层原来写的所有请求、全局变量、DOM操作逻辑即可。
内容的提问来源于stack exchange,提问作者Cole
相关产品推荐
相关产品推荐

