更強大的 React 錯誤處理方式!
在構建應用時,錯誤是不可避免的。即使采用最佳的代碼實踐,運行時也可能會出現意外錯誤,導致應用崩潰。因此,處理錯誤是非常重要的。本文就來看看如何在 React 中捕獲和處理錯誤,以及更強大的 React 錯誤處理方式!
前言
那為什么要在 React 中捕獲錯誤呢?因為從 React 16 開始,React 生命周期中的錯誤如果未被停止,整個應用就會自行卸載,導致崩潰。在此之前,即使組件中存在一些小錯誤,它也會保留在屏幕上,但是現在,UI中某些無關緊要的部分,甚至是無法控制的某些第三方庫中的未捕獲錯誤,都可能導致頁面崩潰,出現白屏的現象,影響用戶體驗。所以,捕獲并處理 React 中的錯誤至關重要。
在JavaScript中,我們可以使用 try...catch 來捕獲錯誤,例如:
const fetchData = async () => {
try {
return await fetch("https://some-url-that-might-fail.com");
} catch (error) {
console.error(error);
return error;
}
};
try...catch僅適用于命令式代碼,例如數據獲??;而不是適用于聲明式代碼,例如在組件中編寫的 JSX 就是聲明式代碼。那該如何在 React 中捕獲錯誤呢?幸運的是,在 React 16 中,引入了一個新概念:React Error Boundary。下面來看一下它是什么,以及如何使用。
React Error Boundary
React Error Boundary (錯誤邊界)是 React 應用中錯誤處理的一種方式。它是一個 React 組件,可以捕獲子組件樹中任何位置的 JavaScript 錯誤,并記錄這些錯誤,顯示一個備選 UI,而不是一個崩潰的組件樹(白屏)。它們就像一個 JavaScript 的 catch {} 塊,但是只針對組件。
基本使用
在錯誤邊界引入之前,組件內部的錯誤會不斷傳播并最終導致白屏或錯誤的 UI,影響用戶體驗。但是,使用錯誤邊界,這些未處理的錯誤可以被有效地包含和管理。
我們可以在整個應用范圍內設置錯誤邊界,也可以在各個組件上進行更細粒度的控制。需要注意的是,錯誤邊界只會捕獲渲染時、生命周期方法和構造函數中的錯誤,但不會捕獲以下錯誤:
- 事件處理(對于這種情況,需要使用常規(guī)的 try/catch 塊)
- 異步代碼(例如,setTimeout 或 requestAnimationFrame 回調函數)
- 服務端渲染
- 錯誤發(fā)生在錯誤邊界本身而不是其子組件中時
錯誤邊界是在 React v16 中引入的,要使用錯誤邊界,需要定義一個類組件,并添加以下生命周期方法之一或兩個:
- getDerivedStateFromError(): 這個生命周期方法在錯誤拋出后渲染備選 UI。它在渲染階段被調用,所以不允許有副作用。
- componentDidCatch(): 這個方法用于記錄錯誤信息。它在提交階段被調用,所以可以執(zhí)行副作用。
下面來看一個 ErrorBoundary 類組件的例子,它實現了 getDerivedStateFromError() 和 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) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 可以渲染任意自定義的備選 UI
return <h1>出錯啦!</h1>;
}
return this.props.children;
}
}
// 在組件中使用
class App extends React.Component {
render() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
}
這里定義了一個名為 ErrorBoundary 的錯誤邊界組件。它的構造函數初始化了狀態(tài)對象,并設置 hasError 屬性為 false,表示當前沒有發(fā)生錯誤。
- getDerivedStateFromError() 方法在捕獲到錯誤時會被調用。它接收一個 error 對象作為參數,并返回一個新的狀態(tài)對象,將 hasError 屬性設置為 true,以便在下一次渲染時展示備選的 UI。
- componentDidCatch() 方法在捕獲到錯誤后會被調用。在這個例子中,將錯誤和錯誤信息輸出到控制臺。
在 render() 方法中,根據 hasError 的值來決定渲染原始子組件還是備選的 UI。如果 hasError 為 true,則渲染 <h1>出錯啦!</h1>,否則渲染原始子組件。在組件中使用錯誤邊界時,將需要進行錯誤邊界保護的組件包裹在 <ErrorBoundary> 組件中即可。
在React組件中,我們可以使用 Error Boundary 來包裹任何組件,這樣就不會因為一個小組件的崩潰,而導致整個組件崩潰,致使出現白屏。只有出現錯誤的組件處不能正常渲染,而是渲染備選 UI。也方便我們快速查找是哪個組件出了問題。
實現錯誤邊界的通用設計模式
在實現 React 錯誤邊界時,可以根據實際情況使用以下三種設計模式。
組件級錯誤邊界
這種方法涉及將單個組件包裝在錯誤邊界中。這提供了高度的細粒度,允許單獨處理每個組件中的錯誤。如果一個組件崩潰,錯誤邊界可以捕獲錯誤并阻止其向上傳播到組件樹。這意味著只有崩潰的組件受到影響,其余的應用可以繼續(xù)正常運行。
組件級錯誤邊界在有彼此隔離且不共享狀態(tài)的組件時特別有用。如果其中一個組件渲染失敗,它不會影響其他組件。然而,如果許多組件需要擁有自己的錯誤邊界,這種方法可能導致很多重復代碼。
布局級錯誤邊界
布局級錯誤邊界位于組件樹中更高的位置,通常包裝相關組件組。當有密切相關的組件并共享一個公共狀態(tài)時,這是一個不錯的選擇。
當一個組件發(fā)生錯誤時,布局級別的錯誤邊界可以捕獲它并為整個組件組顯示一個錯誤消息或備用 UI。這可以成為處理影響應用整個部分的錯誤的一種好方法,例如側邊欄或儀表板等。
然而,布局級錯誤邊界比組件級粒度更大。一個組件的錯誤可能會影響整個組件組,即使其他組件可以正常工作。
頂層錯誤邊界
頂層錯誤邊界位于組件樹的最頂部。它是一個通用的解決方案,可以處理應用中發(fā)生的任何錯誤。這種方法確保如果應用中的任何地方發(fā)生錯誤,都可以捕獲并進行優(yōu)雅處理。
這可以防止整個應用在發(fā)生錯誤時崩潰。然而,頂層錯誤邊界是粒度最大的方法。一個錯誤可能會影響整個應用,而不僅僅是錯誤發(fā)生的組件或組件組。
react-error-boundary
上面我們提到,React Error Boundary 有一些限制,無法處理某些特殊情況。下面就介紹一個更強大的 React 錯誤處理方式:**react-error-boundary。**它是一個小型的 React 錯誤處理庫,它提供了增強的錯誤處理功能和更靈活的方法來處理 React 應用中的錯誤,使開發(fā)人員能夠創(chuàng)建更強大且用戶友好的錯誤處理機制。
react-error-boundary 使用了更現代化的方法,采用了 React Hooks 和函數組件,與當前 React 開發(fā)的趨勢更加一致。它使用了一個簡單的組件叫做 ErrorBoundary,可以用它來包裹潛在的容易出錯的代碼。
下面是 react-error-boundary 提供的 props:
- FallbackComponent:用于指定一個自定義組件,在錯誤邊界內發(fā)生錯誤時進行渲染。它提供了靈活性,可以創(chuàng)建一個視覺上吸引人且信息豐富的用戶界面來顯示錯誤,并提供任何必要的操作。
- fallbackRender:類似于 FallbackComponent,該屬性用于定義一個自定義的渲染函數來渲染錯誤回退界面。它提供了對渲染過程的更多控制,并允許進行更高級的錯誤處理邏輯。
- onError:其接受一個回調函數,在錯誤邊界捕獲到錯誤時調用該函數,并傳遞錯誤對象和組件堆棧跟蹤信息。它使我們能夠執(zhí)行額外的操作,例如記錄錯誤或將錯誤報告發(fā)送到外部服務。
- onReset:其接受一個回調函數,在錯誤邊界成功重置后觸發(fā)。它可以用于執(zhí)行清理操作或在錯誤恢復后更新組件的狀態(tài)。
- fallbackProps:允許向 FallbackComponent 或 fallbackRender 函數傳遞額外的 props。它可以用于提供上下文或附加數據給錯誤回退界面。
- retry:布爾值,確定錯誤邊界是否允許重試導致錯誤的操作。當設置為 true 時,resetErrorBoundary 函數可以從錯誤回退界面中調用以重試操作。
ErrorBoundary 組件
react-error-boundary 提供的 ErrorBoundary 組件有一個叫做 fallbackRender的屬性,它接受一個函數或一個 React 元素,在捕獲到錯誤時顯示。此外,它還提供了一個 resetKeys 屬性,可以在特定 props 改變時重置組件的狀態(tài)。
react-error-boundary 的美妙之處在于避免了手動編寫類組件和處理狀態(tài),在后臺完成了所有工作,使開發(fā)人員能夠專注于構建應用。下面來看一個在組件中使用 react-error-boundary 的例子:
import { ErrorBoundary } from 'react-error-boundary'
function MyFallbackComponent({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>出錯啦:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>點擊重試</button>
</div>
)
}
function MyComponent() {
// 可能會拋出 JavaScript 錯誤的一些組件邏輯
}
function App() {
return (
<ErrorBoundary
FallbackComponent={MyFallbackComponent}
onReset={() => {
// 重置應用狀態(tài)
}}
resetKeys={['someKey']}
>
<MyComponent />
</ErrorBoundary>
)
}
在這個例子中,當 ErrorBoundary 捕獲到錯誤時,會渲染 MyFallbackComponent。它顯示錯誤消息并提供一個按鈕來重置錯誤狀態(tài)并嘗試再次渲染該組件。onReset 屬性用于清理在拋出錯誤之前發(fā)生的副作用,而 resetKeys 屬性用于控制何時重置組件狀態(tài)。
ErrorBoundary 的onError 屬性是一個函數,每當捕獲到錯誤時都會被調用??梢允褂迷搶傩詫㈠e誤進行上報:
// 錯誤上報函數
function logErrorToService(error, info) {
// 錯誤上報邏輯...
console.error("Caught an error:", error, info);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={logErrorToService}
>
<MyComponent />
</ErrorBoundary>
);
}
react-error-boundary 最強大的功能之一是就是能夠重置錯誤邊界狀態(tài),即清除錯誤并嘗試重新渲染組件樹。當錯誤可能是短暫的,例如由于臨時斷開連接而發(fā)生的網絡錯誤時,這是非常有用的。
可以使用提供給回退組件的 resetErrorBoundary 函數來重置錯誤邊界。例如,可以在按鈕點擊時調用此函數,允許用戶手動重試失敗的操作。
ErrorBoundary 可以接受一個 onReset 屬性,該屬性是一個函數,在錯誤狀態(tài)重置之前調用。此函數對于在錯誤后重新渲染之前執(zhí)行任何清理或狀態(tài)重置操作非常有用。
resetKeys 屬性是一個值數組,當更改時,會觸發(fā)錯誤邊界的重置。當知道更改某些 props 或狀態(tài)值應該解決錯誤時,這非常有用。以下是如何使用這些屬性的例子:
import { ErrorBoundary } from 'react-error-boundary'
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>出錯啦:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>點擊重試</button>
</div>
)
}
function MyComponent({ someKey }) {
// 可能會拋出 JS 錯誤的一些組件邏輯
}
function App() {
const [someKey, setSomeKey] = React.useState(null)
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setSomeKey(null)} // 在這里重置應用狀態(tài)
resetKeys={[someKey]} // 當 someKey 更改時重置錯誤邊界
>
<MyComponent someKey={someKey} />
</ErrorBoundary>
)
}
在這個例子中,如果 MyComponent 中捕獲到錯誤,則渲染 ErrorFallback 組件,顯示錯誤消息和一個“點擊重試”按鈕。當點擊此按鈕時,它調用 resetErrorBoundary,觸發(fā) onReset 函數并清除錯誤狀態(tài),從而再次渲染 MyComponent。如果 someKey prop 更改,則錯誤邊界也會重置,提供了一種靈活的方式,根據應用程狀態(tài)的更改恢復錯誤。
useErrorHandler Hook
react-error-boundary 還提供了一個 useErrorBoundary Hook,它是一個自定義的 React Hook,允許在函數組件的任何地方拋出錯誤。被拋出的錯誤將被最近的錯誤邊界捕獲,就像從類組件的生命周期方法或渲染函數中拋出錯誤時會被錯誤邊界捕獲一樣。
當處理異步代碼時,useErrorHandler Hook 特別有用,因為拋出的錯誤不會被組件的錯誤邊界捕獲。以下是使用 useErrorHandler 的例子:
import { useErrorHandler } from 'react-error-boundary'
function MyComponent() {
const handleError = useErrorHandler()
async function fetchData() {
try {
// 獲取數據
} catch (error) {
handleError(error)
}
}
return (
...
);
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
);
}
在這個例子中,MyComponent 使用 useErrorHandler 獲取一個可以用來處理錯誤的函數?!?函數是一個異步函數,用于獲取數據并捕獲錯誤。如果發(fā)生錯誤,它會傳遞給 handleError 函數,該函數會拋出錯誤,以便可以被 ErrorBoundary 捕獲。
useErrorHandler 提供了一種強大的方式來處理函數組件中的錯誤。它與 react-error-boundary 的 ErrorBoundary 組件無縫配合,使得在 React 中進行錯誤處理變得更加簡單明了。
withErrorBoundary HOC
react-error-boundary 包還提供了一個 withErrorBoundary HOC 的解決方案。它是一個高階組件,用于包裝將給定的組件,以捕獲該組件中的錯誤。這樣做的好處是,在不改變組件原始實現或向組件樹中添加額外 JSX 的情況下,為組件添加錯誤邊界。
import { withErrorBoundary } from 'react-error-boundary'
function MyComponent() {
// 組件邏輯
}
const MyComponentWithErrorBoundary = withErrorBoundary(MyComponent, {
FallbackComponent: ErrorFallback,
onError: logErrorToService,
onReset: handleReset,
resetKeys: ['someKey']
});
function App() {
return <MyComponentWithErrorBoundary someKey={someKey} />
}
在這個例子中,使用 withErrorBoundary 將 MyComponent 包裝在一個錯誤邊界內。withErrorBoundary 的第二個參數是一個可選對象,可以在其中提供與 ErrorBoundary 組件相同的 props:FallbackComponent、onError、onReset 和 resetKeys。
這種高階組件(HOC)的方法是在不修改組件實現的情況下為組件添加錯誤邊界的一種優(yōu)雅的解決方案,特別適用于無法使用 Hooks 的類組件。它展示了 react-error-boundary 在適應不同的編碼風格和 React 開發(fā)范式方面的靈活性。
小結
看完了 react-error-boundary 的基本使用方式,我們來總結一下使用 react-error-boundary 的好處:
- 簡單易用:該庫提供了簡單直觀的 API,易于理解和使用。它抽象了錯誤處理的復雜性,并向開發(fā)人員呈現了一種簡單明了的方式來管理錯誤。
- 適用于函數組件 :與傳統(tǒng)的 React 錯誤邊界需要使用類組件不同,react-error-boundary 是專為函數組件而設計的。它使用了 Hooks,更符合當前 React 開發(fā)的趨勢。
- 靈活多樣:該庫提供了多種使用錯誤邊界的方式,包括作為組件、高階組件 (HOC) 和自定義 Hook。這種靈活性使開發(fā)人員能夠根據自己的需求和編碼風格選擇最佳的方法。
- 可自定義的回退界面:該庫允許在捕獲到錯誤時顯示可自定義的回退界面。這比應用崩潰或顯示空白屏幕要提供更好的用戶體驗。
- 重置功能:該庫可以重置錯誤狀態(tài),使應用能夠從錯誤中恢復。這個功能對于可以在不需要完全重新加載頁面的情況下解決的臨時性錯誤特別有用。
- 錯誤報告:通過 onError 屬性,錯誤可以被記錄到錯誤報告服務中,提供了有價值的信息用于調試和解決問題。
- 社區(qū)和維護:該庫在 React 社區(qū)中得到廣泛使用,并且積極維護,因此可以期待定期的更新和改進。
總結
無論是使用類組件還是函數組件,react-error-boundary 都能滿足需求。它靈活的API包括組件、高階組件和自定義Hooks,提供了多種將錯誤處理集成到組件中的方式。此外,它支持自定義備用 UI、錯誤重置功能和錯誤報告,即使出現問題,也能確保順暢的用戶體驗。
在 React 應用中使用 react-error-boundary 可以實現更健壯的錯誤處理、更容易的調試。通過使用這個庫,可以花更少的時間管理錯誤,更多的時間專注于為構建出色的產品功能。