React exception handling

foreword

What we expect is that some exceptions that occur in the UI, that is, exceptions in components (referring to exceptions that occur in React components, including component rendering exceptions, component life cycle method exceptions, etc.), will not interrupt the entire application and can be done in a friendly way. Handle exceptions and report exceptions. Before the React 16 version, it was more troublesome, and a solution was proposed in React 16, which will be introduced from Error Boundaries.

exception boundary

The so-called exception boundary is to mark the range of the area where the current internal exception can be caught. JavaScript exceptions within this boundary can be caught without interrupting the application. This is a method provided in React 16 to handle exceptions in components. ideas. Specifically, React provides an exception boundary component to catch and print JavaScript exceptions in the subcomponent tree, while displaying an exception replacement UI.

Exception in component

Intra-component exceptions, that is, exceptions that can be caught by exception boundary components, mainly include:

  1. Exception during rendering;
  2. Exceptions in lifecycle methods;
  3. Exception in the constructor constructor of each component in the subcomponent tree.

Other exceptions

Of course, there are still some uncaught exceptions in the exception boundary component, mainly asynchronous and server-triggered exceptions:

  1. exceptions in event handlers;
  2. Asynchronous task exceptions, such as setTiemout, ajax request exceptions, etc.;
  3. Server-side rendering exception;
  4. An exception within the exception boundary component itself;

Exception Boundary Component

As mentioned earlier, the exception boundary component can only capture exceptions that occur in its sub-component tree, and cannot capture exceptions thrown by itself, so it is necessary to pay attention to two points:

  1. Existing components cannot be transformed into boundary components, otherwise exceptions of existing components cannot be caught;
  2. The business logic cannot be involved in the boundary component, otherwise the business logic exception here cannot be caught;

Obviously, the final exception boundary component must be an independent intermediate component that does not involve business logic.

So how does an exception boundary component catch its child component tree exception? Very simple, first it is also a React component, then add ComponentDidCatchlifecycle methods.

example

Create a React component, then add ComponentDidCatchlifecycle methods:

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

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Meet Some Errors.</h1>;
    }
    return this.props.children;
  }
}

The component can then be used like a normal React component:

<ErrorBoundary>
  <App />
</ErrorBoundary>

ComponentDidCatch

This is a new lifecycle method with which child component exceptions can be caught, similar in principle to JavaScript exception catchers try, catch.

ComponentDidCatch(error, info)
  1. error: exception thrown by the application;

    exception object

  2. info: exception information, including ComponentStackthe component stack that bubbles up in the process of the exception corresponding to the attribute;

    Exception Information Component Stack

Determine whether the component adds componentDidCatcha life cycle method. If it is added, call the update rendering component method including exception handling:

if (inst.componentDidCatch) {
  this._updateRenderedComponentWithErrorHandling(
    transaction,
    unmaskedContext,
  );
} else {
  this._updateRenderedComponent(transaction, unmaskedContext);
}

Inside _updateRenderedComponentWithErrorHandlinguse try, catchcatch exception:

 /**
   * Call the component's `render` method and update the DOM accordingly.
   *
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
_updateRenderedComponentWithErrorHandling: function(transaction, context) {
  var checkpoint = transaction.checkpoint();
  try {
    this._updateRenderedComponent(transaction, context);
  } catch (e) {
    // Roll back to checkpoint, handle error (which may add items to the transaction),
    // and take a new checkpoint
    transaction.rollback(checkpoint);
    this._instance.componentDidCatch(e);     

    // Try again - we've informed the component about the error, so they can render an error message this time.
    // If this throws again, the error will bubble up (and can be caught by a higher error boundary).
    this._updateRenderedComponent(transaction, context);
  }
},

For specific source code, see Github

unstable_handleError

In fact, the exception boundary component does not suddenly appear in React. In version 0.15, there is already a test React 15 ErrorBoundaries . See Github for the source code . It can be seen that the concept of exception boundary component already exists in the source code, but it is not stable and is not recommended. It can also be seen from the life cycle method name: unstable_handleError, which is also ComponentDidCatchthe predecessor.

Exception boundaries in business projects

As mentioned above, exception boundary components can technically capture internal subcomponent exceptions. For actual business projects, there are still things to think about:

  1. The scope or granularity of the exception boundary component: whether to use the exception boundary component to wrap the application root component (coarse-grained), or only wrap the independent module entry component (fine-grained);
  2. Coarse-grained use of exception boundary components is to violently handle exceptions. Any exception will display the exception replacement UI, completely interrupting the user's use, but it can indeed easily capture all internal exceptions;
  3. Fine-grained use of exception boundary components will handle exceptions in a more friendly way. Local exceptions will only interrupt the use of the module, and other parts of the application will remain normal and unaffected. However, there are usually many modules in the application, and where the specific modules are divided To a certain extent, developers need to consider it, which is more detailed;

Click here to view the example

Out-of-component exception

The exception boundary component provided by React 16 cannot capture all exceptions in the application, and after React 16, all exceptions that are not caught by the exception boundary will cause React to unload the entire application component tree , so it usually needs to be handled by some other front-end exception handling methods. There are two most common ways to capture, handle and report exceptions:

  1. window.onerrorCatch global JavaScript exceptions;

    // 在应用入口组件内调用异常捕获
    componentWillMount: function () {
      this.startErrorLog();
    }
    startErrorLog:function() {
      window.onerror = (message, file, line, column, errorObject) => {
        column = column || (window.event && window.event.errorCharacter);
        const stack = errorObject ? errorObject.stack : null;
    
        // trying to get stack from IE
        if (!stack) {
          var stack = [];
          var f = arguments.callee.caller;
          while (f) {
             stack.push(f.name);
             f = f.caller;
          }
          errorObject['stack'] = stack;
        }
    
        const data = {
          message:message,
          file:file,
          line:line,
          column:column,
          errorStack:stack,
        };
    
        // here I make a call to the server to log the error
        reportError(data);
    
        // the error can still be triggered as usual, we just wanted to know what's happening on the client side
        // if return true, this error will not be console log out  
        return false;
        }
    }
    
  2. try, catchManually locate the logical code that is prone to abnormality in the package;

    class Home extends React.Component {
      constructor(props) {
        super(props);
        this.state = { error: null };
      }
    
      handleClick = () => {
        try {
          // Do something that could throw
        } catch (error) {
          this.setState({ error });
        }
      }
    
      render() {
        if (this.state.error) {
          return <h1>Meet Some Errors.</h1>
        }
        return <div onClick={this.handleClick}>Click Me</div>
      }
    }
    

Common open source exception capture and reporting libraries, such as sentry, badjs, etc., use these methods to provide common JavaScript execution exceptions.

refer to

  1. Error Boundaries
  2. Try Catch in Component
  3. Handle React Errors in v15

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325517260&siteId=291194637