ErrorBoundary
react的boundary
react-boundary文档地址
如果没有找到 ErrorBoundary
,错误会传播到全局,导致应用崩溃。
实现核心逻辑
在 React 中,当子组件抛出错误时,React 会执行以下步骤:
- 查找最近的
ErrorBoundary
: React 会从抛出错误的组件向上查找其父组件树,寻找最近的ErrorBoundary
组件。 - 调用
componentDidCatch
: 如果找到ErrorBoundary
,React 会调用该组件的componentDidCatch(error, info)
方法,传递错误信息和其他信息(如错误发生的组件树)。 - 更新状态:
ErrorBoundary
可以根据捕获的错误更新自身的状态,通常用于显示备用 UI(如错误提示)。 - 渲染备用 UI: 一旦状态更新,
ErrorBoundary
会重新渲染,展示备用 UI,而不是子组件的正常内容。
无法处理的情况
- 事件处理函数(比如 onClick,onMouseEnter)
- 异步代码(如 requestAnimationFrame,setTimeout,promise)
- 服务端渲染
- ErrorBoundary 组件本身的错误。
包含函数详细介绍getDerivedStateFromError和componentDidCatch
作用
getDerivedStateFromError
和 componentDidCatch
都是 React 的错误边界方法,用于处理子组件的错误。这两个方法共同作用,确保组件能够优雅地处理和恢复错误。它们的触发时间点和方式如下:
- getDerivedStateFromError:
- 触发时机: 当子组件抛出错误时,React 会首先调用这个静态方法。
- 功能: 允许你更新状态以便在渲染错误界面之前准备新状态。
- 返回值: 返回一个对象来更新组件状态,或者返回
null
。
- componentDidCatch:
- 触发时机: 在
getDerivedStateFromError
之后调用,主要用于执行副作用,比如日志记录。 - 功能: 可以处理错误信息,进行记录或其他操作。
- 参数: 接收两个参数:错误信息和错误的组件栈。
- 触发时机: 在
为什么分开调用
分开调用 getDerivedStateFromError
和 componentDidCatch
的原因主要有以下几点:
- 职责分离:
getDerivedStateFromError
专注于根据错误更新状态,以便渲染一个替代的 UI。componentDidCatch
处理副作用(如日志记录),关注于错误的处理和反馈。
- 性能优化:
- 分开调用允许 React 在渲染过程中优化性能,避免不必要的重新渲染。
- 灵活性:
- 这种设计允许开发者在状态更新和副作用处理之间做出不同的决策,使得组件更具灵活性。
- 一致性:
getDerivedStateFromError
是一个静态方法,适用于类组件,而componentDidCatch
是实例方法,保持了 API 的一致性。
通过分开处理,React 能够提供更清晰的错误处理机制,提高了组件的可维护性和可读性。
代码实现(补充其他异常捕捉)
当你想补充异步等异常也同步到错误边界组件,如在一个 try-catch
语句中捕获异常并将其同步到错误边界组件,如下:
- 事件处理函数(比如 onClick,onMouseEnter)
- 异步代码(如 requestAnimationFrame,setTimeout,promise)
- 服务端渲染
- ErrorBoundary 组件本身的错误。
- 自定义错误边界:首先,创建一个错误边界组件,使用
componentDidCatch
捕捉错误。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
// 你可以在这里记录错误信息
console.error("Error caught in ErrorBoundary:", error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
- 在组件中使用
try-catch
:在你的组件中,使用try-catch
捕获异常,并调用setState
来更新错误状态。
直接在render抛出异常throw this.state.error; // 抛出错误以让错误边界捕获
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
handleClick = () => {
try {
// 可能抛出错误的代码
} catch (error) {
this.setState({ error: true });
}
};
render() {
try {
// 可能抛出异常的代码
} catch (error) {
this.setState({ error });
}
if (this.state.error) {
throw this.state.error; // 抛出错误以让错误边界捕获
}
return <div>正常内容button onClick={this.handleClick}>Click me</button>;</div>;
}
}
- 包裹组件:在应用中使用错误边界包裹你的组件。
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
函数组件与useErrorBoundary(需自定义Hook)
function useErrorBoundary() {
const [error, setError] = useState(null);
const [info, setInfo] = useState(null);
const handleError = (err, errorInfo) => {
setError(err);
setInfo(errorInfo);
// 上报错误
};
useEffect(() => {
const errorBoundary = React.useErrorBoundary(handleError);
return () => errorBoundary.unsubscribe();
}, []);
if (error) {
return <div>错误:{error.message}</div>;
}
return null;
}
几个关键点,完善一下(还是建议官方的类方式处理):
- 使用 useCallback 缓存错误处理函数,避免重复渲染
- 通过动态创建类组件的方式实现错误边界功能
- 提供了错误状态重置功能(重试按钮)
- 展示了更友好的错误 UI,包括错误信息和组件堆栈
- 实现了组件卸载时的资源清理
在生产环境中使用时,还应该考虑添加以下功能: - 更完善的错误上报逻辑
- 错误 UI 的样式定制
- 错误边界的嵌套策略
- 与 Suspense 的集成(处理异步加载错误)
import { useEffect, useState, useCallback } from 'react';
function useErrorBoundary() {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
const [errorInfo, setErrorInfo] = useState(null);
// 错误处理函数
const handleError = useCallback((err, info) => {
setHasError(true);
setError(err);
setErrorInfo(info);
// 上报错误到监控系统
console.error('Error Boundary Captured:', err, info);
reportErrorToService(err, info); // 假设这是一个错误上报函数
}, []);
// 用于捕获后代组件错误的 effect
useEffect(() => {
// 创建一个错误边界实例
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
handleError(error, errorInfo);
}
render() {
return this.props.children;
}
}
// 为当前组件创建一个错误边界包装器
const ErrorBoundaryWrapper = ({ children }) => (
<ErrorBoundary>{children}</ErrorBoundary>
);
// 将错误边界包装器挂载到当前组件
const wrapperElement = document.createElement('div');
document.body.appendChild(wrapperElement);
// 渲染错误边界组件
const root = ReactDOM.createRoot(wrapperElement);
root.render(<ErrorBoundaryWrapper>{children}</ErrorBoundaryWrapper>);
// 清理函数
return () => {
root.unmount();
document.body.removeChild(wrapperElement);
};
}, [handleError]);
// 重置错误状态
const resetErrorBoundary = useCallback(() => {
setHasError(false);
setError(null);
setErrorInfo(null);
}, []);
// 错误发生时返回错误 UI
if (hasError) {
return (
<div className="error-boundary">
<div className="error-message">
<h2>发生错误</h2>
<p>{error?.message || '未知错误'}</p>
{errorInfo && (
<div className="error-details">
<h3>错误详情</h3>
<pre>{errorInfo.componentStack}</pre>
</div>
)}
</div>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}
// 没有错误时返回 null
return null;
}
// 示例使用方法
function MyComponent() {
const errorBoundary = useErrorBoundary();
return (
<div>
{errorBoundary}
<RiskyComponent /> {/* 可能抛出错误的组件 */}
</div>
);
}
vue的boundary
在 Vue 中,实现这个用errorCaptured
捕捉
其中,errorCaptured
钩子可以捕捉到以下类型的错误:
- 子组件的生命周期钩子中的错误:如
created
、mounted
等。 - 渲染函数中的错误:在模板或渲染函数中发生的错误。
- 事件处理器中的错误:在事件处理函数中抛出的错误。
但是,errorCaptured
无法捕捉到以下类型的错误: - 全局未处理的 Promise 拒绝:这些错误需要使用全局的
window.onunhandledrejection
来捕获。 - 异步操作中的错误:如
setTimeout
、setInterval
中的错误,除非在其中手动捕获并处理。 - Vue 实例的错误:如在根实例中发生的错误,需在全局范围内捕获。
总之,errorCaptured
主要用于捕捉组件内部的错误,而不适用于全局或异步错误。
实现代码:
<!-- AdvancedErrorBoundary.vue -->
<template>
<div>
<slot v-if="!errorState" name="default"></slot>
<slot v-else name="fallback" :error="errorState">
<div class="error-view">
<h3>⚠️ 组件异常</h3>
<p>{{ errorState.message }}</p>
<button @click="reset">重新加载</button>
</div>
</slot>
</div>
</template>
<script>
export default {
data: () => ({
errorState: null
}),
errorCaptured(err, vm, info) {
this.errorState = {
error: err,
component: vm,
info,
timestamp: Date.now()
};
this.reportError(err); // 错误上报
return false;
},
methods: {
reset() {
this.errorState = null;
},
reportError(err) {
// 发送错误到监控系统
}
}
};
</script>
实际应用(异步捕捉手动触发错误边界):
export default {
components: { ErrorBoundary },
methods: {
async fetchData() { //handleClick 点击事件同理
try {
// 异步操作
await apiCall();
} catch (e) {
// 1. 手动触发错误边界
this.$emit('error', e);
// 2. 或调用父组件方法
if (this.parentErrorHandler) {
this.parentErrorHandler(e);
}
}
},
};
</script>
全局异常捕捉:
// vue2
Vue.config.errorHandler = (err, vm, info) => {
// 1. 处理全局错误
console.error('Global error:', err, info);
// 2. 显示全局错误提示
showGlobalErrorOverlay();
};
// vue3
app.config.errorHandler = (err, vm, info) => {
// 错误处理逻辑
};
全局异步异常捕捉:
通过 window.addEventListener('unhandledrejection')
捕获。
nuxt 的 Error boundary
nuxt的组件方式实现,是基于vue的errorCaptured,全局如下:
插件捕捉
// 创建插件 plugins/error-handler.js
export default function ({ error }) {
// 可在此处添加错误上报逻辑
console.error('Nuxt 错误捕获:', error);
}
// 并在 nuxt.config.js 中注册
export default {
plugins: ['~/plugins/error-handler'],
};
微信小程序 Error boundary
实现方式
// components/ErrorBoundary/index.js
Component({
options: {
multipleSlots: true, // 启用多slot支持
},
properties: {
fallback: {
type: String,
value: '页面加载失败,请稍后再试',
},
showError: {
type: Boolean,
value: false,
},
},
data: {
hasError: false,
errorInfo: '',
},
methods: {
// 重置错误状态
resetErrorBoundary() {
this.setData({
hasError: false,
errorInfo: '',
});
// 触发自定义事件通知父组件
this.triggerEvent('reset');
},
},
// 组件生命周期函数,在组件实例进入页面节点树时执行
attached() {
this.setData({
hasError: this.properties.showError,
});
},
// 错误捕获处理函数
pageLifetimes: {
show() {
// 页面显示时重置错误状态(可选)
if (this.data.hasError) {
this.resetErrorBoundary();
}
},
},
// 捕获当前组件错误
lifetimes: {
error(err) {
console.error('ErrorBoundary捕获到错误:', err);
this.setData({
hasError: true,
errorInfo: err.toString(),
});
// 触发自定义事件通知父组件
this.triggerEvent('error', { error: err });
},
},
});
使用方式
// wxml
<!-- 在页面中使用ErrorBoundary -->
<ErrorBoundary fallback="组件加载失败" bind:error="handleError" bind:reset="handleReset">
<!-- 可能出错的组件 -->
<RiskyComponent />
</ErrorBoundary>
// js
// 页面JS
Page({
methods: {
handleError(e) {
console.log('页面收到错误信息:', e.detail.error);
// 可以在这里添加错误上报逻辑
},
handleReset() {
console.log('用户点击了重试按钮');
// 可以在这里添加重新加载数据的逻辑
},
},
});
h5的Error boundary
在纯HTML/JavaScript环境中实现类似React的Error Boundaries功能需要创建一个自定义的错误边界组件,能够捕获子元素的渲染错误并提供降级UI。
<script>
// 错误边界实现
const errorBoundaries = {};
function createErrorBoundary(containerId) {
const container = document.getElementById(containerId);
let hasError = false;
let error = null;
let errorInfo = null;
function render() {
if (!hasError) {
container.innerHTML = `
<div class="error-boundary-content">
<div class="safe-component">
<div class="component-title">安全组件</div>
<div class="component-content">这个组件运行正常</div>
</div>
<div class="unstable-component">
<div class="component-title">不稳定组件</div>
<div class="component-content">点击按钮触发错误</div>
</div>
</div>`;
} else {
container.innerHTML = `
<div class="error-ui">
<div class="error-header">
<span class="error-icon">⚠️</span>
<div class="error-title">组件渲染错误</div>
</div>
<div class="error-message">${error.message}</div>
<div class="actions">
<button class="retry-btn" onclick="resetBoundary('${containerId}')">重试</button>
<button class="report-btn" onclick="reportError()">报告问题</button>
</div>
</div>`;
}
}
function captureError(err, info) {
hasError = true;
error = err;
errorInfo = info;
render();
console.error('ErrorBoundary caught:', err, info);
}
function reset() {
hasError = false;
error = null;
errorInfo = null;
render();
}
// 初始渲染
render();
return {
captureError,
reset
};
}
// 创建错误边界实例
errorBoundaries['boundary1'] = createErrorBoundary('boundary1');
errorBoundaries['boundary2'] = createErrorBoundary('boundary2');
// 触发错误函数
function triggerError(boundaryId) {
try {
// 模拟一个可能出错的函数
const invalidObj = null;
// 故意访问null对象的属性来抛出错误
invalidObj.someProperty = 'test';
} catch (error) {
// 捕获错误并传递给错误边界
errorBoundaries[`boundary${boundaryId}`].captureError(
new Error('组件渲染时发生错误: ' + error.message),
{ componentStack: '在UnstableComponent中' }
);
}
}
// 重置错误边界
function resetBoundary(boundaryId) {
errorBoundaries[boundaryId].reset();
}
// 报告错误
function reportError() {
alert('错误已报告给开发团队,感谢您的反馈!');
}
// 初始化时随机触发一个错误
setTimeout(() => {
if (Math.random() > 0.5) {
triggerError(2);
}
}, 1500);
</script>
使用方式
// 创建错误边界
const boundary = createErrorBoundary('containerId');
// 在可能出错的地方捕获错误
try {
// 可能出错的代码
} catch (error) {
boundary.captureError(error, { componentStack: '...' });
}
// 重置错误边界
boundary.reset();
总结
Vue 与 React 的差异:
- Vue 的错误捕获是单向的(父 → 子),而 React 是双向的(子 → 父)。
Vue 的 errorCaptured 钩子仅捕获渲染期间的错误,不捕获异步错误(如 Promise 错误)。 - 异步错误处理:
全局异步错误需通过 window.addEventListener(‘unhandledrejection’) 捕获。
在 Nuxt 中,异步错误可通过 try…catch 或全局错误页面处理。 - 生产环境错误上报:
建议集成 Sentry 等错误监控工具,在错误捕获时自动上报。