封裝一個處理 react 異常的最簡 ErrorBoundary 組件 🎅

前言 📝

👉 從 React 16 開始,引入了 Error Boundaries 概念,它能夠捕獲它的子組件中產生的錯誤,記錄錯誤日誌,並展現降級內容,具體 官網地址。 👈

Alt

錯誤邊界避免一個組件錯誤致使整個頁面白屏不能使用等狀況,使用優雅降級的方式呈現備用的 UI,錯誤邊界能夠在渲染期間、生命週期和整個組件樹的構造函數中捕獲錯誤。自 React 16 起,任何未被錯誤邊界捕獲的錯誤將會致使整個 React 組件樹被卸載javascript


ErrorBoundary 意義 🤖

  • 某些 UI 崩潰,不至於整個 webapp 崩潰

在瀏覽頁面時,因爲後端返回異常或者前端的某些錯誤校驗,會致使用戶體驗不好,你想一想,你帶着老婆,坐着火車,吃着火鍋唱着歌,忽然被麻匪劫了,忽然就報錯了,有些場景下,好比正在設置金額,或者查看關鍵頁面時,這樣的體驗就會很糟糕,好比你遊戲充值了 500,結果因爲接口緣由顯示出來充值NaN,這種顯示比不顯示還讓人苦惱,不過相信你們對 JS 異常捕獲很熟悉了,try-catch 一包業務代碼就收工了。不過,在組件裏對異常捕獲,須要用到的是 React 提供的 Error Boundary 錯誤邊界特性,用 componentDidCatch 鉤子來對頁面異常進行捕獲,以致於不會將異常擴散到整個頁面,有效防止頁面白屏。html


官網如何實現 🥔

👉 若是一個 class 組件中定義了 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;
  }
}

而後你能夠將它做爲一個常規組件去使用:前端

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

錯誤邊界的工做方式相似於 JavaScript 的 catch {},不一樣的地方在於錯誤邊界只針對 React 組件。只有 class 組件才能夠成爲錯誤邊界組件。大多數狀況下, 你只須要聲明一次錯誤邊界組件, 並在整個應用中使用它,在使用時被包裹組件出現的錯誤或者throw new Error()拋出的異常均可以被錯誤邊界組件捕獲,而且顯示出兜底 UIjava


封裝一個可配置的 ErrorBoundary 🚲

瞭解了官網實現錯誤邊界組件的方法,咱們能夠封裝一個ErrorBoundary組件,造一個好用的輪子,而不是直接寫死return <h1>Something went wrong</h1>,學習了react-redux原理後咱們知道能夠用高階組件來包裹react組件,將store中的數據和方法全局注入,同理,咱們也可使用高階組件包裹使其成爲一個可以錯誤捕獲的 react 組件react

1️⃣ 創造一個可配置的 ErrorBoundary 類組件

相比與官網的 ErrorBoundary,咱們能夠將日誌上報的方法以及顯示的 UI 經過接受傳參的方式進行動態配置,對於傳入的UI,咱們能夠設置以react組件的方式 或 是一個React Element進行接受,並且經過組件的話,咱們能夠傳入參數,這樣能夠在兜底 UI 中拿到具體的錯誤信息git

  • componentDidCatch() : 錯誤日誌處理的鉤子函數
  • static getDerivedStateFromError() : 它將拋出的錯誤做爲參數,並返回一個值以更新 state
class ErrorBoundary extends React.Component {
  state = { error: false };
  static getDerivedStateFromError(error) {
    return { error };
  }
  componentDidCatch(error, errorInfo) {
    if (this.props.onError) {
      //上報日誌經過父組件注入的函數進行執行
      this.props.onError(error, errorInfo.componentStack);
    }
  }
  render() {
    const { fallback, FallbackComponent } = this.props;
    const { error } = this.state;
    if (error) {
      const fallbackProps = { error };
      //判斷是否爲React Element
      if (React.isValidElement(fallback)) {
        return fallback;
      }
      //組件方式傳入
      if (FallbackComponent) {
        return <FallbackComponent {...fallbackProps} />;
      }
      throw new Error("ErrorBoundary 組件須要傳入兜底UI");
    }
    return this.props.children;
  }
}

