错误边界是React的一种错误处理机制,允许你捕获子组件树中任意位置的JavaScript错误,并显示降级UI而不是崩溃的组件树。
子组件抛出错误
错误边界捕获错误
getDerivedStateFromError
componentDidCatch
显示备用UI
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorTimestamp: null
};
}
// 静态方法:更新state使下一次渲染显示降级UI
static getDerivedStateFromError(error) {
return {
hasError: true,
error: error,
errorTimestamp: new Date()
};
}
// 实例方法:捕获错误并记录错误信息
componentDidCatch(error, errorInfo) {
// 记录错误到控制台
console.error('组件错误:', error);
console.error('错误堆栈:', errorInfo.componentStack);
// 可以发送错误到错误监控服务
this.logErrorToService(error, errorInfo);
// 更新state以显示错误详情(可选)
this.setState({
errorInfo: errorInfo
});
}
logErrorToService(error, errorInfo) {
// 发送错误到错误监控服务(如Sentry, LogRocket等)
if (window.errorLoggingService) {
window.errorLoggingService.log({
error: error.toString(),
stack: errorInfo.componentStack,
url: window.location.href,
timestamp: new Date().toISOString()
});
}
}
// 重置错误状态
resetError = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
// 重试加载组件
retry = () => {
this.resetError();
// 如果有重试回调,则调用
if (this.props.onRetry) {
this.props.onRetry();
}
};
render() {
if (this.state.hasError) {
// 显示降级UI
return (
<div className="error-boundary-ui">
<div className="error-content">
<h3>⚠️ 组件发生错误</h3>
<p>抱歉,我们遇到了一个问题。</p>
{this.props.showDetails && this.state.error && (
<div className="error-details">
<p><strong>错误信息:</strong> {this.state.error.toString()}</p>
{this.state.errorInfo && (
<pre className="error-stack">
{this.state.errorInfo.componentStack}
</pre>
)}
</div>
)}
<div className="error-actions">
<button onClick={this.retry} className="retry-btn">
重试
</button>
<button onClick={this.resetError} className="reset-btn">
重置
</button>
{this.props.onReport && (
<button onClick={() => this.props.onReport(this.state.error)}>
报告问题
</button>
)}
</div>
</div>
</div>
);
}
// 正常情况下渲染子组件
return this.props.children;
}
}
// 设置默认props
ErrorBoundary.defaultProps = {
showDetails: process.env.NODE_ENV === 'development'
};
// 使用示例
function App() {
return (
<ErrorBoundary
showDetails={true}
onRetry={() => console.log('正在重试...')}
onReport={(error) => alert(`报告错误: ${error}`)}
>
<BuggyComponent />
</ErrorBoundary>
);
}
这个组件被错误边界保护着。
保护独立的UI组件,防止单个组件错误影响其他部分
<ErrorBoundary>
<UserProfile />
</ErrorBoundary>
<ErrorBoundary>
<DashboardWidget />
</ErrorBoundary>
在路由级别保护整个页面,提供页面级错误处理
<Route
path="/dashboard"
element={
<ErrorBoundary fallback={<DashboardErrorPage />}>
<DashboardPage />
</ErrorBoundary>
}
/>
处理数据获取失败,显示重试按钮或备选内容
<ErrorBoundary
onRetry={fetchData}
fallback={<DataLoadingError />}
>
<DataComponent data={data} />
</ErrorBoundary>
由于错误边界必须是类组件,在函数组件中需要通过HOC(高阶组件)或自定义Hook来使用:
import React, { useEffect, useState } from 'react';
// 高阶组件:为函数组件添加错误边界
function withErrorBoundary(WrappedComponent, FallbackComponent) {
return class extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error in component:', error);
}
render() {
if (this.state.hasError) {
return FallbackComponent ?
<FallbackComponent error={this.state.error} /> :
<DefaultErrorFallback error={this.state.error} />;
}
return <WrappedComponent {...this.props} />;
}
};
}
// 使用高阶组件
const SafeComponent = withErrorBoundary(BuggyFunctionComponent, ErrorFallback);
// 自定义Hook:模拟错误边界效果
function useErrorBoundary() {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
const handleError = (error) => {
setHasError(true);
setError(error);
console.error('Caught error:', error);
};
const resetError = () => {
setHasError(false);
setError(null);
};
return {
hasError,
error,
handleError,
resetError
};
}
// 在函数组件中使用
function FunctionComponentWithErrorHandling() {
const { hasError, error, handleError, resetError } = useErrorBoundary();
useEffect(() => {
try {
// 可能会抛出错误的操作
riskyOperation();
} catch (err) {
handleError(err);
}
}, []);
if (hasError) {
return (
<div>
<p>组件发生错误: {error?.message}</p>
<button onClick={resetError}>重试</button>
</div>
);
}
return <div>正常内容</div>;
}
✅ 正常
✅ 正常
✅ 正常
// 异步错误无法被错误边界捕获
useEffect(() => {
// ❌ 不会被错误边界捕获
fetch('/api/data')
.then(() => { throw new Error('Async error'); });
// ✅ 需要单独处理
fetch('/api/data')
.then(() => { throw new Error('Async error'); })
.catch(error => {
// 手动处理错误
setError(error);
// 或者调用错误处理函数
onError(error);
});
}, []);
// 使用错误边界包装异步操作
class AsyncErrorBoundary extends React.Component {
state = { error: null };
componentDidCatch(error, errorInfo) {
this.setState({ error });
}
handleAsyncError = (error) => {
this.setState({ error });
};
render() {
if (this.state.error) {
return this.props.fallback;
}
return React.cloneElement(this.props.children, {
onError: this.handleAsyncError
});
}
}
function ComponentWithEvent() {
const [error, setError] = useState(null);
const handleClick = () => {
try {
// 可能抛出错误的操作
riskyOperation();
} catch (err) {
// 1. 在事件处理函数中捕获错误
setError(err);
// 2. 可以记录到错误服务
logError(err);
// 3. 显示用户友好的错误信息
alert('操作失败,请重试');
}
};
const handleClickWithBoundary = () => {
try {
riskyOperation();
} catch (err) {
// 抛出错误让错误边界捕获
throw err;
}
};
if (error) {
return <ErrorDisplay error={error} onRetry={() => setError(null)} />;
}
return (
<div>
<button onClick={handleClick}>安全点击</button>
<ErrorBoundary>
<button onClick={handleClickWithBoundary}>
使用错误边界
</button>
</ErrorBoundary>
</div>
);
}
// 嵌套错误边界可以提供更细粒度的错误处理
function App() {
return (
<ErrorBoundary fallback={<AppCrashPage />}>
<Header />
<main>
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<ContentError />}>
<Content>
<ErrorBoundary fallback={<WidgetError />}>
<DashboardWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<ChartError />}>
<DataChart />
</ErrorBoundary>
</Content>
</ErrorBoundary>
</main>
<ErrorBoundary fallback={null}> {/* 静默失败 */}
<Footer />
</ErrorBoundary>
</ErrorBoundary>
);
}
class RetryErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
retryCount: 0,
maxRetries: props.maxRetries || 3
};
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error);
}
handleRetry = () => {
this.setState(prevState => ({
retryCount: prevState.retryCount + 1,
hasError: false,
error: null
}));
};
render() {
if (this.state.hasError) {
// 检查是否超过重试次数
if (this.state.retryCount >= this.state.maxRetries) {
return this.props.fallback || (
<div className="max-retries-exceeded">
<h3>重试次数已用尽</h3>
<p>请稍后再试或联系支持</p>
</div>
);
}
return (
<div className="retry-error-boundary">
<h3>加载失败</h3>
<p>重试次数: {this.state.retryCount}/{this.state.maxRetries}</p>
<button onClick={this.handleRetry}>
重试 ({this.state.retryCount + 1}/{this.state.maxRetries})
</button>
{this.props.onRetry && (
<button onClick={() => this.props.onRetry(this.state.error)}>
报告问题
</button>
)}
</div>
);
}
return this.props.children;
}
}
// 使用示例
<RetryErrorBoundary
maxRetries={5}
fallback={<PermanentErrorPage />}
onRetry={(error) => reportError(error)}
>
<UnstableComponent />
</RetryErrorBoundary>
import React, { createContext, useContext, useState } from 'react';
// 创建错误上下文
const ErrorContext = createContext();
// 错误提供者组件
function ErrorProvider({ children }) {
const [errors, setErrors] = useState([]);
const [globalError, setGlobalError] = useState(null);
const addError = (error, errorInfo) => {
const errorEntry = {
id: Date.now(),
error,
errorInfo,
timestamp: new Date().toISOString(),
url: window.location.href
};
setErrors(prev => [...prev, errorEntry]);
// 发送到错误监控服务
sendToErrorService(errorEntry);
};
const clearError = (errorId) => {
setErrors(prev => prev.filter(err => err.id !== errorId));
};
const clearAllErrors = () => {
setErrors([]);
setGlobalError(null);
};
const sendToErrorService = (errorEntry) => {
// 发送到Sentry, LogRocket等
console.log('Error logged:', errorEntry);
};
return (
<ErrorContext.Provider value={{
errors,
globalError,
addError,
clearError,
clearAllErrors,
setGlobalError
}}>
{children}
</ErrorContext.Provider>
);
}
// 使用上下文的错误边界
class ContextErrorBoundary extends React.Component {
static contextType = ErrorContext;
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 使用上下文记录错误
if (this.context.addError) {
this.context.addError(error, errorInfo);
}
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div>
<h3>组件错误</h3>
<p>错误已被记录</p>
</div>
);
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ErrorProvider>
<ContextErrorBoundary fallback={<AppErrorPage />}>
<YourApp />
</ContextErrorBoundary>
</ErrorProvider>
);
}
// 集成Sentry错误监控
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
// 初始化Sentry
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
integrations: [new BrowserTracing()],
tracesSampleRate: 1.0,
});
// 创建Sentry错误边界
const SentryErrorBoundary = Sentry.ErrorBoundary;
// 使用
<SentryErrorBoundary
fallback={<ErrorFallback />}
onError={(error, errorInfo) => {
console.log('Error sent to Sentry');
}}
>
<YourComponent />
</SentryErrorBoundary>
// 自定义错误报告
class ReportingErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 报告错误到不同服务
this.reportError(error, errorInfo);
}
reportError = (error, errorInfo) => {
const errorData = {
message: error.toString(),
stack: error.stack,
componentStack: errorInfo.componentStack,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString(),
userId: this.getUserId(), // 获取当前用户ID
extraData: this.props.extraData
};
// 发送到多个错误服务
this.sendToBackend(errorData);
this.sendToAnalytics(errorData);
this.sendToLoggingService(errorData);
};
sendToBackend = (errorData) => {
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorData)
}).catch(err => {
console.error('Failed to send error to backend:', err);
});
};
sendToAnalytics = (errorData) => {
if (window.gtag) {
gtag('event', 'error', {
event_category: 'JavaScript',
event_label: errorData.message,
value: 1
});
}
};
sendToLoggingService = (errorData) => {
// 发送到第三方日志服务
console.log('Error logged:', errorData);
};
getUserId = () => {
// 从localStorage、context或state中获取用户ID
return localStorage.getItem('userId');
};
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-fallback">
<h3>出错了</h3>
<p>我们已经记录了这个错误</p>
<button onClick={() => window.location.reload()}>
刷新页面
</button>
</div>
);
}
return this.props.children;
}
}