[譯]React高級話題之Error Boundaries

前言

本文爲意譯,翻譯過程當中摻雜本人的理解,若有誤導,請放棄繼續閱讀。javascript

原文地址:Error Boundarieshtml

在過去,React組件內部的javascript錯誤每每會讓React內部的state變得不可用,而且會在下一次的渲染過程當中產生模棱兩可的錯誤信息。這些錯誤經常是由APP先前的錯誤所引發的,可是React並無提供一個優雅的方案去在組件內部處理這種錯誤,並將APP恢復到正常的狀態。java

正文

介紹Error Boundaries

應用中局部UI中的javascript錯誤按理說不該該致使整個應用的崩潰。爲了幫助React用戶解決這種問題,React在16.x.x中引入了新的概念-「error boundary」。react

什麼是「error boundary」呢?「error boundary」就是一種可以捕獲它的子組件樹所產生的錯誤的React組件。在這種組件裏,你可以把這些錯誤日誌打印出來,又或者相比簡單粗暴地把組件樹崩潰後的界面呈現給用戶,你能夠呈現一個精心設計過的備用界面給用戶(爲了強調error boundary是一個組件,我後面的翻譯過程當中使用<Error boundary>來指代)。<Error boundary>能捕獲在渲染過程當中,全部子組件的constructors和生命週期函數裏面發生的錯誤。git

<Error boundary>不能捕獲如下類型的錯誤:github

  • 發生在事件處理器裏面的。
  • 異步代碼。例如 setTimeout,或者requestAnimationFrame的callbacks。
  • 服務端渲染
  • <Error boundary>自己拋出的錯誤。

從代碼的層面來講,只要一個class component定義了static getDerivedStateFromError()componentDidCatch()方法中的一個,又或者兩個都定義了,咱們就說它是一個<Error boundary>。上面提到的兩個方法實際上是有分工的。通常來講,static getDerivedStateFromError()是不容許發生反作用的,故是負責呈現一個備用的UI給用戶。componentDidCatch()容許發生反作用,故負責打印錯誤日誌,發送錯誤日誌到遠程服務器等。以下:npm

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

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // 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>Something went wrong.</h1>;
    }

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

而後呢,你能夠把它當作一個普通的組件來用:瀏覽器

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

<Error boundary>使用起來就像catch {}語句,只不過它是用於React component而已。只有class component才能成爲<Error boundary>。在實際應用中,大多數狀況,你只須要定義一個<Error boundary>,而後處處使用它。服務器

注意,<Error boundary>是不能捕獲本身所產生的錯誤,只能捕獲在它之下的組件樹所產生的錯誤。在<Error boundary>嵌套使用的狀況下,若是某個<Error boundary>不能渲染一些錯誤信息(調用static getDerivedStateFromError()失敗?),那麼這個錯誤就會往上冒泡到層級最近的個<Error boundary>。這也是catch{}語句在javascript裏面的執行機制。babel

在線Demo

查看如何在React 16版本中定義和使用 error boundary

在哪裏「放置」Error Boundaries

在組件樹中「放置」<Error boundary>的粒度徹底取決於你。你能夠把你最頂級的route component包裹在<Error boundary>中,而後讓<Error boundary>呈現一個備用的界面給用戶,例如「Something went wrong」。服務端渲染常常就是這樣應對應用崩潰的。你也能夠把多個組件分開包裹在<Error boundary>中,以此隔離局部UI之間的影響。

錯誤捕獲的新行爲

在React 16版本中,任何沒有被<Error boundary>捕獲的錯誤都會致使整一顆React組件樹的卸載。這麼作,是有咱們本身的考量的。

關於這個決定,咱們是有爭論過的。可是,依據咱們以往的經驗來看,遺留一個不正常(corrupted)的頁面給用戶比徹底不顯示更糟糕。舉個例子,在Messenger這種的產品中,把一個不正常的頁面給用戶會致使信息錯發給別的人。一樣的,對於一些涉及到支付的應用,狀況會更糟糕。由於涉及到錢的問題都是大問題啊,因此說寧願什麼都不顯示,也不要顯示一個錯誤的金額數字給用戶。

這種錯誤捕獲的新行爲對你是有影響的。假如你已經遷移到React 16版本上面來,你應該去檢查一下你的應用,看看哪些地方有可能致使應用崩潰。而後,在對應的地方添加<Error boundary>,經過備用UI界面來提供一個更好的用戶體驗。

咱們來看看,Facebook的Messenger是怎麼作的。Messenger分別將sidebar的內容,info panel,conversation log和 message input等區域包裹在不用的<Error boundary>中去。若是這些區域中的某個組件發生了錯誤,那麼影響範圍也就僅僅限定在這個區域中而已,別的區域將不會受到影響的。

使用<Error boundary>的同時,咱們也鼓勵你去使用js錯誤報告服務(或本身搭建一個服務器),好讓你能在第一時間瞭解到在生產環境所產生的未處理異常,並及時修復它。

組件棧的追蹤

在開發環境下,React 16會將渲染過程當中出現的全部錯誤打印在控制檯。這也包括了那些應用程序靜默處理的錯誤(even if the application accidentally swallows them.)。除了錯誤信息和js的調用棧,React 16還會打印組件樹的棧追蹤。如今,你能夠看到錯誤是發生在組件樹中的哪一個位置了。

在組件棧追蹤裏面,你也以可查看錯誤組件代碼所在的文件和行號。在由create-react-app建立的項目裏面,這是默認行爲:

若是你沒有使用create-react-app來建立你的應用腳手架,那麼你也能夠經過給你的Babel手動地添加這個插件來實現這個功能。注意,這個功能只應該在開發環境使用,假如你是經過給Babel配置來實現這個功能的話,那麼你記得在生產環境下禁用它。

注意:在組件追蹤棧上顯示的組件名是基於Function.name屬性的。假如你須要支持一些沒有在原生層級實現了這個特性的老瀏覽器或者設備(例如:IE11),那麼就能夠考慮引用polyfill(function.name-polyfill)到你的代碼中。除此以外,你也能夠顯式地指定組件的displayName屬性的值。

那try/catch怎麼辦啊?

try / catch 是挺好用的,可是也僅僅針對命令式代碼好用而已:

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

然而,React組件是經過聲明式的編碼來指定咱們想要渲染什麼:

<Button />
複製代碼

<Error boundary>保留了React天生的聲明式特性,使用它的結果也會如你所願的。舉個例子說,在componentDidUpdate方法裏面發生了一個錯誤,即便這個錯誤是由層級很深的組件在調用setState的時候引發的,這個錯誤仍是會正確地冒泡到離它最近的那個<Error boundary>中去,並被它所捕獲。

那Event Handlers怎麼辦啊?

<Error boundary>沒法捕獲發生在事件處理器中的錯誤。

React不須要<Error boundary>把應用從事件處理器中發生的錯誤所引發的崩潰中恢復過來。不像render方法和生命週期方法,事件處理器的調用並無發生在渲染過程當中。可是,假如在事件處理器中拋出錯誤了,React也知道該如何顯示它。

假設,你須要在事件處理器中捕獲一個錯誤,你可使用常規的try/catch語句:

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

  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>
  }
}
複製代碼

React 15到React 16的命名更改

React 15包含了一個叫unstable_handleError的方法。這個方法對error boundaries功能的實現提供了一些有限的支持。這個方法在React 16 beta 版以後就不可用了。你能夠將它替換爲componentDidCatch方法。爲了支持API遷移,咱們提供了一個叫codemod的類庫幫助你自動升級。

相關文章
相關標籤/搜索