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