【翻译】使用React Error Boundary来捕获并处理React的错误

1,616 阅读5分钟

原文地址:React error handling with react-error-boundary | Saeloun Blog

当我们在编写React程序的时候,不可避免的会遇到报错。但即使在编码的时候很严谨,在程序运行时也还是会有不符合预期的报错出现。而这些错误很可能会导致我们的网站完全崩溃,对用户体验造成影响。这就是为什么我们捕获和处理错误的方式尤为重要。

React Error Boundary可以让开发者捕获并处理错误,同时还能防止整个程序崩溃,确保为用户提供顺滑的体验。

React Error Boundaries

React Error Boundary在提供了一种优雅的方式处理错误的同时,又不会让这个应用崩溃掉。这个API允许开发者在某些特定组件内部捕获和处理错误。这些组件提供了降级兜底的UI渲染,而不是直接让应用白屏。

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

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    logErrorToMyService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // we can render any custom fallback UI
      return this.props.fallback;
    }

    return this.props.children;
  }
}

我们可以在组件外层包一个ErrorBoundary

<ErrorBoundary fallback={<p>Error occurred.</p>}>
  <MyComponent />
</ErrorBoundary>

React Error Boundary 在提供了丝滑无缝的用户体验的方式同时,也有一些限制存在。

React Error Boundary的限制

事件回调和异步代码的错误

React Error Boundary 只能捕获出现在渲染阶段的错误。他们不能捕获出现在事件回调,异步代码(setTimeoutfetch)或者是SSR中的错误

class MyComponent extends React.Component {
  handleClick() {
    // This error will not be caught by the error boundary
    throw new Error('Error in event handler');
  }

  render() {
    return <button onClick={handleClick}>Click me</button>;
  }
}
出现在render boundary中的错误

React Error Boundary不能捕获出现在自身组件中render方法中出现的错误

这就到了react-error-boundary这个库发挥作用的时候了。这个库提供了更灵活的错误捕获和处理的方式,使得开发者能够打造更稳定和更用户友好的错误处理机制。

react-error-boundary

这个库提供了大量的属性能够对于 Error Boundary 的行为来定制化。让我们来看看几个重要的属性和用法:

  1. FallbackComponent:当 Error Boundary 内部捕获到错误的时候,该属性能够让我们指定一个自定义组件渲染。这个API能够让我们更灵活地编写一个视觉效果友好且有信息的UI。同时,除了展示错误信息之外还可以提供一些其他的行为。
  2. fallbackRender,与 fallbackcomponent 类似,该属性让开发者自定义一个渲染函数来渲染兜底错误UI。不同的是,对于渲染过程自定义程度更高,还能够做一些更高级的错误兜底逻辑处理。
  3. onError,该属性接受一个回调函数,当错误被 Error Boundary 捕获的时候会调用这个函数,同时将组件的错误堆栈传递过来。我们可以使用这个属性做一些错误日志上报等等。
  4. onReset,这个属性接受一个回调函数。当一个错误出现后,Error Boundary 成功地重置时,会调用这个函数。当错误恢复之后,清理一些行为和更新组件的状态是很好用的。
  5. fallbackProps,这个属性可以让我们透传一些额外的属性给 FallbackComponent 或者 fallbackRender 函数。给错误兜底UI传递一些额外的数据时是很好用的。
  6. retry,这个属性是一个布尔值。布尔值来决定 Error Boundary 是否来重试导致错误异常的操作。当该属性为true的时候,resetErrorBoundary属性传入的回调函数就会被调用来触发重试操作。
import React, { useState } from "react";
import { ErrorBoundary } from "react-error-boundary";

const App = () => {
  const ErrorFallback = ({ error }) => {
    // we can customize the UI as we want
    return (
      <div
        style="color:red"
      >
        <h2>
          Oops! An error occurred
          <br />
          <br />
          {error.message}
        </h2>
        {/* Additional custom error handling */}
      </div>
    );
  };

  const logError = (error) => {
    setErrorMessage(error.message);
    console.error(error);
    // we can also send the error to a logging service
  };

  const handleResetError = () => {
    console.log("Error boundary reset");
    setErrorMessage("");
    //additional logic to perform code cleanup and state update actions
  };

  const UserProfile = ({ user }) => {
    return (
      <div>
        <h2>User Profile</h2>
        <p>Name: {user.name}</p>
        <p>Email: {user.personalID.email}</p>
      </div>
    );
  };

  const user = {
    name: 'Shruti Apte',
    //missing personalID.email property on purpose
  };

  const [errorMessage, setErrorMessage] = useState("");

  return (
    <ErrorBoundary
      onError={logError}
      onReset={handleResetError}
      FallbackComponent={ErrorFallback}
    >
      <UserProfile user={user} />
    </ErrorBoundary>
  );
};

export default App;

以下是react-error-boundary捕获到错误后的UI行为

reactBoundary.gif 我们还可以使用fallbackRender属性而不是FallbackComponent。只需要传一个内敛函数作为属性就可以了。

<ErrorBoundary
  fallbackRender={({ error, resetErrorBoundary }) => (
    <div>
      <h2>An error occurred: {error.message}</h2>
      <button onClick={resetErrorBoundary}>Retry</button>
    </div>
  )}
>
  {/* Component code */}
</ErrorBoundary>

至于说这两个属性使用哪一个完全依靠我们的喜好和错误降级兜底UI的复杂度。如果我们更倾向于单独抽离出一个组件,且这个组件有自己的渲染逻辑,FallbackComponent是一个合适的选择。另一方面如果我们需要对渲染错误兜底UI有更大的灵活度,那么fallbackRender能够让我们定义一个内敛的render函数。

useErrorBoundary hook

这个库还提供了一个useErrorBoundary的hook。这个hook能够让开发者使用更简洁,函数式的方式来打造 Error Boundary 的UI。

我们先引入这个hook

import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';

现在我们修改上面的例子ErrorFallback来看看这个hooks到底是怎么用的

const ErrorFallback = ({ error}) => {

  const { resetErrorBoundary } = useErrorBoundary();

  // we can customize the UI as we want
  return (
    <div>
      <h2 style="color: red">Oops! An error occurred: {error.message}</h2>
      <button onClick={resetErrorBoundary}>Retry</button>
    </div>
  );
};

resetErrorBoundary 函数能够让用户重置 Error Boundary 并且尝试重渲染 UserProfile 组件。我们也可以传一个 onReset 属性来重置这个组件。

withErrorBoundary HOC

这个库还提供了高阶组件,接收的属性跟上面的例子保持一致。

import { withErrorBoundary } from 'react-error-boundary';

我们把 UserProfile 组件包在 withErrorBoundary 的高阶组件内部。

const App = () => {
  const user = {
    name: 'Shruti Apte',
    // Missing email property intentionally to trigger an error
  };

  const UserProfileWithBoundary = withErrorBoundary(UserProfile, {
  FallbackComponent: ErrorFallback,
});

  return (
    <UserProfileWithBoundary user={user} />
    //we can also render other components wrapped inside withErrorBoundary HOC. 
  );
};

export default App;

通过使用 withErrorBoundary 的hoc,我们可以很轻松地将error boundry应用到多个组件上。同时不用重复写错误处理的模版代码。

总结

React Error Boundary 是在组件内部错误处理的很有用的工具,但是同时它自身也有很多限制,比如不能捕获在事件回调中,异步代码中和自身报错里的错误。react-error-boundary的库提供了额外的特性和灵活度解决了这些限制,比如自定义报错兜底组件和重试机制。通过使用这个库。开发者能够在程序运行时更好的处理错误并提供更好的用户体验。