ErrorBoundary

7 分钟
60 阅读
ErrorBoundary

ErrorBoundary 使用详解

ErrorBoundary(错误边界)是 React 提供的一种组件错误处理机制,用于捕获并处理子组件树中 JavaScript 错误,防止整个应用崩溃。

一、基本概念

1. 什么是 ErrorBoundary?

  • 一个 React 组件,可以捕获其子组件树中发生的 JavaScript 错误
  • 记录这些错误,并显示备用 UI 而不是崩溃的组件树
  • 类似于 JavaScript 的 try-catch,但是针对组件

2. 能捕获哪些错误?

  • 子组件的渲染错误
  • 生命周期方法中的错误
  • 构造函数中的错误

3. 不能捕获哪些错误?

  • 事件处理程序中的错误(使用常规 try-catch)
  • 异步代码中的错误(setTimeout、fetch 等)
  • 服务端渲染错误
  • 错误边界组件自身抛出的错误

二、基本使用

1. 类组件实现 ErrorBoundary

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) {
    // 你可以将错误日志上报到服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义备用 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

2. 使用方式

jsx 复制代码
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

三、高级用法

1. 带错误重置功能的 ErrorBoundary

jsx 复制代码
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false,
      error: null,
      errorInfo: null
    };
    this.resetError = this.resetError.bind(this);
  }

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

  componentDidCatch(error, errorInfo) {
    this.setState({ error, errorInfo });
    logErrorToMyService(error, errorInfo);
  }

  resetError() {
    this.setState({ hasError: false, error: null, errorInfo: null });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
          <button onClick={this.resetError}>Try again</button>
        </div>
      );
    }

    return this.props.children;
  }
}

2. 使用 Hooks 的函数组件实现

虽然官方推荐使用类组件实现 ErrorBoundary,但可以通过包装类组件来在函数组件中使用:

jsx 复制代码
function useErrorBoundary() {
  const [error, setError] = useState(null);
  
  const ErrorBoundary = useMemo(() => {
    return class extends React.Component {
      static getDerivedStateFromError(error) {
        setError(error);
        return { hasError: true };
      }
      
      componentDidCatch(error, errorInfo) {
        console.error(error, errorInfo);
      }
      
      render() {
        return this.props.children;
      }
    };
  }, []);
  
  return { ErrorBoundary, error };
}

// 使用
function App() {
  const { ErrorBoundary, error } = useErrorBoundary();
  
  if (error) {
    return <div>Error: {error.message}</div>;
  }
  
  return (
    <ErrorBoundary>
      <BuggyComponent />
    </ErrorBoundary>
  );
}

3. 使用 react-error-boundary 库

第三方库 react-error-boundary 提供了更强大的功能:

bash 复制代码
npm install react-error-boundary

使用示例:

jsx 复制代码
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // 重置应用状态
      }}
      onError={(error, info) => {
        // 记录错误日志
      }}
    >
      <MyApp />
    </ErrorBoundary>
  );
}

四、最佳实践

1. 错误边界的位置

  • 在路由级别使用:防止整个页面崩溃
  • 在复杂组件周围使用:防止部分 UI 崩溃
  • 避免在最顶层使用:这样会隐藏所有错误
jsx 复制代码
function App() {
  return (
    <ErrorBoundary FallbackComponent={PageErrorFallback}>
      <Router>
        <Routes>
          <Route path="/" element={
            <ErrorBoundary FallbackComponent={DashboardErrorFallback}>
              <Dashboard />
            </ErrorBoundary>
          } />
          <Route path="/profile" element={
            <ErrorBoundary FallbackComponent={ProfileErrorFallback}>
              <Profile />
            </ErrorBoundary>
          } />
        </Routes>
      </Router>
    </ErrorBoundary>
  );
}

2. 错误恢复

提供重置错误的机制,让用户可以恢复应用状态:

jsx 复制代码
function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div>
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => window.location.reload()}
    >
      <MyApp />
    </ErrorBoundary>
  );
}

3. 错误日志记录

将错误信息发送到错误监控服务:

jsx 复制代码
class ErrorBoundary extends React.Component {
  // ...

  componentDidCatch(error, errorInfo) {
    // 使用错误监控服务
    Sentry.captureException(error, { extra: errorInfo });
    // 或自定义日志
    logErrorToMyService(error, errorInfo.componentStack);
  }

  // ...
}

4. 与 Suspense 结合使用

处理异步加载错误:

jsx 复制代码
<ErrorBoundary FallbackComponent={ErrorFallback}>
  <Suspense fallback={<Loading />}>
    <LazyComponent />
  </Suspense>
</ErrorBoundary>

五、常见问题解决方案

1. 事件处理程序中的错误

ErrorBoundary 不能捕获事件处理程序中的错误,需要单独处理:

jsx 复制代码
function MyComponent() {
  const handleClick = () => {
    try {
      // 可能出错的代码
    } catch (error) {
      // 处理错误
      logError(error);
    }
  };

  return <button onClick={handleClick}>Click me</button>;
}

2. 异步错误处理

ErrorBoundary 不能捕获异步代码错误,需要转换为同步错误:

jsx 复制代码
function AsyncWrapper({ children }) {
  const [error, setError] = useState(null);
  
  if (error) {
    throw error; // 让 ErrorBoundary 捕获
  }
  
  return React.cloneElement(children, {
    // 将异步错误转换为同步错误
    onError: (error) => setError(error)
  });
}

// 使用
<ErrorBoundary>
  <AsyncWrapper>
    <MyAsyncComponent />
  </AsyncWrapper>
</ErrorBoundary>

3. 多个错误边界嵌套

可以嵌套多个 ErrorBoundary 以处理不同级别的错误:

jsx 复制代码
<ErrorBoundary FallbackComponent={AppError}>
  <Header />
  <ErrorBoundary FallbackComponent={SidebarError}>
    <Sidebar />
  </ErrorBoundary>
  <ErrorBoundary FallbackComponent={MainContentError}>
    <MainContent />
  </ErrorBoundary>
  <Footer />
</ErrorBoundary>

六、总结

  1. ErrorBoundary 是 React 的错误处理机制,用于捕获子组件树的 JavaScript 错误
  2. 实现方式:通过类组件的 getDerivedStateFromErrorcomponentDidCatch 生命周期方法
  3. 最佳实践
    • 在适当层级使用(路由级别、复杂组件周围)
    • 提供错误恢复机制
    • 记录错误日志
  4. 限制:不能捕获事件处理、异步代码、服务端渲染和自身错误
  5. 增强方案:可以使用 react-error-boundary 等第三方库

通过合理使用 ErrorBoundary,可以显著提高 React 应用的健壮性和用户体验。

评论

评论

发表评论