Appearance
错误边界基础
第一部分:错误边界概述
1.1 什么是错误边界
错误边界(Error Boundaries)是React组件,用于捕获其子组件树中的JavaScript错误,记录这些错误,并显示备用UI而不是崩溃的组件树。
核心特点:
javascript
// 错误边界是一个类组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新state,下次渲染显示备用UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 记录错误到错误报告服务
console.error('Error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>出错了</h1>;
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
);
}1.2 为什么需要错误边界
javascript
// 问题:没有错误边界
function App() {
return (
<div>
<Header />
<BuggyCounter /> {/* 这里出错会导致整个App崩溃 */}
<Footer />
</div>
);
}
function BuggyCounter() {
const [count, setCount] = useState(0);
if (count === 3) {
throw new Error('I crashed!'); // 崩溃!
}
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// 结果:用户看到白屏,整个应用不可用
// 解决:使用错误边界
function App() {
return (
<div>
<Header />
<ErrorBoundary fallback={<div>计数器出错了</div>}>
<BuggyCounter />
</ErrorBoundary>
<Footer />
</div>
);
}
// 结果:只有BuggyCounter区域显示错误,其他部分正常1.3 错误边界的能力范围
javascript
// ✅ 错误边界可以捕获:
// 1. 子组件渲染时的错误
// 2. 生命周期方法中的错误
// 3. 构造函数中的错误
class ProblematicComponent extends React.Component {
constructor(props) {
super(props);
throw new Error('Constructor error'); // ✅ 可以捕获
}
componentDidMount() {
throw new Error('Lifecycle error'); // ✅ 可以捕获
}
render() {
throw new Error('Render error'); // ✅ 可以捕获
}
}
// ❌ 错误边界不能捕获:
// 1. 事件处理器中的错误
// 2. 异步代码(setTimeout, requestAnimationFrame)
// 3. 服务端渲染的错误
// 4. 错误边界自身的错误
function NotCaught() {
const handleClick = () => {
throw new Error('Event handler error'); // ❌ 不能捕获
};
useEffect(() => {
setTimeout(() => {
throw new Error('Async error'); // ❌ 不能捕获
}, 1000);
}, []);
return <button onClick={handleClick}>Click</button>;
}
// 事件处理器需要try-catch
function SafeEventHandler() {
const [error, setError] = useState(null);
const handleClick = () => {
try {
throw new Error('Event error');
} catch (err) {
setError(err);
}
};
if (error) {
return <div>错误: {error.message}</div>;
}
return <button onClick={handleClick}>Click</button>;
}1.4 基本实现
javascript
// 最简单的错误边界
class SimpleErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 带错误信息的版本
class ErrorBoundaryWithInfo extends React.Component {
state = { hasError: false, error: null, errorInfo: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
// 可选:记录到错误服务
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>出错了</h2>
<details>
<summary>错误详情</summary>
<p>{this.state.error?.toString()}</p>
<p>{this.state.errorInfo?.componentStack}</p>
</details>
</div>
);
}
return this.props.children;
}
}
// 可配置的错误边界
class ConfigurableErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
this.props.onError?.(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback({
error: this.state.error,
reset: () => this.setState({ hasError: false, error: null })
});
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ConfigurableErrorBoundary
fallback={({ error, reset }) => (
<div>
<h2>Error: {error.message}</h2>
<button onClick={reset}>重试</button>
</div>
)}
onError={(error, errorInfo) => {
console.error('Caught error:', error, errorInfo);
}}
>
<MyComponent />
</ConfigurableErrorBoundary>
);
}第二部分:错误边界模式
2.1 粒度控制
javascript
// 粗粒度:整个应用
function CoarseGrained() {
return (
<ErrorBoundary fallback={<FullPageError />}>
<App />
</ErrorBoundary>
);
}
// 优点:简单
// 缺点:一个错误导致整个应用不可用
// 细粒度:每个组件
function FineGrained() {
return (
<div>
<ErrorBoundary fallback={<HeaderError />}>
<Header />
</ErrorBoundary>
<ErrorBoundary fallback={<MainError />}>
<Main />
</ErrorBoundary>
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
</div>
);
}
// 优点:隔离错误,其他部分正常
// 缺点:代码冗余
// 最佳实践:关键功能边界
function BestPractice() {
return (
<div>
<Header /> {/* 不容易出错,不包裹 */}
<ErrorBoundary fallback={<DashboardError />}>
<Dashboard /> {/* 复杂组件,包裹 */}
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError />}>
<ThirdPartyWidget /> {/* 第三方组件,包裹 */}
</ErrorBoundary>
<Footer /> {/* 简单组件,不包裹 */}
</div>
);
}2.2 嵌套错误边界
javascript
// 多层错误处理
function NestedErrorBoundaries() {
return (
<ErrorBoundary fallback={<AppLevelError />}>
{/* 顶层:应用级错误 */}
<Header />
<ErrorBoundary fallback={<PageLevelError />}>
{/* 第二层:页面级错误 */}
<PageContent />
<ErrorBoundary fallback={<ComponentError />}>
{/* 第三层:组件级错误 */}
<ComplexWidget />
</ErrorBoundary>
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}
// 执行逻辑:
// 1. ComplexWidget出错 -> ComponentError显示
// 2. PageContent出错 -> PageLevelError显示
// 3. Header/Footer出错 -> AppLevelError显示
// 优先级错误处理
class PriorityErrorBoundary extends React.Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, errorInfo) {
const priority = this.props.priority || 'low';
if (priority === 'critical') {
// 关键错误:立即通知
alertCriticalError(error, errorInfo);
} else if (priority === 'high') {
// 高优先级:记录并通知
logError(error, errorInfo);
notifyTeam(error);
} else {
// 低优先级:仅记录
logError(error, errorInfo);
}
}
render() {
if (this.state.error) {
return this.props.fallback;
}
return this.props.children;
}
}
// 使用
function App() {
return (
<div>
<PriorityErrorBoundary
priority="critical"
fallback={<CriticalError />}
>
<PaymentSystem />
</PriorityErrorBoundary>
<PriorityErrorBoundary
priority="low"
fallback={<MinorError />}
>
<Comments />
</PriorityErrorBoundary>
</div>
);
}2.3 错误恢复
javascript
// 带重置功能的错误边界
class ResettableErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
reset = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>出错了</h2>
<p>{this.state.error?.message}</p>
<button onClick={this.reset}>重试</button>
</div>
);
}
return this.props.children;
}
}
// 自动重置
class AutoResetErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidUpdate(prevProps) {
// Props变化时自动重置
if (this.state.hasError && prevProps.resetKey !== this.props.resetKey) {
this.setState({ hasError: false, error: null });
}
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return this.props.children;
}
}
// 使用
function UserProfile({ userId }) {
return (
<AutoResetErrorBoundary resetKey={userId}>
<ProfileContent userId={userId} />
</AutoResetErrorBoundary>
);
}
// userId变化时自动重置错误状态
// 带重试限制的错误边界
class RetryLimitErrorBoundary extends React.Component {
state = {
hasError: false,
error: null,
retryCount: 0
};
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
retry = () => {
const { retryCount } = this.state;
const maxRetries = this.props.maxRetries || 3;
if (retryCount < maxRetries) {
this.setState(prev => ({
hasError: false,
error: null,
retryCount: prev.retryCount + 1
}));
}
};
render() {
const { hasError, error, retryCount } = this.state;
const maxRetries = this.props.maxRetries || 3;
if (hasError) {
if (retryCount >= maxRetries) {
return (
<div>
<h2>多次重试失败</h2>
<p>请刷新页面或联系支持</p>
</div>
);
}
return (
<div>
<h2>出错了</h2>
<p>{error?.message}</p>
<button onClick={this.retry}>
重试 ({retryCount}/{maxRetries})
</button>
</div>
);
}
return this.props.children;
}
}2.4 错误上报
javascript
// 错误上报服务集成
class ReportingErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 上报到Sentry
if (window.Sentry) {
window.Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack
}
}
});
}
// 上报到自定义服务
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: error.toString(),
componentStack: errorInfo.componentStack,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
})
});
// 上报到Google Analytics
if (window.gtag) {
window.gtag('event', 'exception', {
description: error.toString(),
fatal: true
});
}
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}
// 带用户上下文的错误上报
class ContextualErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
const { user, route } = this.props;
reportError({
error: error.toString(),
componentStack: errorInfo.componentStack,
user: {
id: user?.id,
email: user?.email
},
route: route,
timestamp: Date.now()
});
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
// 使用
function App() {
const user = useUser();
const location = useLocation();
return (
<ContextualErrorBoundary
user={user}
route={location.pathname}
fallback={<ErrorPage />}
>
<AppContent />
</ContextualErrorBoundary>
);
}第三部分:实战应用
3.1 路由级错误边界
javascript
// React Router错误边界
function AppRouter() {
return (
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<ErrorBoundary fallback={<HomeError />}>
<Home />
</ErrorBoundary>
}
/>
<Route
path="/dashboard"
element={
<ErrorBoundary fallback={<DashboardError />}>
<Dashboard />
</ErrorBoundary>
}
/>
<Route
path="/profile"
element={
<ErrorBoundary fallback={<ProfileError />}>
<Profile />
</ErrorBoundary>
}
/>
</Routes>
</BrowserRouter>
);
}
// 统一路由错误处理
function RouterWithErrorBoundary() {
return (
<BrowserRouter>
<ErrorBoundary
fallback={({ error, reset }) => (
<RouteError error={error} onReset={reset} />
)}
>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</ErrorBoundary>
</BrowserRouter>
);
}3.2 异步组件错误边界
javascript
// lazy + ErrorBoundary
import { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<ErrorBoundary fallback={<LoadError />}>
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
}
// 处理chunk加载失败
class ChunkLoadErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
if (error.name === 'ChunkLoadError') {
return { hasError: true, isChunkError: true };
}
return { hasError: true };
}
render() {
if (this.state.hasError) {
if (this.state.isChunkError) {
return (
<div>
<h2>加载失败</h2>
<p>请刷新页面重试</p>
<button onClick={() => window.location.reload()}>
刷新页面
</button>
</div>
);
}
return <GenericError />;
}
return this.props.children;
}
}3.3 表单错误边界
javascript
// 表单提交错误处理
function FormWithErrorBoundary() {
const [submitError, setSubmitError] = useState(null);
const handleSubmit = async (data) => {
try {
setSubmitError(null);
await submitForm(data);
} catch (error) {
setSubmitError(error);
}
};
return (
<ErrorBoundary fallback={<FormRenderError />}>
{submitError && (
<div className="submit-error">
提交失败: {submitError.message}
</div>
)}
<Form onSubmit={handleSubmit} />
</ErrorBoundary>
);
}
// ErrorBoundary捕获渲染错误
// try-catch捕获异步错误3.4 第三方集成错误边界
javascript
// 包裹第三方组件
function ThirdPartyIntegration() {
return (
<ErrorBoundary
fallback={<ThirdPartyFallback />}
onError={(error) => {
console.warn('Third party error:', error);
}}
>
<ThirdPartyMap />
<ThirdPartyChat />
<ThirdPartyAnalytics />
</ErrorBoundary>
);
}
// 隔离不稳定组件
function IsolateUnstable() {
return (
<div>
<StableHeader />
<ErrorBoundary fallback={<WidgetPlaceholder />}>
<UnstableWidget />
</ErrorBoundary>
<StableFooter />
</div>
);
}注意事项
1. 只能是类组件
javascript
// ❌ 函数组件不能是错误边界
function FunctionErrorBoundary({ children }) {
// 没有getDerivedStateFromError
// 没有componentDidCatch
return children;
}
// ✅ 必须是类组件
class ClassErrorBoundary extends React.Component {
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error(error, errorInfo);
}
render() {
// ...
}
}
// 函数组件的替代方案:使用库
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<ErrorBoundary fallback={<Error />}>
<MyComponent />
</ErrorBoundary>
);
}2. 不捕获自身错误
javascript
// ❌ 错误边界不能捕获自己的错误
class SelfErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
// 可以捕获子组件错误
return { hasError: true };
}
render() {
if (this.state.hasError) {
throw new Error('Fallback error'); // ❌ 不能捕获
}
return this.props.children;
}
}
// ✅ 使用外层错误边界
function SafeNesting() {
return (
<OuterErrorBoundary>
<InnerErrorBoundary>
<Content />
</InnerErrorBoundary>
</OuterErrorBoundary>
);
}3. 事件处理器需要try-catch
javascript
// 正确处理事件错误
function SafeComponent() {
const [error, setError] = useState(null);
const handleClick = () => {
try {
riskyOperation();
} catch (err) {
setError(err);
}
};
if (error) {
return <div>错误: {error.message}</div>;
}
return <button onClick={handleClick}>Click</button>;
}常见问题
Q1: 错误边界和try-catch的区别?
A: 错误边界捕获渲染错误,try-catch捕获命令式代码错误。
Q2: 可以在函数组件中使用错误边界吗?
A: 不能创建,但可以使用类错误边界包裹函数组件。
Q3: 错误边界会影响性能吗?
A: 几乎无影响,只在错误发生时有开销。
Q4: 如何测试错误边界?
A: 手动抛出错误或使用测试库模拟错误。
Q5: 错误边界可以嵌套吗?
A: 可以,内层错误会被最近的边界捕获。
Q6: 异步错误能被捕获吗?
A: 不能,需要手动try-catch。
Q7: 开发环境和生产环境有区别吗?
A: 开发环境显示错误堆栈,生产环境显示fallback。
Q8: 错误边界能捕获Promise rejection吗?
A: 不能,需要.catch()或try-catch。
Q9: 如何区分不同类型的错误?
A: 在componentDidCatch中检查error对象属性。
Q10: 错误边界影响SEO吗?
A: 如果服务端渲染时出错,可能影响;客户端不影响。
总结
核心要点
1. 错误边界作用
✅ 捕获渲染错误
✅ 显示备用UI
✅ 防止应用崩溃
✅ 记录错误信息
2. 实现要点
✅ 必须是类组件
✅ getDerivedStateFromError
✅ componentDidCatch
✅ 合理的fallback
3. 使用场景
✅ 关键功能隔离
✅ 第三方组件包裹
✅ 路由级保护
✅ 异步组件保护最佳实践
1. 粒度控制
✅ 关键功能单独包裹
✅ 避免过度嵌套
✅ 平衡隔离和复杂度
2. 用户体验
✅ 友好的错误提示
✅ 提供重试机制
✅ 保留部分功能
✅ 引导用户操作
3. 错误处理
✅ 上报错误
✅ 区分错误类型
✅ 添加上下文信息
✅ 监控错误趋势错误边界是React应用健壮性的基石,合理使用能显著提升用户体验和应用稳定性。