咱们指望的是在UI中发生的一些异常,即组件内异常(指React 组件内发生的异常,包括组件渲染异常,组件生命周期方法异常等),不会中断整个应用,能够以比较友好的方式处理异常,上报异常。在React 16版本之前是比较麻烦的,在React 16中提出了解决方案,将从异常边界(Error Boundaries)开始介绍。html
所谓异常边界,便是标记当前内部发生的异常可以被捕获的区域范围,在此边界内的JavaScript异常能够被捕获到,不会中断应用,这是React 16中提供的一种处理组件内异常的思路。具体实现而言,React提供一种异常边界组件,以捕获并打印子组件树中的JavaScript异常,同时显示一个异常替补UI。前端
组件内异常,也就是异常边界组件可以捕获的异常,主要包括:react
固然,异常边界组件依然存在一些没法捕获的异常,主要是异步及服务端触发异常:git
前面提到异常边界组件只能捕获其子组件树发生的异常,不能捕获自身抛出的异常,因此有必要注意两点:github
很显然,最终的异常边界组件必然是不涉及业务逻辑的独立中间组件。ajax
那么一个异常边界组件如何捕获其子组件树异常呢?很简单,首先它也是一个React组件,而后添加ComponentDidCatch
生命周期方法。app
建立一个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组件同样使用该组件:ide
<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中,在0.15版本中已经有测试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执行异常。