你能用 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
是一个带有 message
和 type
的简单包装,this._target
是对 window.console
的引用。所以我对 info
、warn
和 error
.
做了同样的事情
此外,我创建了一个方法 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 异常)上进行调整或在拦截器中吞下它,这样它就不会加倍。
我制作了一个未 运行 的 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
是一个带有 message
和 type
的简单包装,this._target
是对 window.console
的引用。所以我对 info
、warn
和 error
.
此外,我创建了一个方法 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 异常)上进行调整或在拦截器中吞下它,这样它就不会加倍。