在React 15.x版本及以前版本中,组件内的UI异常将中断组件内部状态,致使下一次渲染时触发隐藏异常。React并未提供友好的异常捕获和处理方式,一旦发生异常,应用将不能很好的运行。而React 16版本有所改进。本文主旨就是探寻React异常捕获的现状,问题及解决方案。html
咱们指望的是在UI中发生的一些异常,即组件内异常(指React 组件内发生的异常,包括组件渲染异常,组件生命周期方法异常等),不会中断整个应用,能够以比较友好的方式处理异常,上报异常。在React 16版本之前是比较麻烦的,在React 16中提出了解决方案,将从异常边界(Error Boundaries)开始介绍。react
所谓异常边界,便是标记当前内部发生的异常可以被捕获的区域范围,在此边界内的JavaScript异常能够被捕获到,不会中断应用,这是React 16中提供的一种处理组件内异常的思路。具体实现而言,React提供一种异常边界组件,以捕获并打印子组件树中的JavaScript异常,同时显示一个异常替补UI。git
组件内异常,也就是异常边界组件可以捕获的异常,主要包括:github
固然,异常边界组件依然存在一些没法捕获的异常,主要是异步及服务端触发异常:ajax
前面提到异常边界组件只能捕获其子组件树发生的异常,不能捕获自身抛出的异常,因此有必要注意两点:app
很显然,最终的异常边界组件必然是不涉及业务逻辑的独立中间组件。异步
那么一个异常边界组件如何捕获其子组件树异常呢?很简单,首先它也是一个React组件,而后添加ComponentDidCatch
生命周期方法。ide
建立一个React组件,而后添加ComponentDidCatch
生命周期方法:函数
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;
}
}复制代码
接下来能够像使用普通React组件同样使用该组件:
<ErrorBoundary>
<App />
</ErrorBoundary>复制代码
这是一个新的生命周期方法,使用它能够捕获子组件异常,其原理相似于JavaScript异常捕获器try, catch
。
ComponentDidCatch(error, info)复制代码
error:应用抛出的异常;
info:异常信息,包含ComponentStack
属性对应异常过程当中冒泡的组件栈;
判断组件是否添加componentDidCatch
生命周期方法,添加了,则调用包含异常处理的更新渲染组件方法:
if (inst.componentDidCatch) {
this._updateRenderedComponentWithErrorHandling(
transaction,
unmaskedContext,
);
} else {
this._updateRenderedComponent(transaction, unmaskedContext);
}复制代码
在_updateRenderedComponentWithErrorHandling
里面使用try, catch
捕获异常:
/**
* 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);
}
},复制代码
其实异常边界组件并非忽然出如今React中,在15.x版本中已经有测试React 15 ErrorBoundaries,源码见Github。能够看见在源码中已经存在异常边界组件概念,可是尚不稳定,不推荐使用,从生命周期方法名也能够看出来:unstable_handleError
,这也正是ComponentDidCatch
的前身。
前面提到的都是异常边界组件技术上能够捕获内部子组件异常,对于业务实际项目而言,还有须要思考的地方:
React 16提供的异常边界组件并不能捕获应用中的全部异常,并且React 16之后,全部未被异常边界捕获的异常都将致使React卸载整个应用组件树,因此一般须要经过一些其余前端异常处理方式进行异常捕获,处理和上报等,最多见的有两种方式:
window.onerror
捕获全局JavaScript异常;
// 在应用入口组件内调用异常捕获
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;
}
}复制代码
try, catch
手动定位包裹易出现异常的逻辑代码;
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>
}
}复制代码
常见的开源异常捕获,上报库,如sentry,badjs等都是利用这些方式提供常见的JavaScript执行异常。