封裝一個(gè)最簡(jiǎn)單ErrorBoundary組件處理react異常
前言
從 React 16 開始,引入了 Error Boundaries 概念,它可以捕獲它的子組件中產(chǎn)生的錯(cuò)誤,記錄錯(cuò)誤日志,并展示降級(jí)內(nèi)容,具體 官網(wǎng)地址

錯(cuò)誤邊界避免一個(gè)組件錯(cuò)誤導(dǎo)致整個(gè)頁面白屏不能使用等情況,使用優(yōu)雅降級(jí)的方式呈現(xiàn)備用的 UI,錯(cuò)誤邊界可以在渲染期間、生命周期和整個(gè)組件樹的構(gòu)造函數(shù)中捕獲錯(cuò)誤。自 React 16 起,任何未被錯(cuò)誤邊界捕獲的錯(cuò)誤將會(huì)導(dǎo)致整個(gè) React 組件樹被卸載
ErrorBoundary 意義
- 某些 UI 崩潰,不至于整個(gè) webapp 崩潰
在瀏覽頁面時(shí),由于后端返回異?;蛘咔岸说哪承╁e(cuò)誤校驗(yàn),會(huì)導(dǎo)致用戶體驗(yàn)很差,你想想,你帶著老婆,坐著火車,吃著火鍋唱著歌,突然被麻匪劫了,突然就報(bào)錯(cuò)了,有些場(chǎng)景下,比如正在設(shè)置金額,或者查看關(guān)鍵頁面時(shí),這樣的體驗(yàn)就會(huì)很糟糕,比如你游戲充值了 500,結(jié)果由于接口原因顯示出來充值NaN,這種顯示比不顯示還讓人苦惱,不過相信大家對(duì) JS 異常捕獲很熟悉了,try-catch 一包業(yè)務(wù)代碼就收工了。不過,在組件里對(duì)異常捕獲,需要用到的是 React 提供的 Error Boundary 錯(cuò)誤邊界特性,用 componentDidCatch 鉤子來對(duì)頁面異常進(jìn)行捕獲,以至于不會(huì)將異常擴(kuò)散到整個(gè)頁面,有效防止頁面白屏。
官網(wǎng)如何實(shí)現(xiàn)
👉 如果一個(gè) class 組件中定義了 static getDerivedStateFromError() 或 componentDidCatch() 這兩個(gè)生命周期方法中的任意一個(gè)(或兩個(gè))時(shí),那么它就變成一個(gè)錯(cuò)誤邊界。當(dāng)拋出錯(cuò)誤后,請(qǐng)使用 static getDerivedStateFromError() 渲染備用 UI ,使用 componentDidCatch() 打印錯(cuò)誤信息 👈
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能夠顯示降級(jí)后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同樣可以將錯(cuò)誤日志上報(bào)給服務(wù)器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定義降級(jí)后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后你可以將它作為一個(gè)常規(guī)組件去使用:
<ErrorBoundary> <MyWidget /> </ErrorBoundary>
錯(cuò)誤邊界的工作方式類似于 JavaScript 的 catch {},不同的地方在于錯(cuò)誤邊界只針對(duì) React 組件。只有 class 組件才可以成為錯(cuò)誤邊界組件。大多數(shù)情況下, 你只需要聲明一次錯(cuò)誤邊界組件, 并在整個(gè)應(yīng)用中使用它,在使用時(shí)被包裹組件出現(xiàn)的錯(cuò)誤或者throw new Error()拋出的異常都可以被錯(cuò)誤邊界組件捕獲,并且顯示出兜底 UI
封裝一個(gè)可配置的 ErrorBoundary
了解了官網(wǎng)實(shí)現(xiàn)錯(cuò)誤邊界組件的方法,我們可以封裝一個(gè)ErrorBoundary組件,造一個(gè)好用的輪子,而不是直接寫死return <h1>Something went wrong</h1>,學(xué)習(xí)了react-redux原理后我們知道可以用高階組件來包裹react組件,將store中的數(shù)據(jù)和方法全局注入,同理,我們也可以使用高階組件包裹使其成為一個(gè)能夠錯(cuò)誤捕獲的 react 組件
1.創(chuàng)造一個(gè)可配置的 ErrorBoundary 類組件
相比與官網(wǎng)的 ErrorBoundary,我們可以將日志上報(bào)的方法以及顯示的 UI 通過接受傳參的方式進(jìn)行動(dòng)態(tài)配置,對(duì)于傳入的UI,我們可以設(shè)置以react組件的方式 或 是一個(gè)React Element進(jìn)行接受,而且通過組件的話,我們可以傳入?yún)?shù),這樣可以在兜底 UI 中拿到具體的錯(cuò)誤信息
- componentDidCatch() : 錯(cuò)誤日志處理的鉤子函數(shù)
- static getDerivedStateFromError() : 它將拋出的錯(cuò)誤作為參數(shù),并返回一個(gè)值以更新 state
class ErrorBoundary extends React.Component {
state = { error: false };
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, errorInfo) {
if (this.props.onError) {
//上報(bào)日志通過父組件注入的函數(shù)進(jìn)行執(zhí)行
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;
}
}
這樣就可以對(duì)兜底UI顯示和錯(cuò)誤日志進(jìn)行動(dòng)態(tài)獲取,使組件更加靈活,但是又有一個(gè)問題出現(xiàn),有時(shí)候會(huì)遇到這種情況:服務(wù)器突然 503、502 了,前端獲取不到響應(yīng),這時(shí)候某個(gè)組件報(bào)錯(cuò)了,但是過一會(huì)又正常了。比較好的方法是用戶點(diǎn)一下被ErrorBoundary封裝的組件中的一個(gè)方法來重新加載出錯(cuò)組件,不需要重刷頁面,這時(shí)候需要兜底的組件中應(yīng)該暴露出一個(gè)方法供ErrorBoundary進(jìn)行處理

