網(wǎng)站首頁 編程語言 正文
Web 前端開發(fā)者對渲染和重新渲染應(yīng)該不陌生,在 React 中,它們究竟是什么意思?
- 渲染:React 讓組件根據(jù)當(dāng)前的 props 和 state 描述它要展示的內(nèi)容。
- 重新渲染:React 讓組件重新描述它要展示的內(nèi)容。
要將組件顯示到屏幕上,React 的工作主要分為兩個(gè)階段,本文介紹與 React 渲染相關(guān)的知識。
- render 階段(渲染階段):計(jì)算組件的輸出并收集所有需要應(yīng)用到 DOM 上的變更。
- commit 階段(提交階段):將 render 階段計(jì)算出的變更應(yīng)用到 DOM 上。
在 commit 階段 React 會更新 DOM 節(jié)點(diǎn)和組件實(shí)例的 ref,如果是類組件,React會同步運(yùn)行 componentDidMount 或 componentDidUpdate 生命周期方法,如果是函數(shù)組件,React會同步運(yùn)行 useLayoutEffect 勾子,當(dāng)瀏覽器繪制 DOM 之后,再運(yùn)行所有的 useEffect 勾子。
React 重新渲染
初始化渲染之后,下面的這些原因會讓React重新渲染組件:
類組件
- 調(diào)用 this.setState 方法。
- 調(diào)用this.forceUpdate方法。
函數(shù)組件
- 調(diào)用 useState 返回的 setState。
- 調(diào)用 useReducer 返回的 dispatch。
其他
- 組件訂閱的 context value 發(fā)生變更
- 重新調(diào)用 ReactDOM.render(
<AppRoot>
)
假設(shè)組件樹如下
默認(rèn)情況,如果父組件重新渲染,那么 React 會重新渲染它所有的子組件。當(dāng)用戶點(diǎn)擊組件 A 中的按鈕,使 A 組件 count 狀態(tài)值加1,將發(fā)生如下的渲染流程:
- React將組件A添加到重新渲染隊(duì)列中。
- 從組件樹的頂部開始遍歷,快速跳過不需要更新的組件。
- React發(fā)生A組件需要更新,它會渲染A。A返回B和C
- B沒有被標(biāo)記為需要更新,但由于它的父組件A被渲染了,所以React會渲染B
- C沒有被標(biāo)記為需要更新,但由于它的父組件A被渲染了,所以React會渲染C,C返回D
- D沒有標(biāo)記為需要更新,但由于它的父組件C被渲染了,所以D會被渲染。
在默認(rèn)渲染流程中,React 不關(guān)心子組件的 props 是否改變了,它會無條件地渲染子組件。很可能上圖中大多數(shù)組件會返回與上次完全相同的結(jié)果,因此 React 不需要對DOM 做任何更改,但是,React 仍然會要求組件渲染自己并對比前后兩次渲染輸出的結(jié)果,這兩者都需要時(shí)間。
Reconciliation
Reconciliation 被稱為 diff 算法,它用來比較兩顆 React 元素樹之間的差異,為了讓組件重新渲染變得高效,React 盡可能地復(fù)用現(xiàn)有的組件和 DOM。為了降低時(shí)間復(fù)雜度,Diff 算法基于如下兩個(gè)假設(shè):
- 兩個(gè)不同類型的元素對應(yīng)的元素樹完全不同。
- 在同一個(gè)列表中,如果兩個(gè)元素key屬性的值相同,那么它們被識別為同一個(gè)元素。
元素類型對 Diff 的影響
React 使用元素的 type 字段比較元素類型是否相同,如果兩顆樹在相同位置要渲染的元素類型相同,那么 React 就重用這些元素,并在適當(dāng)?shù)臅r(shí)候更新,不需要重新創(chuàng)建元素,這意味著,只要一直要求 React 將某組件渲染在相同的位置,那么 React 始終不會卸載該組件。如果相同位置的元素類型不同,例如從 div 到 span 或者從ComponentA 到 ComponentB,React會認(rèn)為整個(gè)樹發(fā)生了變化,為了加快比較過程,React 會銷毀整個(gè)現(xiàn)有的組件樹,包括所有的 DOM 節(jié)點(diǎn),然后重新創(chuàng)建元素。
瀏覽器內(nèi)置元素的 type 字段是一個(gè)字符串,自定義組件元素的 type 字段是一個(gè)類或者函數(shù),由于元素類型對 Diff的影響,所以在渲染期間不要?jiǎng)?chuàng)建組件,只要?jiǎng)?chuàng)建一個(gè)新的組件,那么它的 type 字段就是不同的引用,這將導(dǎo)致 React 不斷地銷毀并重新創(chuàng)建子組件樹。不要有如下的代碼:
function ParentCom() { // 每一次渲染 ParentCom 時(shí),都會創(chuàng)建新的ChildCom組件 function ChildCom() {/**do something*/} return <ChildCom /> }
上述代碼不推薦,正確的做法是將 ChildCom 放在ParentCom 的外面。
key 對 Diff 的影響
React 識別元素的另一種方式是通過 key 屬性,key 作為組件的唯一標(biāo)識符不會當(dāng)作prop傳遞到組件中,可以給任何組件添加一個(gè) key 屬性來標(biāo)注它,更改 key 的值會導(dǎo)致舊的組件實(shí)例和 DOM 被銷毀。
列表是使用 key 屬性的主要場景,在 React 官方文檔中提到,不要將數(shù)組的下標(biāo)作為 key 值,而是用數(shù)據(jù)唯一 ID 作為 key 值。在這里分別介紹這兩種方式的區(qū)別。
假如 Todo List 中有 10 項(xiàng),先用數(shù)組下標(biāo)作為 key 的值,這 10 項(xiàng) Todo 的 key 值為 0...9,現(xiàn)在刪除數(shù)組的第 6 項(xiàng)和第 7 項(xiàng),并在數(shù)組末尾添加 3 個(gè)新的數(shù)據(jù)項(xiàng),我們最終將得到 key 值為0..10的 Todo,看起來只是在末尾新增 1 項(xiàng),將原來的列表從10項(xiàng)變成了11項(xiàng),React 很樂意復(fù)用已有的 DOM 節(jié)點(diǎn)和組件實(shí)例,這意味著原來 #6 對應(yīng)的組件實(shí)例沒有被銷毀,現(xiàn)在它接收新的 props 用于呈現(xiàn)原來的 #8。在這個(gè)例子中 React 會創(chuàng)建 1 個(gè)Todo,更新 4 個(gè)Todo。
如果使用數(shù)據(jù)的 ID 作為 key 值,React 能發(fā)現(xiàn)第 6 項(xiàng)和第 7 項(xiàng)被刪除了,它也能發(fā)現(xiàn)數(shù)組新增了 3 項(xiàng),所以 React 會銷毀 #6 和 #7 項(xiàng)對應(yīng)的組件實(shí)例及其關(guān)聯(lián)的 DOM,還會創(chuàng)建 3 個(gè)組件實(shí)例及其關(guān)聯(lián)的 DOM。
提高渲染性能
要將組件顯示在界面上,組件必須經(jīng)歷渲染流程,但渲染工作有時(shí)候會被認(rèn)為是浪費(fèi)時(shí)間,如果渲染的輸出結(jié)果沒有改變,它對應(yīng)的DOM節(jié)點(diǎn)也不需要更新,此時(shí)與該組件相關(guān)的渲染工作真的是在浪費(fèi)時(shí)間。React組件的輸出結(jié)果始終基于當(dāng)前 props 和 state 的值,因此,如果我們知道組件的 props 和 state 沒有改變,那么我們可以無后顧之憂地讓組件跳過重新渲染。
跳過重新渲染
React 提供了 3 個(gè)主要的API讓我們跳過重新渲染:
- React.Component 的 shouldComponentUpdate:這是類組件可選的生命周期函數(shù),它在組件 render 階段早期被調(diào)用,如果返回false,React 將跳過重新渲染該組件,使用它最常見的場景是檢查組件的 props 和 state 是否自上次以來發(fā)生了變更,如果沒有改變則返回false。
- React.PureComponent:它在 React.Component 的基礎(chǔ)上添加默認(rèn)的 shouldComponentUpdate 去比較組件的 props 和 state 自上次渲染以來是否有變更。
- React.memo():它是一個(gè)高階組件,接收自定義組件作為參數(shù),返回一個(gè)被包裹的組件,被包裹的組件的默認(rèn)行為是檢查 props 是否有更改,如果沒有,則跳過重新渲染。
上述方法都通過‘淺比較’來確定值是否有變更,如果通過 mutable 的方式修改狀態(tài),這些 API 會認(rèn)為狀態(tài)沒有變。
- 如果組件在其渲染過程中返回的元素的引用與上一次渲染時(shí)的引用完全相同,那么 React 不會重新渲染引用相同的組件。示例如下:
function ShowChildren(props: {children: React.ReactNode}) { const [count, setCount] = useState<number>(0) return ( <div> {count} <button onClick={() => setCount(c => c + 1)}>click</button> {/* 寫法一 */} {props.children} {/* 寫法二 */} {/* <Children/> */} </div> ) }
上述 ShowChildren 的 props.children 對應(yīng) Children 組件,因此寫法一和寫法二在瀏覽器中呈現(xiàn)一樣。點(diǎn)擊按鈕不會讓寫法一的 Children 組件重新渲染,但是會使寫法二的 Children 組件重新渲染。
上述4種方式跳過重新渲染意味著 React 會跳過整個(gè)子樹的重新渲染。
Props 對渲染優(yōu)化的影響
默認(rèn)情況,只要組件重新渲染,React 會重新渲染所有被它嵌套的后代組件,即便組件的 props 沒有變更。如果試圖通過 React.memo 和 React.PureComponent 優(yōu)化組件的渲染性能,那么要注意每個(gè) prop 的引用是否有變更。下面的示例試圖使用 React.memo 讓組件不重新渲染,但事與愿違,組件會重新渲染,代碼如下:
const MemoizedChildren = React.memo(Children) function Parent() { const onClick = () => { /** todo*/} return <MemoizedChildren onClick={onClick}/> }
上述代碼中,Parent 組件重新渲染會創(chuàng)建新的 onClick 函數(shù),所以對 MemoizedChildren 而言,props.onClic k的引用有變化,最終被 React.memo 包裹的Children 會重新渲染,如果讓組件跳過重新渲染對你真的很重要,那么在上述代碼中將 React.memo 與 useCallback 配合使用才能達(dá)到目的。
總結(jié)
渲染與更新 DOM 是不同的事情,組件經(jīng)歷了渲染,DOM 不一定會更新,如果渲染組件返回的結(jié)果與上次的相同,那么它的 DOM 節(jié)點(diǎn)不需要有任何更新。與 React 渲染密切相關(guān)的還有另一個(gè)概念,即Immutability,在React 狀態(tài)的不變性一文已介紹過它。
原文鏈接:https://juejin.cn/post/7168257213738254344
相關(guān)推薦
- 2022-08-29 .NET?Core項(xiàng)目使用swagger開發(fā)組件_實(shí)用技巧
- 2022-05-26 Python編程中內(nèi)置的NotImplemented類型的用法_python
- 2023-01-19 利用Python構(gòu)建Flutter應(yīng)用的教程詳解_python
- 2022-04-12 Redis?Server啟動過程的詳細(xì)步驟_Redis
- 2022-07-17 baselines示例程序train_cartpole.py的ImportError_python
- 2022-09-27 React?Native?中限制導(dǎo)入某些組件和模塊的方法_React
- 2022-10-04 C++兩種素?cái)?shù)判定方法_C 語言
- 2022-11-04 go-cqhttp環(huán)境配置及安裝過程_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支