你能用 try/catch 块捕获 React.js 应用程序的所有错误吗?

can you catch all errors of a React.js app with a try/catch block?

我制作了一个未 运行 的 React 应用程序,使用它的人注意到偶尔会出现一些奇怪的错误。我不知道为什么或发生了什么,无法重现。

所以我想知道是否有办法将整个应用程序或部分应用程序包装在 try/catch 块中,以便我可以将错误发送到服务器上的错误日志?

到目前为止,我所读到的是,您可以将整个渲染函数包装在一个 try/catch 中,但这不会捕获由于用户交互而导致的任何错误,对吗?

我遇到了同样的问题。我创建了一个 Office 应用程序,其中既没有调试控制台也没有开发人员工具,所以我找不到错误发生的地方。

我创建了一个组件(一个 es6-class)来捕获所有 console 消息,将消息保存到一个单独的数组中并调用 "real" console功能。

log(message) {
    const msg = new Log(message);
    this.pushMessage(msg);
    this._target.log(message);
}

其中 Log 是一个带有 messagetype 的简单包装,this._target 是对 window.console 的引用。所以我对 infowarnerror.

做了同样的事情

此外,我创建了一个方法 handleThrownErrors(message, url, lineNumber) 来捕获异常。

window.onerror = this.handleThrownErrors.bind(this);

至少我创建了一个 class 的实例(我称之为 LogCollector)并将其附加到 window.

window.logCollector = new LogCollector();

现在我创建了一个反应组件,它获取 logCollector 实例 (window.logCollector) 作为 属性。 React 组件会定期检查收集到的消息并将其显示在屏幕上。

componentDidMount() {
    this.setInterval(this._forceUpdate, 500);
},

_forceUpdate() {
    this.setState({update: !this.state.update});
}

this.setInterval()是一个自己的函数,只是调用window.setInterval()

并且在 render() 方法中:

return (
    <div class="console">
        {this.props.logCollector.getMessages().map(this.createConsoleMessage)}
    </div>
);

NOTE: It is important to include the LogCollector before all other files.

NOTE: The above solution as a very simplified version. For example: You can improve it by adding custom (message-) listeners, or catching 404 Not found errors (for js-scripts and css-files).

这就是我最终使用的

编辑:React 16 介绍了执行此操作的正确方法,请参阅@goldylucks 的回答。

  componentWillMount() {
    this.startErrorLog();
  }

  startErrorLog() {
    window.onerror = (message, file, line, column, errorObject) => {
      column = column || (window.event && window.event.errorCharacter);
      var 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;
      }

      var data = {
        message: message,
        file: file,
        line: line,
        column: column,
        errorStack: stack
      };

      //here I make a call to the server to log the error

      //the error can still be triggered as usual, we just wanted to know what's happening on the client side
      return false;
    };
  }

您可以利用 React 的 BatchingStrategy API 轻松地将 try/catch 包裹在您的所有 React 代码中。与 window.onerror 相比,这样做的好处是您可以在所有浏览器中获得良好的堆栈跟踪。即使像 Microsoft Edge 和 Safari 这样的现代浏览器也不提供 window.onerror.

的堆栈跟踪

这是 React 15.4 的样子:

import ReactUpdates from "react-dom/lib/ReactUpdates";
import ReactDefaultBatchingStrategy from "react-dom/lib/ReactDefaultBatchingStrategy";

let isHandlingError = false;
const ReactTryCatchBatchingStrategy = {
  // this is part of the BatchingStrategy API. simply pass along
  // what the default batching strategy would do.
  get isBatchingUpdates () { return ReactDefaultBatchingStrategy.isBatchingUpdates; },

  batchedUpdates (...args) {
    try {
      ReactDefaultBatchingStrategy.batchedUpdates(...args);
    } catch (e) {
      if (isHandlingError) {
        // our error handling code threw an error. just throw now
        throw e;
      }

      isHandlingError = true;
      try {
        // dispatch redux action notifying the app that an error occurred.
        // replace this with whatever error handling logic you like.
        store.dispatch(appTriggeredError(e));
      } finally {
        isHandlingError = false;
      }
    }
  },
};

ReactUpdates.injection.injectBatchingStrategy(ReactTryCatchBatchingStrategy);

全文在这里:https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/

引入 React 16 Error Boundaries and the componentDidCatch lifecycle method:

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>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

那你就可以把它当作常规组件来使用了:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

或者您可以使用 npm 包 react-error-boundary 包装您的根组件,并设置后备组件和行为。

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

const myErrorHandler = (error: Error, componentStack: string) => {
  // ...
};

<ErrorBoundary onError={myErrorHandler}>
  <ComponentThatMayError />
</ErrorBoundary>

Error boundaries 太有限,无法捕获所有错误。

在 React 17 中,捕获所有错误,例如:

  • 来自承诺的事件(事件处理程序 on click),
  • 以及 undefined exception 等同步异常

您需要两个全局处理程序:

// TypeScript

export function registerHandlers(store: Store) {
  
  window.addEventListener("error", (event) => {
    store.dispatch<any>(setErrorAction({ message: event.message }));
  });

  window.addEventListener("unhandledrejection", (event: PromiseRejectionEvent) => {
    store.dispatch<any>(setErrorAction({ message: event.reason.message }));
  });
}

在创建 Redux Store 后调用它,结果所有异常都将传递给 Redux,因此您可以 useSelector 获取它并在某处显示或记录(例如发送到存储服务器)。

为了更好地覆盖 HTTP 错误,您可以在 Axios Response Interceptor 上捕获它们并从那里推送到存储(您将获得有关该错误的更多信息)。只需在 unhandledrejection(未处理的 promise 异常)上进行调整或在拦截器中吞下它,这样它就不会加倍。