React進(jìn)階筆記12(Error Boundaries)

過(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ò)誤:
  • 事件處理 (了解更多
  • 異步代碼 (例如 setTimeoutrequestAnimationFrame 回調(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)遷移你的代碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,217評(píng)論 25 708
  • 先人乘鶴去,陰陽(yáng)兩相隔。 一歲一思憶,青冢音容絕。
    鼓樓和魚(yú)pandada閱讀 253評(píng)論 0 0
  • 總以為愛(ài)若微塵 便可得絲絲星光 后來(lái)才發(fā)現(xiàn) 凡是卑微的愛(ài) 都沒(méi)有好下場(chǎng)
    我是張小生閱讀 202評(píng)論 0 0
  • 就像阿多尼斯在《我的孤獨(dú)是一座花園》里所說(shuō)的: “當(dāng)我把眼睛沉入你的眼睛,我瞥見(jiàn)幽深的黎明,我看到古老的昨天,看到...
    我是文心閱讀 197評(píng)論 0 0
  • .cer、crt 與 pem、pfx .cer/.crt是用于存放證書(shū),它是2進(jìn)制形式存放的,不含私鑰,含公鑰。....
    程守斌閱讀 4,776評(píng)論 0 0

友情鏈接更多精彩內(nèi)容