咱們指望的是在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執行異常。