React渲染的優(yōu)化方案
一、引子
react的渲染機(jī)制是非常獨(dú)特的,有別于 Vue 框架的渲染次數(shù)的優(yōu)化計(jì)算。React 很久以來就有PureComponent、shouldUpdate。Function component 又有了memo、useMemo、useCallback 這樣的函數(shù)工具,讓它成為有一定深度的前端框架。
怎么使用 useMemo 和 useCallback 是我們值得思考的點(diǎn)。
二、代碼范式
首先,假設(shè)大家對 React 都有一個(gè)基礎(chǔ)的入門水平,所以本文不再贅述“useMemo 和 useCallback ”基本用法。
2.1 Memo 緩存組件
引起重渲染的最常見的情況是,組件的 props。memo包裹著函數(shù)組件,針對props 參數(shù)的淺對比。
子組件的渲染有些情況,子組件控制不住,它受到父組件的參數(shù)影響。
function _Boxes({ boxes }: { boxes: { flex: number; background: string; }[] }) { return ( <div className="boxes-wrapper"> {boxes.map((boxStyles, index) => ( <div className="box" style={boxStyles} key={index} /> ))} </div> ); } const Boxes = memo(_Boxes);
2.2 父組件層面,對象使用 useMemo
作為 Box 的父組件,boxes 的傳參是一個(gè)對象,每次改變,它會生成一個(gè)全新的對象。這里要對 boxes 對象使用緩存(useMemo)。這樣,age 的改動(dòng)將不會影響到 Box 的重渲染。
function App() { const [age, setAge] = React.useState(0); const [boxWidth, setBoxWidth] = React.useState(1); const id = React.useId(); // Age 屬性的變更不會影響 boxes 屬性變化 const boxes = useMemo(() => { return [ { flex: boxWidth, background: 'hsl(345deg 100% 50%)' }, { flex: 3, background: 'hsl(260deg 100% 40%)' }, { flex: 1, background: 'hsl(50deg 100% 60%)' }, ]; }, [boxWidth]); return ( <> <Boxes boxes={boxes} /> <section> <button onClick={() => { setAge(age + 1) }}> Increment age </button> <p>Hello! You are {age}.</p> </section> <section> <label htmlFor={`${id}-box-width`}> First box width: </label> <input id={`${id}-box-width`} type="range" min={1} max={5} step={0.01} value={boxWidth} onChange={(event) => { setBoxWidth(Number(event.target.value)); }} /> </section> </> ); }
2.3 context provider 最好用 useMemo
同理,參考《How To useContext With useReducer》,context provider 的 value 參數(shù)是一個(gè)很容易被遺忘的點(diǎn),provider 可能會傳入一個(gè)對象,利用useMemo 或者 useCallback 來保護(hù) App 組件不會做出過多重渲染。
const Main = () => { const [state, dispatch] = useReducer(reducer, { age: 42 }); // 利用useMemo 或者 useCallback 來保護(hù) Context 不會做出過多重渲染 const contextValue = useMemo(() => { return { state, dispatch }; }, [state, dispatch]); return ( <MyContext.Provider value={contextValue}> <App /> </MyContext.Provider> ) }
2.4 其他
還有一種特殊情況是:不規(guī)范、分批的 Context 調(diào)用導(dǎo)致了頁面的重新渲染。針對一些老舊項(xiàng)目,以前的業(yè)務(wù)邏輯導(dǎo)致 Context 的調(diào)用混亂,已經(jīng)不是前面幾種方法能夠解決的。
解決方法:改動(dòng)代碼,把多次 Context 調(diào)用整合為一次。
三、補(bǔ)救防范
3.1 斷點(diǎn)查看調(diào)用堆棧
利用 Chrome 原生調(diào)試工具打斷點(diǎn),看每一行代碼的堆棧信息。這種方式最為原始但是它往往“行之有效”。
3.2 devtool 查看渲染次數(shù)和渲染堆棧
React Devtool 的 Profiler 能協(xié)助我們排查 React 渲染次數(shù)和渲染堆棧。
- 點(diǎn)擊 Profiler 的記錄圓圈
- 刷新頁面或者做其他操作
- 停止記錄
- 參考快照記錄
同時(shí),我們還能夠通過“highlight updates when components render”來可視化整個(gè)渲染過程。
3.3 渲染打印工具
ahooks的useWhyDidYouUpdate
該函數(shù)能夠幫助開發(fā)者排查是哪個(gè)屬性改變導(dǎo)致了組件的 rerender,但是更多集中在 props、state,開發(fā)者需要主動(dòng)去縮小范圍,它起到輔助打印的工作,如果是 Context 或者更外側(cè)的數(shù)據(jù)變動(dòng),效果不見得達(dá)到效果。
Welldone Software 的 why-did-you-render
該工具能全局打印 rerender 日志,但是在復(fù)雜項(xiàng)目當(dāng)中,它的打印較為混亂,不一定能夠很好的發(fā)現(xiàn)問題。
四、總結(jié)
React 的渲染優(yōu)化有非常多篇博客已經(jīng)聊過,但是還是“No Silver Bullet”,沒有最佳方案。特別在一些數(shù)據(jù)流非常復(fù)雜的前端工程項(xiàng)目當(dāng)中。
React 的前端項(xiàng)目能夠劃分為:聰明組件和懶惰組件。聰明組件負(fù)責(zé)的內(nèi)容是頁面邏輯的數(shù)據(jù)流向,懶惰組件負(fù)責(zé)的是樣式的渲染,數(shù)據(jù)流越是清晰,代碼可維護(hù)性越強(qiáng)。
以下是我個(gè)人的代碼編寫建議:
- 不要把所有數(shù)據(jù)都往 Context 里面放,只有極其核心,多個(gè)頁面都復(fù)用情況。
- 簡單的頁面,盡量以聰明組件和懶惰組件的方式來處理。
- 如果已經(jīng)出現(xiàn)數(shù)據(jù)流混亂的情況下,合理使用 memo,或者狀態(tài)管理工具(例如zustand、jotai等)讓代碼數(shù)據(jù)流盡量走向清晰,初始化主路徑盡量批量渲染好。
雖然有很多分析工具,但是它們更多是輔助,最重要還是要通過人工分析。重點(diǎn)要分析出渲染次數(shù)過多的代碼,做出針對性地處理。
以上就是React渲染的優(yōu)化方案的詳細(xì)內(nèi)容,更多關(guān)于React渲染優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React中setState/useState的使用方法詳細(xì)介紹
這篇文章主要介紹了React中setState/useState的使用方法,useState 和 setState 在React開發(fā)過程中 使用很頻繁,但很多人都停留在簡單的使用階段,并沒有正在了解它們的執(zhí)行機(jī)制2023-04-04Native?Memory?Tracking追蹤區(qū)域示例分析
這篇文章主要為大家介紹了Native?Memory?Tracking追蹤區(qū)域示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11antd?3.x?Table組件如何快速實(shí)現(xiàn)虛擬列表詳析
這篇文章主要給大家介紹了關(guān)于antd?3.x?Table組件如何快速實(shí)現(xiàn)虛擬列表的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用antd具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-11-11在?React?Native?中給第三方庫打補(bǔ)丁的過程解析
這篇文章主要介紹了在?React?Native?中給第三方庫打補(bǔ)丁的過程解析,有時(shí)使用了某個(gè)React Native 第三方庫,可是它有些問題,我們不得不修改它的源碼,本文介紹如何修改源碼又不會意外丟失修改結(jié)果的方法,需要的朋友可以參考下2022-08-08antd之RangePicker設(shè)置默認(rèn)值方式
這篇文章主要介紹了antd之RangePicker設(shè)置默認(rèn)值方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12React通過hook實(shí)現(xiàn)封裝表格常用功能
這篇文章主要為大家詳細(xì)介紹了React通過hook封裝表格常用功能的使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-12-12Redux thunk中間件及執(zhí)行原理詳細(xì)分析
redux的核心概念其實(shí)很簡單:將需要修改的state都存入到store里,發(fā)起一個(gè)action用來描述發(fā)生了什么,用reducers描述action如何改變state tree,這篇文章主要介紹了Redux thunk中間件及執(zhí)行原理分析2022-09-09react-router v4如何使用history控制路由跳轉(zhuǎn)詳解
這篇文章主要給大家介紹了關(guān)于react-router v4如何使用history控制路由跳轉(zhuǎn)的相關(guān)資料,文中通過示例代碼介紹的的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01解決antd的Table組件使用rowSelection屬性實(shí)現(xiàn)多選時(shí)遇到的bug
這篇文章主要介紹了解決antd的Table組件使用rowSelection屬性實(shí)現(xiàn)多選時(shí)遇到的bug問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08