1.在 ErrorBoundary 中添加方法,檢測(cè)是否有注入重置方法,如果有重置方法就執(zhí)行并且重置 state 中的 error,使其錯(cuò)誤狀態(tài)為 false
resetErrorBoundary = () => {
if (this.props.onReset) this.props.onReset();
this.setState({ error: false });
};
2.在 render 中添加函數(shù)組件類型進(jìn)行渲染,可以將重置的方法以及錯(cuò)誤信息當(dāng)做參數(shù)進(jìn)行傳遞到當(dāng)前組件進(jìn)行處理
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 通過高階函數(shù)進(jìn)行包裹返回
import React from "react";
import DefaultErrorBoundary from "./core";
const catchreacterror = (Boundary = DefaultErrorBoundary) => InnerComponent => {
return props => (
<Boundary {...props}>
<InnerComponent {...props} />
</Boundary>
);
};
使用&測(cè)試
通過一個(gè)點(diǎn)擊自增的 Demo,當(dāng)數(shù)字到達(dá)某值,拋出異常,這里分別對(duì) class 組件和 Function 組件作為發(fā)起異常的組件進(jìn)行測(cè)試
- 發(fā)起異常的組件
//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>;
}
}
- 處理錯(cuò)誤異常的函數(shù)組件
const errorbackfn = ({ error: { message }, resetErrorBoundary }) => (
<div>
<p>出錯(cuò)啦</p>
<pre>{message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
- 處理錯(cuò)誤異常的普通組件
const errorbackcom = () => <h1>出錯(cuò)啦,不可撤銷</h1>;
- 測(cè)試組件
//對(duì)發(fā)起異常的組件進(jìn)行包裹處理,返回一個(gè)可以處理錯(cuò)誤編輯的高階組件
const SafeCount1 = catchreacterror()(fnCount1);
const SafeCount2 = catchreacterror()(fnCount2);
//測(cè)試主組件
const App = () => {
const [count, setCount] = useState(0);
const ListenError = (arg, info) => console.log("出錯(cuò)了:" + arg.message, info); //錯(cuò)誤時(shí)進(jìn)行的回調(diào)
const onReset = () => setCount(0); //點(diǎn)擊重置時(shí)進(jìn)行的回調(diào)
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>
);
};

大功告成!
遇到的問題&總結(jié)
有很多時(shí)候 react 錯(cuò)誤邊界不是萬能的比如
- 事件錯(cuò)誤

上面 this.o 不存在,會(huì)報(bào)錯(cuò),window.onerror 可以捕獲,但是錯(cuò)誤邊界捕獲不到。
- 異步代碼

服務(wù)端渲染 和 錯(cuò)誤邊界自己的錯(cuò)誤
總結(jié)
- 抽離組件 ✔
- 錯(cuò)誤反饋 ✔
- UI 抽離 ✔
- 錯(cuò)誤重置 ✔
- 抽離 hook 模式 ✖
- 服務(wù)端 ✖
至此,謝謝各位在百忙之中點(diǎn)開這篇文章,希望對(duì)你們能有所幫助,相信你對(duì) react 中的錯(cuò)誤邊界有了大概的認(rèn)實(shí),也會(huì)編寫一個(gè)簡(jiǎn)單的ErrorBoundary總的來說優(yōu)化的點(diǎn)還有很多,如有問題歡迎各位大佬指正。更多關(guān)于ErrorBoundary,react的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
React如何使用sortablejs實(shí)現(xiàn)拖拽排序
這篇文章主要介紹了React如何使用sortablejs實(shí)現(xiàn)拖拽排序問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過程詳解
這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過程詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
React中使用react-json-view展示JSON數(shù)據(jù)的操作方法
react-json-view是一個(gè)用于顯示和編輯javascript數(shù)組和JSON對(duì)象的React組件,本文給大家分享React中使用react-json-view展示JSON數(shù)據(jù)的操作方法,感興趣的朋友一起看看吧2023-12-12
react koa rematch 如何打造一套服務(wù)端渲染架子
這篇文章主要介紹了react koa rematch 如何打造一套服務(wù)端渲染架子,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
React?Hooks項(xiàng)目實(shí)戰(zhàn)
React?Hooks是React?16.8版本引入的新特性,它使得在函數(shù)組件中也能夠使用狀態(tài)(state)和其他React特性,本文就來詳細(xì)介紹一下React?Hooks項(xiàng)目實(shí)戰(zhàn),感興趣的可以了解一下2023-11-11

