在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執行異常。