React+Artsy API猜艺术家游戏页面无响应问题求助
猜艺术家游戏加载卡死问题排查与修复
我用React和Artsy API开发猜艺术家游戏,通过首页按钮跳转进入游戏页面:
<Link to="/guessing-game"> {" "} {/* Link to Guessing Game page */} <Button type="primary">Play Guessing Game</Button>{" "} {/* Button to navigate to Guessing Game page */} </Link>
点击按钮跳转后,游戏页面一直显示“Guess the Artist”和“Loading...”,完全无响应,甚至无法右键打开控制台查看错误。
初始GuessingsGame.tsx代码
import React, { useState, useEffect } from "react"; import axios from "axios"; // Import Axios import "./GuessingGame.css"; interface Artwork { _embedded: { artists: { name: string }[]; }; _links: { thumbnail: { href: string }; artists: { href: string }; }; title: string; } const GuessingGame: React.FC = () => { const [artwork, setArtwork] = useState<Artwork | null>(null); const [options, setOptions] = useState<string[]>([]); const [selectedOption, setSelectedOption] = useState<string | null>(null); const [score, setScore] = useState<number>(0); const [loading, setLoading] = useState<boolean>(true); useEffect(() => { const fetchArtworkData = async () => { try { const response = await axios.get("https://api.artsy.net/api/artworks", { headers: { "X-XAPP-Token": "My_API_KEY", }, }); const { _embedded } = response.data; if (_embedded && _embedded.artworks && _embedded.artworks.length > 0) { const randomArtwork: Artwork = _embedded.artworks[ Math.floor(Math.random() * _embedded.artworks.length) ]; setArtwork(randomArtwork); const randomArtists = await getRandomArtists( randomArtwork._links.artists.href ); const correctArtist = randomArtwork._embedded.artists[0].name; setOptions([...randomArtists, correctArtist]); setLoading(false); } } catch (error) { console.error("Error fetching artwork data:", error); setLoading(false); } }; fetchArtworkData(); }, []); const getRandomArtists = async (artistsLink: string): Promise<string[]> => { try { const response = await axios.get(artistsLink, { headers: { "X-XAPP-Token": "My_API_KEY", }, }); const { _embedded } = response.data; if (_embedded && _embedded.artists) { const randomArtists: string[] = []; while (randomArtists.length < 3) { const randomIndex = Math.floor( Math.random() * _embedded.artists.length ); const randomArtist = _embedded.artists[randomIndex].name; if (!randomArtists.includes(randomArtist)) { randomArtists.push(randomArtist); } } return randomArtists; } } catch (error) { console.error("Error fetching artists:", error); } return []; }; const handleOptionSelect = (option: string) => { setSelectedOption(option); if (option === artwork?._embedded.artists[0].name) { setScore(score + 1); } }; return ( <div className="guessing-game-container"> <h1>Guess the Artist</h1> {loading ? ( <p>Loading...</p> ) : artwork ? ( <div> <img src={artwork._links.thumbnail.href} alt={artwork.title} className="artwork-thumbnail" /> <h2>{artwork.title}</h2> <h3>Options:</h3> <div className="options-container"> {options.map((option, index) => ( <button key={index} onClick={() => handleOptionSelect(option)} className={`option-button ${ selectedOption === option ? "selected" : "" }`} > {option} </button> ))} </div> <p>Score: {score}</p> </div> ) : ( <p>No artwork available</p> )} </div> ); }; export default GuessingGame;
已尝试的修改(改用fetch API)
import React, { useState, useEffect } from "react"; import "./GuessingGame.css"; interface Artwork { _embedded: { artists: { name: string }[]; }; _links: { thumbnail: { href: string }; artists: { href: string }; }; title: string; } const GuessingGame: React.FC = () => { const [artwork, setArtwork] = useState<Artwork | null>(null); const [options, setOptions] = useState<string[]>([]); const [selectedOption, setSelectedOption] = useState<string | null>(null); const [score, setScore] = useState<number>(0); const [loading, setLoading] = useState<boolean>(true); useEffect(() => { const fetchArtworkData = async () => { try { const response = await fetch("https://api.artsy.net/api/artworks", { headers: { "X-XAPP-Token": "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6IiIsInN1YmplY3RfYXBwbGljYXRpb24iOiIzMDU2MDk0Yi05ODUwLTRjYTktOTJiNy05MWM4MWYwZTYyNDYiLCJleHAiOjE3MTUzMzQ0MDUsImlhdCI6MTcxNDcyOTYwNSwiYXVkIjoiMzA1NjA5NGItOTg1MC00Y2E5LTkyYjctOTFjODFmMGU2MjQ2IiwiaXNzIjoiR3Jhdml0eSIsImp0aSI6IjY2MzRiMjg1MTc0YmUxMDAxMmRiNDc2ZSJ9.hGIPQb-W4jHjitTo5KGWguRo0YpRqlUNIAQlKJpaDl8", }, }); const { _embedded } = await response.json(); if (_embedded && _embedded.artworks && _embedded.artworks.length > 0) { const randomArtwork: Artwork = _embedded.artworks[ Math.floor(Math.random() * _embedded.artworks.length) ]; setArtwork(randomArtwork); const randomArtists = await getRandomArtists( randomArtwork._links.artists.href ); const correctArtist = randomArtwork._embedded.artists[0].name; setOptions([...randomArtists, correctArtist]); setLoading(false); } } catch (error) { console.error("Error fetching artwork data:", error); setLoading(false); } }; fetchArtworkData(); }, []); const getRandomArtists = async (artistsLink: string): Promise<string[]> => { try { const response = await fetch(artistsLink, { headers: { "X-XAPP-Token": "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6IiIsInN1YmplY3RfYXBwbGljYXRpb24iOiIzMDU2MDk0Yi05ODUwLTRjYTktOTJiNy05MWM4MWYwZTYyNDYiLCJleHAiOjE3MTUzMzQ0MDUsImlhdCI6MTcxNDcyOTYwNSwiYXVkIjoiMzA1NjA5NGItOTg1MC00Y2E5LTkyYjctOTFjODFmMGU2MjQ2IiwiaXNzIjoiR3Jhdml0eSIsImp0aSI6IjY2MzRiMjg1MTc0YmUxMDAxMmRiNDc2ZSJ9.hGIPQb-W4jHjitTo5KGWguRo0YpRqlUNIAQlKJpaDl8", }, }); const { _embedded } = await response.json(); if (_embedded && _embedded.artists) { const randomArtists: string[] = []; while (randomArtists.length < 3) { const randomIndex = Math.floor( Math.random() * _embedded.artists.length ); const randomArtist = _embedded.artists[randomIndex].name; if (!randomArtists.includes(randomArtist)) { randomArtists.push(randomArtist); } } return randomArtists; // Add return statement here } } catch (error) { console.error("Error fetching artists:", error); } return []; // Add return statement here to handle other cases }; const handleOptionSelect = (option: string) => { setSelectedOption(option); if (option === artwork?._embedded.artists[0].name) { setScore(score + 1); } }; return ( <div className="guessing-game-container"> <h1>Guess the Artist</h1> {loading ? ( <p>Loading...</p> ) : artwork ? ( <div> <img src={artwork._links.thumbnail.href} alt={artwork.title} className="artwork-thumbnail" /> <h2>{artwork.title}</h2> <h3>Options:</h3> <div className="options-container"> {options.map((option, index) => ( <button key={index} onClick={() => handleOptionSelect(option)} className={`option-button ${ selectedOption === option ? "selected" : "" }`} > {option} </button> ))} </div> <p>Score: {score}</p> </div> ) : ( <p>No artwork available</p> )} </div> ); }; export default GuessingGame;
但问题仍未解决,求代码修改方案或解决思路。
问题排查与修复方案
1. 终止挂起请求,防止页面卡死
页面无法响应大概率是请求挂起或无限循环导致的,给所有请求添加终止信号,组件卸载时强制终止未完成的请求:
useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const fetchArtworkData = async () => { try { const response = await fetch("https://api.artsy.net/api/artworks", { headers: { "X-XAPP-Token": "你的API_TOKEN", }, signal, // 绑定终止信号 }); const { _embedded } = await response.json(); if (_embedded && _embedded.artworks && _embedded.artworks.length > 0) { const randomArtwork: Artwork = _embedded.artworks[Math.floor(Math.random() * _embedded.artworks.length)]; setArtwork(randomArtwork); const randomArtists = await getRandomArtists(randomArtwork._links.artists.href, signal); const correctArtist = randomArtwork._embedded.artists[0].name; setOptions([...randomArtists, correctArtist]); setLoading(false); } else { setLoading(false); } } catch (error) { if (error instanceof DOMException && error.name === "AbortError") { return; } console.error("Error fetching artwork data:", error); setLoading(false); } }; fetchArtworkData(); // 组件卸载时终止请求 return () => controller.abort(); }, []); // 给getRandomArtists添加signal参数 const getRandomArtists = async (artistsLink: string, signal: AbortSignal): Promise<string[]> => { try { const response = await fetch(artistsLink, { headers: { "X-XAPP-Token": "你的API_TOKEN", }, signal, }); const { _embedded } = await response.json(); if (_embedded && _embedded.artists) { const randomArtists: string[] = []; let maxAttempts = 10; // 防止无限循环 while (randomArtists.length < 3 && maxAttempts > 0) { maxAttempts--; const randomIndex = Math.floor(Math.random() * _embedded.artists.length); const randomArtist = _embedded.artists[randomIndex].name; if (!randomArtists.includes(randomArtist)) { randomArtists.push(randomArtist); } } // 补充不足的选项 while (randomArtists.length < 3) { randomArtists.push(`艺术家${randomArtists.length + 1}`); } return randomArtists; } } catch (error) { if (error instanceof DOMException && error.name === "AbortError") { return []; } console.error("Error fetching artists:", error); return []; } return []; };
2. 修复循环阻塞风险
原代码中getRandomArtists的while循环如果遇到_embedded.artists长度不足3的情况,会陷入无限循环,添加最大尝试次数限制并补充默认选项,避免阻塞渲染。
3. 添加错误边界兜底
给组件套上错误边界,防止组件崩溃导致页面完全无响应:
class ErrorBoundary extends React.Component<{children: React.ReactNode}, {hasError: boolean}> { constructor(props: {children: React.ReactNode}) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error: Error) { return { hasError: true }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error("组件错误:", error, errorInfo); } render() { if (this.state.hasError) { return <h2>游戏加载失败,请刷新重试</h2>; } return this.props.children; } }
在路由中使用:
<Route path="/guessing-game"> <ErrorBoundary> <GuessingGame /> </ErrorBoundary> </Route>
4. 强制打开控制台的技巧
如果页面卡死无法右键,直接用键盘快捷键打开控制台:
- Windows/Linux:
F12或Ctrl+Shift+I - Mac:
Cmd+Opt+I
内容的提问来源于stack exchange,提问作者e_robinson
相关产品推荐
相关产品推荐

