#962 全局 ErrorBoundary 隔离组件渲染错误
main.tsx 加 ErrorBoundary 包裹 App,任何组件抛错都会显示在页面上而不是让 root 空白. 基于 #961 诊断:React 18 mount 正常,问题在 App.tsx 组件树.
This commit is contained in:
1 parent
4cf910ee3e
commit
420e0f6603
2 files changed
+109
-13
No files matched your search
@@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
errorInfo: React.ErrorInfo | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* #962: 全局 ErrorBoundary — 隔离任何组件渲染错误
|
||||
* - 防止单个组件抛错导致整个 React 树崩溃(root 空白)
|
||||
* - 显示错误信息便于诊断
|
||||
* - 提供 reset 按钮,方便恢复
|
||||
*/
|
||||
export default class ErrorBoundary extends React.Component<{ children: React.ReactNode }, State> {
|
||||
constructor(props: { children: React.ReactNode }) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null, errorInfo: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): Partial<State> {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
console.error('[qihu-error-boundary]', error, errorInfo);
|
||||
this.setState({ errorInfo });
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({ hasError: false, error: null, errorInfo: null });
|
||||
// Hard reload to clear any bad state
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError && this.state.error) {
|
||||
const { error, errorInfo } = this.state;
|
||||
return (
|
||||
<div style={{
|
||||
padding: '32px',
|
||||
margin: '32px auto',
|
||||
maxWidth: '800px',
|
||||
fontFamily: 'monospace',
|
||||
background: '#fef2f2',
|
||||
border: '2px solid #dc2626',
|
||||
borderRadius: '8px',
|
||||
color: '#1a1a1a',
|
||||
}}>
|
||||
<h1 style={{ color: '#dc2626', marginTop: 0 }}>❌ React 组件渲染错误</h1>
|
||||
<p style={{ color: '#666', marginBottom: '16px' }}>
|
||||
ErrorBoundary 捕获了渲染错误,防止整个应用崩溃。
|
||||
</p>
|
||||
<h3>错误信息</h3>
|
||||
<pre style={{
|
||||
background: '#fff',
|
||||
padding: '12px',
|
||||
borderRadius: '4px',
|
||||
overflow: 'auto',
|
||||
fontSize: '13px',
|
||||
border: '1px solid #fca5a5',
|
||||
}}>
|
||||
{error.name}: {error.message}
|
||||
</pre>
|
||||
{errorInfo && errorInfo.componentStack && (
|
||||
<>
|
||||
<h3>组件堆栈</h3>
|
||||
<pre style={{
|
||||
background: '#fff',
|
||||
padding: '12px',
|
||||
borderRadius: '4px',
|
||||
overflow: 'auto',
|
||||
fontSize: '11px',
|
||||
border: '1px solid #fca5a5',
|
||||
maxHeight: '300px',
|
||||
}}>
|
||||
{errorInfo.componentStack}
|
||||
</pre>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={this.reset}
|
||||
style={{
|
||||
marginTop: '16px',
|
||||
padding: '10px 24px',
|
||||
background: '#5B5FED',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
}}
|
||||
>
|
||||
🔄 重置页面
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-13
@@ -2,34 +2,26 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import './styles/global.css';
|
||||
|
||||
// #961: 全局错误处理 + 防止 hydration mismatch
|
||||
window.addEventListener('error', (e) => {
|
||||
const root = document.getElementById('root');
|
||||
if (root && e.error) {
|
||||
console.error('[qihu-global-error]', e.error);
|
||||
}
|
||||
});
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
console.error('[qihu-unhandled-rejection]', e.reason);
|
||||
});
|
||||
|
||||
const rootEl = document.getElementById('root')!;
|
||||
// 清空避免 hydration mismatch
|
||||
rootEl.innerHTML = '';
|
||||
|
||||
try {
|
||||
ReactDOM.createRoot(rootEl).render(
|
||||
ReactDOM.createRoot(rootEl).render(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('[qihu-mount-error]', err);
|
||||
rootEl.innerHTML = '<pre style="padding:20px;color:#dc2626;font-family:monospace">React mount failed: ' + String(err) + '</pre>';
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user