React Error Boundary 错误边界限制

1. 基本概念

1.1 什么是 Error Boundary

Error Boundary 是 React 16 引入的一个特性,它可以捕获子组件树中的 JavaScript 错误,记录错误并展示备用 UI,而不是让整个应用崩溃。大白话:嵌套组件某个组件出错时显示备用页面而非报错信息,影响整个页面显示

1.2 基本实现

jsx 复制代码
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:', error);
    console.error('Error Info:', errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 渲染降级 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

2. 使用方式

2.1 基本用法

jsx 复制代码
// 包裹可能出错的组件
function App() {
  return (
    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
  );
}

// 嵌套使用
function ComplexApp() {
  return (
    <ErrorBoundary>
      <div>
        <ErrorBoundary>
          <ComponentA />
        </ErrorBoundary>
        <ErrorBoundary>
          <ComponentB />
        </ErrorBoundary>
      </div>
    </ErrorBoundary>
  );
}

2.2 自定义错误 UI

jsx 复制代码
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h2>Oops! Something went wrong</h2>
          <details>
            <summary>Error Details</summary>
            <pre>{this.state.error.toString()}</pre>
          </details>
          <button onClick={() => window.location.reload()}>
            Refresh Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

3. 高级用法

3.1 错误恢复

jsx 复制代码
class RecoverableErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  handleReset = () => {
    this.setState({ hasError: false });
  };

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Something went wrong</h2>
          <button onClick={this.handleReset}>
            Try Again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

3.2 错误日志上报

jsx 复制代码
class LoggingErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    // 发送错误到日志服务
    logErrorToService(error, errorInfo);
  }

  async logErrorToService(error, errorInfo) {
    try {
      await fetch('/api/error-logging', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          error: error.toString(),
          componentStack: errorInfo.componentStack,
          timestamp: new Date().toISOString()
        })
      });
    } catch (loggingError) {
      console.error('Failed to log error:', loggingError);
    }
  }
}

3.3 条件性错误边界

jsx 复制代码
class ConditionalErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    // 只捕获特定类型的错误
    if (error instanceof CustomError) {
      return { hasError: true };
    }
    // 其他错误向上传播
    throw error;
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <div>Error occurred</div>;
    }

    return this.props.children;
  }
}

4. 实践模式

4.1 创建可重用的错误边界

jsx 复制代码
// 创建通用错误边界组件
class ReusableErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-fallback">
          {this.props.errorMessage || 'Something went wrong'}
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用示例
function App() {
  return (
    <ReusableErrorBoundary
      fallback={<CustomErrorUI />}
      errorMessage="Failed to load dashboard"
    >
      <Dashboard />
    </ReusableErrorBoundary>
  );
}

4.2 错误边界与 Suspense 结合

jsx 复制代码
function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<Loading />}>
        <AsyncComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

4.3 错误边界与 React Router 结合

jsx 复制代码
function App() {
  return (
    <Router>
      <ErrorBoundary>
        <Switch>
          <Route path="/dashboard">
            <ErrorBoundary>
              <Dashboard />
            </ErrorBoundary>
          </Route>
          <Route path="/profile">
            <ErrorBoundary>
              <Profile />
            </ErrorBoundary>
          </Route>
        </Switch>
      </ErrorBoundary>
    </Router>
  );
}

5. 最佳实践

5.1 错误边界的粒度

jsx 复制代码
function App() {
  return (
    <ErrorBoundary>
      {/* 顶层错误边界,捕获通用错误 */}
      <Layout>
        <ErrorBoundary>
          {/* 功能级错误边界 */}
          <UserDashboard />
        </ErrorBoundary>
        <ErrorBoundary>
          {/* 组件级错误边界 */}
          <ComplexChart />
        </ErrorBoundary>
      </Layout>
    </ErrorBoundary>
  );
}

5.2 错误恢复策略

jsx 复制代码
class SmartErrorBoundary extends React.Component {
  state = { hasError: false, retryCount: 0 };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  handleRetry = () => {
    this.setState(state => ({
      hasError: false,
      retryCount: state.retryCount + 1
    }));
  };

  render() {
    if (this.state.hasError) {
      if (this.state.retryCount >= 3) {
        return <div>Too many retries. Please refresh the page.</div>;
      }

      return (
        <div>
          <p>Something went wrong</p>
          <button onClick={this.handleRetry}>
            Retry (Attempt {this.state.retryCount + 1}/3)
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

6. 注意事项

6.1 错误边界的限制

  1. 不能捕获以下类型的错误:
    • 事件处理器中的错误
    • 异步代码中的错误
    • 服务端渲染中的错误
    • 错误边界自身抛出的错误

6.2 处理未捕获的错误

jsx 复制代码
// 在应用入口处添加全局错误处理
window.addEventListener('error', (event) => {
  console.error('Uncaught error:', event.error);
  // 显示全局错误 UI 或重新加载页面
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  // 处理未捕获的 Promise 错误
});

7. 总结

7.1 使用场景

  1. 数据获取错误处理
  2. 组件渲染错误处理
  3. 复杂计算错误处理
  4. 第三方组件错误隔离

7.2 最佳实践建议

  1. 合理设置错误边界的粒度
  2. 实现适当的错误恢复机制
  3. 做好错误日志记录
  4. 提供友好的用户错误提示
  5. 考虑错误边界的性能影响
相关推荐
Rowrey2 小时前
react+typescript,初始化与项目配置
javascript·react.js·typescript
谢尔登2 小时前
Vue 和 React 的异同点
前端·vue.js·react.js
风清云淡_A9 小时前
【react18】如何使用useReducer和useContext来实现一个todoList功能
react.js
化作繁星10 小时前
React 高阶组件的优缺点
前端·javascript·react.js
贩卖纯净水.15 小时前
REACT学习DAY02(恨连接不上服务器)
服务器·学习·react.js
一路向前的月光17 小时前
react(9)-redux
前端·javascript·react.js
谢尔登18 小时前
Vue 和 React 响应式的区别
前端·vue.js·react.js
程序员远仔21 小时前
【Vue.js 和 React.js 的主要区别是什么?】
前端·javascript·css·vue.js·react.js·性能优化·html5
小刘不知道叫啥1 天前
React源码揭秘 | 启动入口
前端·react.js·前端框架
程序员小续2 天前
Excel 表格和 Node.js 实现数据转换工具
前端·javascript·react.js·前端框架·vue·excel·reactjs