[譯]React高級指引4:異常捕獲邊界(Error Boundaries)

原文連接:reactjs.org/docs/error-…html

引言

過去,組件內出現JavaScript異常時會致使React內部的state被破壞而且在下一次渲染時拋出 可能沒法跟蹤的 異常。這些錯誤基本上都是有早期代碼(非React組件代碼)形成的,可是React並無提供可以優雅地在組件中處理和回覆這些異常的方法。react

異常捕獲邊界(Error Boundaries)

在部分UI中出現的JavaScript異常是不該該致使整個應用的崩潰的。爲了解決這個問題,React16引進了一個新的概念「異常捕獲邊界(Error Boundaries)「。git

異常捕獲邊界是一種React組件,它可以捕獲在它子組件樹中出現的任何JavaScript異常,將它們打印出來並展現一個備用UI,這樣就不會致使組件樹的崩潰。異常捕獲邊界可以捕獲它的子組件數中在渲染,生命週期方法和構造函數中出現的任何異常。github

注意: 異常捕獲邊界不會捕獲下列異常:npm

  • 事件處理(瞭解更多)
  • 異步代碼(如setTimeoutrequestAnimationFrame回調函數)
  • 服務端渲染
  • 異常捕獲邊界自身拋出的異常(不是它的子元素拋出的異常)

只要在組件中定義其中一個及以上的生命週期方法(static getDerivedStateFromError()componentDidCatch()),那麼這個組件就變成了異常捕獲邊界。當異常被拋出時使用static getDerivedStateFromError()來渲染一個備用UI,使用componentDidCatch()來打印異常信息。瀏覽器

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    //更新state後再下次渲染時會顯示備用UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    //你也能夠在異常報告設備中打印異常信息
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      //你能夠渲染自定義的任意備用UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
複製代碼

如今你就能夠像正常組件同樣使用它了:bash

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>
複製代碼

異常捕獲邊界的工做機制相似於JavaScript的catch{}模塊,可是它是捕獲組件拋出的異常的。只有class組件可以成爲異常捕獲邊界。在實踐中,絕大部分實踐咱們只想要聲明一個異常捕獲邊界,而後再整個應用中使用它。babel

注意異常捕獲邊界只會捕獲那些做爲它的子組件的組件拋出的異常。但它不會捕獲自身拋出的異常。若是一個異常捕獲邊界未能成功渲染異常信息,那麼它會把這個異常信息傳遞給離他最近的祖先異常捕獲邊界組件。這也是相似於JavaScript catch{}模塊的工做機制。app

異常捕獲邊界的放置位置

異常捕獲邊界的粒度由你來決定。你能夠包裹頂層組件來向用戶展現「有什麼地方出錯了」,就像服務端框架處理崩潰同樣。你也能夠將小部件包裹再異常處理邊界中來防止它崩潰時影響其餘部分。框架

未捕獲異常的新行爲

這一改變有着重要的意義。在React16中,任何沒有被異常捕獲邊界捕獲的異常將會致使整個React組件樹的卸載

對這個決定咱們也存在着爭議,可是在咱們以往的經驗中,不處理崩潰的UI比將它們徹底移除更加糟糕。好比在社交軟件這類產品中,崩潰的UI留在界面上可能會致使用戶將信息發送給錯誤的對象。一樣的,在支付軟件中顯示錯誤的金額比什麼也不展現形成的影響更大。

這一改變意味着當你遷移到React16時,你可能會發現以前沒有發現的錯誤。使用異常捕獲邊界可以讓你的應用在出現異常時提供一個更好的用戶體驗。

好比,Facebook Messager將側邊欄、信息面板、聊天記錄以及信息輸入框包裹在單獨的異常捕獲邊界中,這樣若是其中的某一部分崩潰了,也不影響其餘部分的正常運行。

同時咱們也推薦使用JS錯誤報告服務(或者自行構建),這樣就能夠了解在生產環境中未捕獲的異常信息而且修復它們。

組件棧追蹤

在開發環境中,React16會將渲染過程當中出現的全部異常都打印到控制檯上,即便應用之外地將它們掩蓋了。除了異常信息和JavaScript棧,React16同時還提供了組件棧追蹤功能。如今你能夠看到異常發生在組件樹中的具體位置了。

你也能夠在組件棧追蹤中看到異常所在的文件名和行數。這在經過Create React App建立的項目中默認執行。

若是你沒有使用Create React App,你能夠在Babel配置中手動添加這個插件。注意這只是在開發環境中使用的,在生產環境中必定要關閉這個功能

注意: 在棧追蹤中展現的組件名稱取決於Function.name屬性。若是你想要支持還沒有提供該功能的瀏覽器或設備(好比IE11),考慮在你的打包應用程序中加入一個包含Function.namepolyfill,好比function.name-polyfill。做爲替代的,你也能夠在你的組件中顯式地設置displayName屬性。

關於try/catch?

try/catch很棒但它只能用於命令式代碼(imperative code):

try {
  showButton();
} catch (error) {
  // ...
}
複製代碼

可是React組件是聲明式的而且明確指出了什麼是要被渲染的:

<Button />
複製代碼

異常捕獲邊界保留了React聲明式的特性而且可以像你預期的同樣工做。即便在組件樹的深層層級中的componentDidUpdate方法中調用setState方法發生了異常,它也會將這個異常傳遞給最近的那個異常捕獲邊界。

關於事件處理器

異常捕獲邊界不會捕獲發生在事件處理器中的異常。

React不須要經過異常捕獲邊界來修復發生在事件處理器中的異常。不想render方法和生命週期方法,事件處理器不會再渲染期間被調用。因此即便事件處理器拋出了異常,React任然知道須要展現什麼。

若是你須要在事件處理器中捕獲異常,請使用常規的JavaScript try/catch語句:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    try {
      // 會拋出異常的操做
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <div onClick={this.handleClick}>Click Me</div>
  }
}
複製代碼

注意上面的例子只是展現了常規的JavaScript行爲,並無使用異常捕獲邊界。

自React15的命名更改

React15有一個對異常捕獲支持有限的方法:unstable_handleError。這個方法如今已經再也不起做用了,自使用React16開始,你須要把unstable_handleError方法改成componentDidCatch

爲了這個改變,咱們提供了codemod來自動遷移你的代碼。

相關文章
相關標籤/搜索