過(guò)去組件內(nèi)的的js錯(cuò)誤常常會(huì)破壞React內(nèi)部的狀態(tài),并在下一次渲染時(shí)產(chǎn)生 加密的 錯(cuò)誤信息。
這些錯(cuò)誤總會(huì)早應(yīng)用代碼的早期觸發(fā),但是React并沒(méi)有能提供一種方式能夠在組件內(nèi)部?jī)?yōu)雅的處理,也不能從錯(cuò)誤中恢復(fù)。
錯(cuò)誤邊界介紹
部分ui的異常,不應(yīng)該破壞整個(gè)React應(yīng)用,為了解決這個(gè)問(wèn)題,React16引進(jìn)了一種稱(chēng)為用戶邊界的新概念。
錯(cuò)誤邊界是用于 捕獲其子組件樹(shù)的js錯(cuò)誤異常,記錄錯(cuò)誤并且展示一個(gè)回退的UI的React組件。而不是整個(gè)子組件樹(shù)的異常,錯(cuò)誤組件在渲染期間,生命周期方法內(nèi),以及整個(gè)組件樹(shù)的構(gòu)造函數(shù)內(nèi)捕捉錯(cuò)誤。
錯(cuò)誤邊界無(wú)法捕獲如下錯(cuò)誤:
- 事件處理 (了解更多)
- 異步代碼 (例如
setTimeout或requestAnimationFrame回調(diào)函數(shù)) - 服務(wù)端渲染
- 錯(cuò)誤邊界自身拋出來(lái)的錯(cuò)誤 (而不是其子組件)
如果一個(gè)類(lèi)組件定義了一個(gè)名為`componentDidCatch(ERR,INFO)的方法。那么它成為了一個(gè)錯(cuò)誤邊界。
class ErrorBoundary extends React.Component{
constructor(props){
super(props);
this.state={hasError:false}
}
componentDidCatch(error,info){
this.setState({hasError:true});
logErrorToMyService(error,info);
}
render(){
if(this.state.hasError){
return <h1>somethings went wrong</h1>
}
return this.props.children;
}
}
//然后就可以像一個(gè)普通組件一樣使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
componentDidCatch() 方法機(jī)制類(lèi)似于 JavaScript catch {},但是針對(duì)組件。僅有類(lèi)組件可以成為錯(cuò)誤邊界。實(shí)際上,大多數(shù)時(shí)間你僅想要定義一個(gè)錯(cuò)誤邊界組件并在你的整個(gè)應(yīng)用中使用。
錯(cuò)誤邊界只能捕獲 子組件的錯(cuò)誤,它無(wú)法捕獲自身的錯(cuò)誤。如果一個(gè)錯(cuò)誤邊界無(wú)法渲染錯(cuò)誤信息,那么錯(cuò)誤邊界會(huì)向上冒泡到最接近的錯(cuò)誤邊界。這也類(lèi)似于 js 的catch 的工作機(jī)制。
componentDidCatch(err,info)參數(shù)
其中 ,err是被拋出的參數(shù)。
info是一個(gè)含有componentStack的屬性對(duì)象。這其中包含了組件在錯(cuò)誤期間的信息。
//...
componentDidCatch(error, info) {
/* Example stack information:
in ComponentThatThrows (created by App)
in ErrorBoundary (created by App)
in div (created by App)
in App
*/
logComponentStackToMyService(info.componentStack);
}
//...
在線演示
查看通過(guò) React 16 beta 版本來(lái)定義和使用錯(cuò)誤邊界的例子。
如何放置錯(cuò)誤邊界
錯(cuò)誤邊界的粒度完全取決于你的應(yīng)用,可以將它包裝在最頂層的路由組件,并且為用戶展示一個(gè)發(fā)生異常的 的錯(cuò)誤信息。就像服務(wù)端框架通常處理崩潰那樣。你也可以將單獨(dú)的插件包裝在錯(cuò)誤邊界內(nèi)部以保護(hù)應(yīng)用不受該組件崩潰的影響。
未捕獲錯(cuò)誤(Uncaught Errors)的新行為
這個(gè)改變有著非常重要的意義。自 React 16 開(kāi)始,任何未被錯(cuò)誤邊界捕獲的錯(cuò)誤將會(huì)卸載整個(gè) React 組件樹(shù)。
我們對(duì)這一決定飽含爭(zhēng)論,但在我們的經(jīng)驗(yàn)中放置下一個(gè)錯(cuò)誤的UI比完全移除它要更糟糕。例如,在類(lèi)似 Messenger 的產(chǎn)品中留下一個(gè)異常的可見(jiàn) UI 可能會(huì)導(dǎo)致用戶將信息發(fā)錯(cuò)給別人。類(lèi)似的,對(duì)于支付類(lèi)的應(yīng)用來(lái)說(shuō),什么都不展示也比顯示一堆錯(cuò)誤更好。
這一改變意味著隨著你遷入到 React 16,你將可能會(huì)發(fā)現(xiàn)一些已存在你應(yīng)用中但未曾注意到的崩潰。增加錯(cuò)誤邊界能夠讓你在發(fā)生異常時(shí)提供更好的用戶體驗(yàn)。
例如,F(xiàn)acebook Messenger 將側(cè)邊欄、信息面板,對(duì)話框以及信息輸入框包裝在單獨(dú)的錯(cuò)誤邊界中。如果其中的某些 UI 組件崩潰,其余部分仍然能夠交互。
我們也鼓勵(lì)使用 JS 錯(cuò)誤報(bào)告服務(wù)(或自行構(gòu)建)這樣你能夠掌握在生產(chǎn)環(huán)境中發(fā)生的未捕獲的異常,并將其修復(fù)。
組件棧追蹤
React 16 會(huì)將渲染期間所有在開(kāi)發(fā)環(huán)境下的發(fā)生的錯(cuò)誤打印到控制臺(tái),即使應(yīng)用程序意外的將其掩蓋。除了錯(cuò)誤信息和 JavaScript 棧外,其還提供了組件棧追蹤?,F(xiàn)在你可以準(zhǔn)確地查看發(fā)生在組件樹(shù)內(nèi)的錯(cuò)誤信息:
也可以在組件堆棧中查看文件名和行數(shù)。這一功能在 Create React App 項(xiàng)目中默認(rèn)開(kāi)啟:
若你不使用 Create React App,你可以手動(dòng)添加該插件到你的 Babel 配置中。注意其僅能在開(kāi)發(fā)環(huán)境中使用并禁止在生產(chǎn)環(huán)境中使用。
為何不使用 try/catch?
try / catch 非常棒,但其僅能在命令式代碼(imperative code)下可用:
然而,React 組件是聲明式的并且具體指出 聲明 什么需要被渲染:
<Button />
錯(cuò)誤邊界保留了 React 原生的聲明性質(zhì),且其行為符合你的預(yù)期。例如,即使錯(cuò)誤發(fā)生 componentDidUpdate 時(shí)期由某一個(gè)深層組件樹(shù)中的 setState 調(diào)用引起,其仍然能夠冒泡到最近的錯(cuò)誤邊界。
事件處理器如何處理?
錯(cuò)誤邊界無(wú)法捕獲事件處理器內(nèi)部的錯(cuò)誤。
React 不需要錯(cuò)誤邊界在事件處理器內(nèi)將其從錯(cuò)誤中恢復(fù)。不像渲染方法或生命周期鉤子,事件處理器不會(huì)再渲染周期內(nèi)觸發(fā)。因此若他們拋出異常,React 仍然能夠知道需要在屏幕上顯示什么。
如果你需要在事件處理器內(nèi)部捕獲錯(cuò)誤,使用普通的 JavaScript try / catch 語(yǔ)句:
class MyComponent 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>Caught an error.</h1>
}
return <div onClick={this.handleClick}>Click Me</div>
}
}
注意上述例子僅是說(shuō)明普通的 JavaScript 行為而并未使用錯(cuò)誤邊界。
自 React 15 的名稱(chēng)變更
React 15 在一個(gè)不同的方法名下:unstable_handleError 包含了一個(gè)支持有限的錯(cuò)誤邊界。這一方法不再能用,同時(shí)自 React 16 beta 發(fā)布起你需要在代碼中將其修改為 componentDidCatch。
為這一改變,我們已提供了一個(gè) codemod 來(lái)幫助你自動(dòng)遷移你的代碼。