React捕獲并處理異常的方式
一 前言
今天來聊一聊在 React 應(yīng)用中,如何發(fā)現(xiàn)異常并處理異常的。
JSX 是優(yōu)勢也是劣勢?
在 React 中,出現(xiàn)一次渲染異常的后果是很嚴重的。比如如下的場景:
function Comp({ data }){ return <div>{ data.value }</div> } /* 頁面 */ export default function App(){ return <div> <div>hello</div> <Comp data={{value:'hello world'}} /> <Comp data={{value:'前端跨端開發(fā)指南'}} /> <Comp data={null} /> </div> }
如上,在第三個 Comp 組件渲染的時候,因為 data 傳入的值是 null ,而在渲染階段讀取了 data 下面的屬性,這個時候就會報空指針的錯誤:Cannot read properties of null
,結(jié)果就是整個頁面都白屏。
這樣后果是嚴重的,所以 React 中要特別注意渲染數(shù)據(jù)的規(guī)范與嚴謹。
這個問題本質(zhì)上和 React 采用 JSX 語法而并非渲染模版有一定的關(guān)系。JSX 給 React 帶來很便利的開發(fā)體驗,開發(fā)者可以借助 JSX 靈活使用組合模式,render props 模式,Hoc 等各種設(shè)計模式,JSX 給開發(fā)者帶來了很大的發(fā)揮空間,但是凡事都有兩面性。JSX 的靈活性也帶來一定的潛在風險。
React jsx 在編譯階段,會被 babel 變成 React.Element 的形式,它的執(zhí)行是在 React 整個渲染的 render 階段執(zhí)行的,如果 React.Element 出現(xiàn)了空指針等異常,那么就會中斷 render 階段的執(zhí)行,當然也不會執(zhí)行渲染真實 DOM 的 commit 階段。所以如果是初次渲染,任何渲染動作也就不會執(zhí)行,最終呈現(xiàn)給我們的視圖就是白屏。
那么如何處理這個問題呢?
二 渲染異常處理
componentDidCatch
還好 React 中提供了 componentDidCatch 或者 getDerivedStateFromError 生命周期,去挽救由于渲染階段出現(xiàn)問題造成 UI 界面無法顯示的情況。 我們以 componentDidCatch 為例子,看一下它是如何處理的異常。
componentDidCatch 是 React 類組件的生命周期,它接受兩個參數(shù):
1 error —— 拋出的錯誤。 2 info —— 帶有 componentStack key 的對象,其中包含有關(guān)組件引發(fā)錯誤的棧信息。 先來打印一下,生命周期 componentDidCatch 參數(shù)長什么樣子。
那么 componentDidCatch 中可以再次觸發(fā) setState,來降級 UI 渲染,componentDidCatch 會在 commit 階段被調(diào)用,因此允許執(zhí)行副作用。我們給上面的例子用類組件和 componentDidCatch 改造,如下:
function Comp({ data }){ return <div>{ data.value }</div> } class CompSafe extends React.Component{ state = { isError:false } componentDidCatch(){ this.setState({ isError:true }) } render(){ const { isError } = this.state return isError ? null : <Comp {...this.props} /> } } export default function App(){ return <div> <div>hello</div> <CompSafe data={{value:'hello world'}} /> <CompSafe data={{value:'前端跨端開發(fā)指南'}} /> <CompSafe data={null} /> </div> }
如上,我們將 Comp 組件包裝一層,通過 CompSafe 包裹,然后 CompSafe 內(nèi)容通過 componentDidCatch 來捕獲異常,這樣就可以將渲染異常產(chǎn)生的影響,由頁面維護,降低到了組件維度。其他部分的視圖也能夠正常渲染了。
但是這樣同樣暴露出一個問題。就是我們把所有的組件,都像 Comp 一樣,在配套一個渲染異常的組件 CompSafe, 那樣是不切實際的,所以我們需要一個通用能力,這樣就需要一個渲染異常的高級組件來解決。
hoc 高階組件模式也是 React 比較常用的一種包裝強化模式之一,高階函數(shù)是接收一個函數(shù),返回一個函數(shù),而所謂高階組件,就是接收一個組件,返回一個組件,返回的組件是根據(jù)需要對原始組件的強化。
HOC 助力渲染異常組件
我們接下來編寫一個通用高階組件,解決渲染異常。
function SafeCompHoc(Comp) { return class CompSafe extends React.Component{ state = { isError:false } componentDidCatch(){ this.setState({ isError:true }) } render(){ const { isError } = this.state return isError ? <div>渲染異常</div> : <Comp {...this.props} /> } } } const CompSafe = SafeCompHoc(Comp) export default function App(){ return <div> <div>hello</div> <CompSafe data={{value:'hello world'}} /> <CompSafe data={{value:'前端跨端開發(fā)指南'}} /> <CompSafe data={null} /> </div> }
如上,經(jīng)過 SafeCompHoc 包裝之后的,可以批量處理渲染異常的組件,可能出現(xiàn)渲染異常的核心組件,就可以用 SafeCompHoc 統(tǒng)一處理了。
三 渲染異常監(jiān)控
渲染監(jiān)控:
如上通過 HOC 的方式做到了渲染降級,但是如果只做到監(jiān)控級別,那是遠遠不夠的,我們要做的就是,發(fā)現(xiàn)問題,去根本解決問題,這種渲染問題大概率可能是渲染數(shù)據(jù)結(jié)構(gòu)出現(xiàn)了問題,而數(shù)據(jù)結(jié)構(gòu)大概率又是后端返回的,所以這個異常本質(zhì)上很可能是服務(wù)端出了問題。
這個時候,發(fā)現(xiàn)問題也是非常重要的,那么就需要一個渲染的監(jiān)控方法。 接下來我們將用context 上下文 + 插槽的方案來實現(xiàn)一個渲染模版監(jiān)控方案。
渲染插槽+context上下文
技術(shù)方案: 核心技術(shù)實現(xiàn):context + 插樁組件
- 首先,我們用 context 保存一個記錄模版狀態(tài)的方法集合。在頁面初始化之后, 接下來會請求數(shù)據(jù),在請求數(shù)據(jù)之后,頁面會循環(huán)渲染子組件列表,在渲染之前,記錄每一個 API 返回的模版,每一個模版需要有一個唯一標識。
- 每一個渲染模版里面有一個插樁組件,插樁組件在每一個模版下部,確保組件正常渲染,插樁組件一定會渲染。插樁組件的生命周期 componentDidMount 或者 useLayoutEffect 里面,觸發(fā)事件給最上層組件,并上報該模版的唯一標識。
- 根組件在完成首次渲染之后,通過短暫的延時后,對比渲染列表里面的每一個模版的標識,是否均備插樁組件上報,如果有個別組件的標識沒有上報,則認為是該組件渲染異常。如果有子組件發(fā)生渲染異常,上報該子組件的渲染數(shù)據(jù)。方便查詢問題。
原理圖:
介紹完原理來看一下代碼的實現(xiàn):
渲染插樁組件:
import React from 'react' /* 上下文保存渲染異常狀態(tài) */ export const RenderErrorContext = React.createContext() /* 渲染插樁組件 */ export default function RenderErrorComponent({renderKey}){ const { setRenderKey } = React.useContext(RenderErrorContext) React.useLayoutEffect(()=>{ /* 渲染正常,上報渲染 key */ setRenderKey && setRenderKey(renderKey) },[]) return <React.Fragment /> }
如上編寫的渲染插樁組件 RenderErrorComponent
和渲染狀態(tài)上下文 RenderErrorContext
,如果渲染插樁組件正常渲染,那么說明當前組件沒有出現(xiàn)渲染異常,接下來需要在 useLayoutEffect
鉤子函數(shù)里面,上傳渲染成功狀態(tài)。
接下來看一下使用渲染上下文的頁面組件。
import React, { useEffect } from 'react' import { RenderErrorContext } from './renderError' import Comp from './component/comp1' /* 模擬的渲染數(shù)據(jù) */ const renderList = [ { id:1, data: { value:'我不是外星人' }, }, { id:2, data: { value:'大前端跨端開發(fā)指南' }, }, { /* 異常數(shù)據(jù) */ id:3, data: null } ] function App() { const [list,setList] = React.useState([]) const renderState = React.useRef({ errorList:[], setRenderKey(id){ //如果渲染成功了,那么將當前 key 移除 const index = renderState.current.errorList.indexOf(id) renderState.current.errorList.splice(index,1) }, getRenderKey(key){ //這里表示渲染了哪些組件 renderState.current.errorList.push(key) } }) useEffect(()=>{ /* 記錄每一個待渲染的模版 */ renderList.forEach(item => renderState.current.getRenderKey(item.id)) setList(renderList) /* 驗證模版是否正常渲染,如果 errorList 不為空,那么有渲染異常的組件,里面的 item 就是渲染異常的 id */ setTimeout(()=>{ console.log('errorList',renderState.current.errorList) }) },[]) return ( <RenderErrorContext.Provider value={renderState.current}> { list.map(item=><Comp data={item.data} id={item.id} key={item.id} />) } </RenderErrorContext.Provider> ); } export default App;
如上就是頁面組件的使用,這里重點介紹一下每一個環(huán)節(jié):
- 首先,用 ref 保存渲染狀態(tài) renderState,是一個對象,在對象里面一定要有 setRenderKey 方法,提供給插槽組件使用。最終將渲染狀態(tài)傳遞給 RenderErrorContext 的 Provider 中,接下來每一個需要監(jiān)控的下游組件都可以回傳渲染狀態(tài)了。
- 在 useEffect 模擬請求數(shù)據(jù),然后根據(jù)數(shù)據(jù),記錄下來待渲染的 id,通過 getRenderKey 將 id 放入到數(shù)組中。
- 接下來當插樁組件正常渲染,那么會回傳狀態(tài),證明渲染成功了,那么將此渲染 id 從數(shù)組中移除。
- 接下來用 setTimeout 驗證模版是否正常渲染,如果 errorList 不為空,那么有渲染異常的組件,里面的 item 就是渲染異常的 id 。
- 在渲染列表中,我們模擬一條異常數(shù)據(jù),就是第三條,data 為 null。
接下來看一下渲染插樁組件的使用:
import React from 'react' import RenderErrorComponent from '../renderError' function Comp({ data, id }){ return <div> <div>{ data.value } </div> <RenderErrorComponent renderKey={id} /> </div> } function ErrorHandle (Component){ return class Wrap extends React.Component{ state = { isError:false } componentDidCatch(){ this.setState({isError : true }) } render(){ const { isError } = this.state return isError ? null : <Component {...this.props} /> } } } export default ErrorHandle(Comp)
如上當渲染 Comp 組件的時候,如果 data 為 null, 那么肯定會報出渲染異常,這個時候頁面都不會正常顯示,為了能夠讓頁面正常展示,我們用一個錯誤處理組件 ErrorHandle 來防止白屏情況發(fā)生。
看一下效果:
如上頁面能夠正常渲染,從渲染異常列表里,能夠查詢到渲染異常的組件 id=3,預(yù)期達成。
四 總結(jié)
以上就是React捕獲并處理異常的方式的詳細內(nèi)容,更多關(guān)于React捕獲并處理異常的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在react項目中webpack使用mock數(shù)據(jù)的操作方法
這篇文章主要介紹了在react項目中webpack使用mock數(shù)據(jù)的操作方法,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-06-06使用React和Redux Toolkit實現(xiàn)用戶登錄功能
在React中,用戶登錄功能是一個常見的需求,為了實現(xiàn)該功能,需要對用戶輸入的用戶名和密碼進行驗證,并將驗證結(jié)果保存到應(yīng)用程序狀態(tài)中,在React中,可以使用Redux Toolkit來管理應(yīng)用程序狀態(tài),從而實現(xiàn)用戶登錄功能,需要詳細了解可以參考下文2023-05-05教你react中如何理解usestate、useEffect副作用、useRef標識和useContext
這篇文章主要介紹了react中如何理解usestate、useEffect副作用、useRef標識和useContext,其實與vue中的ref和reactive一樣,通過useState獲取到的數(shù)據(jù)可以實現(xiàn)組件視圖實時交互,而普通定義的數(shù)據(jù)僅僅在業(yè)務(wù)中使用,需要的朋友可以參考下2022-11-11react?hooks?UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案
這篇文章主要為大家介紹了react?hooks?UI與業(yè)務(wù)邏輯分離必要性技術(shù)方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11React中g(shù)etDefaultProps的使用小結(jié)
React中的getDefaultProps功能允許開發(fā)者為類組件定義默認屬性,提高組件的靈活性和容錯性,本文介紹了getDefaultProps的作用、語法以及最佳實踐,并探討了其他替代方案,如函數(shù)組件中的默認參數(shù)、高階組件和ContextAPI等,理解這些概念有助于提升代碼的可維護性和用戶體驗2024-09-09