👉 從 React 16 開始,引入了 Error Boundaries 概念,它能夠捕獲它的子組件中產生的錯誤,記錄錯誤日誌,並展現降級內容,具體 官網地址。 👈
錯誤邊界避免一個組件錯誤致使整個頁面白屏不能使用等狀況,使用優雅降級的方式呈現備用的 UI,錯誤邊界能夠在渲染期間、生命週期和整個組件樹的構造函數中捕獲錯誤。自 React 16 起,任何未被錯誤邊界捕獲的錯誤將會致使整個 React 組件樹被卸載javascript
在瀏覽頁面時,因爲後端返回異常或者前端的某些錯誤校驗,會致使用戶體驗不好,你想一想,你帶着老婆,坐着火車,吃着火鍋唱着歌,忽然被麻匪劫了,忽然就報錯了,有些場景下,好比正在設置金額,或者查看關鍵頁面時,這樣的體驗就會很糟糕,好比你遊戲充值了 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
組件,造一個好用的輪子,而不是直接寫死return <h1>Something went wrong</h1>
,學習了react-redux
原理後咱們知道能夠用高階組件來包裹react
組件,將store
中的數據和方法全局注入,同理,咱們也可使用高階組件包裹使其成爲一個可以錯誤捕獲的 react 組件react
相比與官網的 ErrorBoundary
,咱們能夠將日誌上報的方法以及顯示的 UI
經過接受傳參的方式進行動態配置,對於傳入的UI
,咱們能夠設置以react
組件的方式 或 是一個React Element
進行接受,並且經過組件的話,咱們能夠傳入參數,這樣能夠在兜底 UI 中拿到具體的錯誤信息git
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
resetErrorBoundary = () => { if (this.props.onReset) this.props.onReset(); this.setState({ error: false }); };
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; }
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> ); };
大功告成!redux
有不少時候 react 錯誤邊界不是萬能的好比後端
上面 this.o 不存在,會報錯,window.onerror 能夠捕獲,可是錯誤邊界捕獲不到。
總結
至此,謝謝各位在百忙之中點開這篇文章,但願對大家能有所幫助,相信你對 react 中的錯誤邊界有了大概的認實,也會編寫一個簡單的ErrorBoundary
總的來講優化的點還有不少,若有問題歡迎各位大佬指正。
求個 star,謝謝你們了