這樣就能夠對兜底UI顯示和錯誤日誌進行動態獲取,使組件更加靈活,可是又有一個問題出現,有時候會遇到這種狀況:服務器忽然 50三、502 了,前端獲取不到響應,這時候某個組件報錯了,可是過一會又正常了。比較好的方法是用戶點一下被ErrorBoundary封裝的組件中的一個方法來從新加載出錯組件,不須要重刷頁面,這時候須要兜底的組件中應該暴露出一個方法供ErrorBoundary進行處理github

image-1

  1. 在 ErrorBoundary 中添加方法,檢測是否有注入重置方法,若是有重置方法就執行而且重置 state 中的 error,使其錯誤狀態爲 false
resetErrorBoundary = () => {
  if (this.props.onReset) this.props.onReset();
  this.setState({ error: false });
};
  1. 在 render 中添加函數組件類型進行渲染,能夠將重置的方法以及錯誤信息當作參數進行傳遞到當前組件進行處理
render() {
    const { fallback, FallbackComponent, fallbackRender } = this.props;
    const { error } = this.state;
    if (error) {
      const fallbackProps = {
        error,
        resetErrorBoundary: this.resetErrorBoundary,
      };
      ...
      if (typeof fallbackRender === "function")return fallbackRender(fallbackProps);
      ...
    }
    return this.props.children;
  }
2️⃣ 將 ErrorBoundary 經過高階函數進行包裹返回
import React from "react";
import DefaultErrorBoundary from "./core";
const catchreacterror = (Boundary = DefaultErrorBoundary) => InnerComponent => {
  return props => (
    <Boundary {...props}>
      <InnerComponent {...props} />
    </Boundary>
  );
};

二、使用&測試 🏁

經過一個點擊自增的 Demo,當數字到達某值,拋出異常,這裏分別對 class 組件和 Function 組件做爲發起異常的組件進行測試web

  • 發起異常的組件
//Function組件
const fnCount1 = ({ count }) => {
  if (count == 3) throw new Error("count is three");
  return <span>{count}</span>;
};
//Class組件
class fnCount2 extends React.Component {
  render() {
    const { count } = this.props;
    if (count == 2) throw new Error("count is two");
    return <span>{count}</span>;
  }
}
  • 處理錯誤異常的函數組件
const errorbackfn = ({ error: { message }, resetErrorBoundary }) => (
  <div>
    <p>出錯啦</p>
    <pre>{message}</pre>
    <button onClick={resetErrorBoundary}>Try again</button>
  </div>
);
  • 處理錯誤異常的普通組件
const errorbackcom = () => <h1>出錯啦,不可撤銷</h1>;
  • 測試組件
//對發起異常的組件進行包裹處理,返回一個能夠處理錯誤編輯的高階組件
const SafeCount1 = catchreacterror()(fnCount1);
const SafeCount2 = catchreacterror()(fnCount2);

//測試主組件
const App = () => {
  const [count, setCount] = useState(0);
  const ListenError = (arg, info) => console.log("出錯了:" + arg.message, info); //錯誤時進行的回調
  const onReset = () => setCount(0); //點擊重置時進行的回調
  return (
    <div className="App">
      <section>
        <button onClick={() => setCount(count => count + 1)}>+</button>
        <button onClick={() => setCount(count => count - 1)}>-</button>
      </section>
      <hr />
      <div>
        Class componnet:
        <SafeCount2
          count={count}
          fallbackRender={errorbackfn}
          onReset={onReset}
          onError={ListenError}
        />
      </div>
      <div>
        Function componnet:
        <SafeCount1
          count={count}
          FallbackComponent={errorbackcom}
          onError={ListenError}
        />
      </div>
    </div>
  );
};

demo-1

大功告成!redux

遇到的問題&總結 💢

有不少時候 react 錯誤邊界不是萬能的好比後端

  • 事件錯誤

demo-1
上面 this.o 不存在,會報錯,window.onerror 能夠捕獲,可是錯誤邊界捕獲不到。

  • 異步代碼

demo-1

  • 服務端渲染 和 錯誤邊界本身的錯誤

總結

  • 抽離組件 ✔
  • 錯誤反饋 ✔
  • UI 抽離 ✔
  • 錯誤重置 ✔
  • 抽離 hook 模式 ✖
  • 服務端 ✖

至此,謝謝各位在百忙之中點開這篇文章,但願對大家能有所幫助,相信你對 react 中的錯誤邊界有了大概的認實,也會編寫一個簡單的ErrorBoundary總的來講優化的點還有不少,若有問題歡迎各位大佬指正。

參考文獻

求個 star,謝謝你們了

相關文章
相關標籤/搜索