feat: 添加 React Error Boundary 组件防止渲染异常白屏

This commit is contained in:
2026-05-31 13:40:05 +08:00
parent e1bf667519
commit ac62da2ad8
2 changed files with 77 additions and 8 deletions
+11 -4
View File
@@ -2,6 +2,7 @@ import { useState } from 'react';
import MainMenu from './components/menu/MainMenu'; import MainMenu from './components/menu/MainMenu';
import GameView from './components/game/GameView'; import GameView from './components/game/GameView';
import ReplayView from './components/replay/ReplayView'; import ReplayView from './components/replay/ReplayView';
import ErrorBoundary from './components/common/ErrorBoundary';
import './App.css'; import './App.css';
type Page = 'menu' | 'game' | 'replay'; type Page = 'menu' | 'game' | 'replay';
@@ -13,15 +14,21 @@ function App() {
const handleReplayStart = () => setPage('replay'); const handleReplayStart = () => setPage('replay');
const handleBackToMenu = () => setPage('menu'); const handleBackToMenu = () => setPage('menu');
if (page === 'game') return <GameView onBackToMenu={handleBackToMenu} />; let content: React.ReactNode;
if (page === 'replay') return <ReplayView onBackToMenu={handleBackToMenu} />; if (page === 'game') {
content = <GameView onBackToMenu={handleBackToMenu} />;
return ( } else if (page === 'replay') {
content = <ReplayView onBackToMenu={handleBackToMenu} />;
} else {
content = (
<MainMenu <MainMenu
onGameStart={handleGameStart} onGameStart={handleGameStart}
onReplayStart={handleReplayStart} onReplayStart={handleReplayStart}
/> />
); );
}
return <ErrorBoundary>{content}</ErrorBoundary>;
} }
export default App; export default App;
+62
View File
@@ -0,0 +1,62 @@
import { Component, type ReactNode } from 'react';
interface Props {
children: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export default class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
color: '#F5DEB3',
background: '#3C2415',
gap: 16,
fontFamily: 'Microsoft YaHei, sans-serif',
}}>
<h2></h2>
<pre style={{ fontSize: 14, opacity: 0.7 }}>
{this.state.error?.message}
</pre>
<button
onClick={() => {
this.setState({ hasError: false, error: null });
window.location.reload();
}}
style={{
padding: '10px 24px',
fontSize: 16,
border: '2px solid #8B7355',
borderRadius: 6,
background: '#DEB887',
color: '#3C2415',
cursor: 'pointer',
}}
>
</button>
</div>
);
}
return this.props.children;
}
}