前言 ??
?? 從 React 16 開始,引入了 Error Boundaries 概念,它可以捕獲它的子組件中產生的錯誤,記錄錯誤日志,并展示降級內容,具體 官網地址, ??
錯誤邊界避免一個組件錯誤導致整個頁面白屏不能使用等情況,使用優雅降級的方式呈現備用的 UI,錯誤邊界可以在渲染期間、生命周期和整個組件樹的建構式中捕獲錯誤,自 React 16 起,任何未被錯誤邊界捕獲的錯誤將會導致整個 React 組件樹被卸載
ErrorBoundary 意義 ??
- 某些 UI 崩潰,不至于整個 webapp 崩潰
在瀏覽頁面時,由于后端回傳例外或者前端的某些錯誤校驗,會導致用戶體驗很差,你想想,你帶著老婆,坐著火車,吃著火鍋唱著歌,突然被麻匪劫了,突然就報錯了,有些場景下,比如正在設定金額,或者查看關鍵頁面時,這樣的體驗就會很糟糕,比如你游戲充值了 500,結果由于介面原因顯示出來充值NaN,這種顯示比不顯示還讓人苦惱,不過相信大家對 JS 例外捕獲很熟悉了,try-catch 一包業務代碼就收工了,不過,在組件里對例外捕獲,需要用到的是 React 提供的 Error Boundary 錯誤邊界特性,用 componentDidCatch 鉤子來對頁面例外進行捕獲,以至于不會將例外擴散到整個頁面,有效防止頁面白屏,
官網如何實作 ??
?? 如果一個 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()拋出的例外都可以被錯誤邊界組件捕獲,并且顯示出兜底 UI
封裝一個可配置的 ErrorBoundary ??
了解了官網實作錯誤邊界組件的方法,我們可以封裝一個ErrorBoundary組件,造一個好用的輪子,而不是直接寫死return <h1>Something went wrong</h1>,學習了react-redux原理后我們知道可以用高階組件來包裹react組件,將store中的資料和方法全域注入,同理,我們也可以使用高階組件包裹使其成為一個能夠錯誤捕獲的 react 組件
1?? 創造一個可配置的 ErrorBoundary 類組件
相比與官網的 ErrorBoundary,我們可以將日志上報的方法以及顯示的 UI 通過接受傳參的方式進行動態配置,對于傳入的UI,我們可以設定以react組件的方式 或 是一個React Element進行接受,而且通過組件的話,我們可以傳入引數,這樣可以在兜底 UI 中拿到具體的錯誤資訊
- 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顯示和錯誤日志進行動態獲取,使組件更加靈活,但是又有一個問題出現,有時候會遇到這種情況:服務器突然 503、502 了,前端獲取不到回應,這時候某個組件報錯了,但是過一會又正常了,比較好的方法是用戶點一下被ErrorBoundary封裝的組件中的一個方法來重新加載出錯組件,不需要重刷頁面,這時候需要兜底的組件中應該暴露出一個方法供ErrorBoundary進行處理
- 在 ErrorBoundary 中添加方法,檢測是否有注入重置方法,如果有重置方法就執行并且重置 state 中的 error,使其錯誤狀態為 false
resetErrorBoundary = () => {
if (this.props.onReset) this.props.onReset();
this.setState({ error: false });
};
- 在 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 組件作為發起例外的組件進行測驗
- 發起例外的組件
//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}
one rror={ListenError}
/>
</div>
<div>
Function componnet:
<SafeCount1
count={count}
FallbackComponent={errorbackcom}
one rror={ListenError}
/>
</div>
</div>
);
};
大功告成!
遇到的問題&總結 ??
有很多時候 react 錯誤邊界不是萬能的比如
- 事件錯誤
上面 this.o 不存在,會報錯,window.onerror 可以捕獲,但是錯誤邊界捕獲不到,
- 異步代碼
- 服務端渲染 和 錯誤邊界自己的錯誤
總結
- 抽離組件 ?
- 錯誤反饋 ?
- UI 抽離 ?
- 錯誤重置 ?
- 抽離 hook 模式 ?
- 服務端 ?
至此,謝謝各位在百忙之中點開這篇文章,希望對你們能有所幫助,相信你對 react 中的錯誤邊界有了大概的認實,也會撰寫一個簡單的ErrorBoundary總的來說優化的點還有很多,如有問題歡迎各位大佬指正,
- ??:跳轉 github
參考文獻
- ??:React.js |錯誤邊界組件
- ??:捕獲 React 例外
- ??:造一個 React 錯誤邊界的輪子
- ??:錯誤邊界(Error Boundaries)
- ??:深入淺出 React 的例外錯誤邊界
求個 star,謝謝大家了
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/276603.html
標籤:其他
