声明式错误边界Error-Boundary

React ErrorBoundary 类组件实现、生命周期、捕获范围与 Vue 等价方案

#status / evergreen #tech / dev / component-patterns #type / concept

声明式错误边界 Error-Boundary

  • 核心模式:装饰器模式 (Decorator) / 责任链 (Chain of Responsibility)
  • 解构
    • 错误边界就像一个套在外面的保护层(装饰器)。内部 <router-view> 正常工作时它透明;一旦抛出异常,异常冒泡(责任链),错误边界捕获并展示友好 UI。

React ErrorBoundary 实现

ErrorBoundary 必须是 class 组件(截至 React 18,尚无 Hook 版本)。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  // 渲染阶段调用,更新 state 以展示 fallback UI
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  // 错误报告(日志、监控上报)
  componentDidCatch(error, errorInfo) {
    console.error('ErrorBoundary caught:', error, errorInfo.componentStack);
    // 上报到 Sentry 等监控服务
    // reportErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <h1>出错了</h1>;
    }
    return this.props.children;
  }
}

使用方式:

<ErrorBoundary fallback={<ErrorPage />}>
  <RouterView />
</ErrorBoundary>

两个生命周期的区别

方法阶段用途
getDerivedStateFromError渲染更新 state 触发 fallback UI 渲染
componentDidCatch提交副作用:日志记录、错误上报

ErrorBoundary 不捕获的场景

以下错误不会被 ErrorBoundary 捕获:

  1. 事件处理器 — 事件回调中的错误不会冒泡到渲染阶段
  2. 异步代码setTimeoutPromiseasync/await 中的错误
  3. SSR — 服务端渲染期间的错误
  4. ErrorBoundary 自身的错误 — 不会自我捕获
// 事件处理器:需要用 try-catch 手动处理
function Button() {
  const handleClick = () => {
    try {
      riskyOperation();
    } catch (error) {
      // 手动处理
    }
  };
  return <button onClick={handleClick}>Click</button>;
}

// 异步:需要全局捕获或 Promise.catch
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled rejection:', event.reason);
});

Vue 等价方案

Vue 3 提供 onErrorCaptured 生命周期钩子:

<script setup>
import { onErrorCaptured } from 'vue';

onErrorCaptured((err, instance, info) => {
  console.error('捕获到子组件错误:', err);
  // 返回 false 阻止错误继续向上传播
  return false;
});
</script>

Vue 生态中也有成熟的第三方库:vue-error-boundary@vue/composables 中的 onErrorCaptured

创建于 2026/2/21 更新于 2026/5/27