React渲染機(jī)制及相關(guān)優(yōu)化方案
一、react渲染步驟
準(zhǔn)備階段(Prepare Phase)
在準(zhǔn)備階段,React 會收集組件的依賴關(guān)系,建立組件樹的數(shù)據(jù)結(jié)構(gòu),確定組件的更新優(yōu)先級,并生成用于渲染的工作單元。計(jì)算階段(Compute Phase)
在計(jì)算階段,React 會根據(jù)組件的更新優(yōu)先級和調(diào)度策略,將工作單元分成多個批次進(jìn)行處理。每個批次都會執(zhí)行一小部分工作單元,以保證用戶界面的響應(yīng)性。渲染階段(Render Phase)
在渲染階段,React 會根據(jù)工作單元的類型和優(yōu)先級,執(zhí)行相應(yīng)的渲染操作。這包括創(chuàng)建新的虛擬 DOM 節(jié)點(diǎn)、更新現(xiàn)有的虛擬 DOM 節(jié)點(diǎn),以及卸載不再需要的組件。提交階段(Commit Phase)
在提交階段,React 會將更新后的虛擬 DOM 節(jié)點(diǎn)映射到實(shí)際的 DOM,更新用戶界面。這個階段還會執(zhí)行一些副作用操作,如執(zhí)行useEffect。
二、concurrent機(jī)制以及產(chǎn)生作用的機(jī)會
注:React 的并發(fā)模式(Concurrency Mode)是一種用于處理大型和復(fù)雜應(yīng)用程序的特性,旨在提高應(yīng)用程序的性能和響應(yīng)能力。解決react中狀態(tài)更新就會觸發(fā)該組件及該組件下所有子組件無腦更新而引發(fā)的性能問題;同時提供部分控制作業(yè)調(diào)度優(yōu)先級的能力給開發(fā)者使用
在傳統(tǒng)的 React 渲染模式中,更新操作是同步進(jìn)行的,即在進(jìn)行更新時,會立即進(jìn)行組件的重新渲染,可能會阻塞主線程,導(dǎo)致頁面響應(yīng)變慢或失去響應(yīng)出現(xiàn)掉幀問題。
而concurrent mode通過引入一種新的調(diào)度算法和優(yōu)先級機(jī)制,將更新操作劃分為多個優(yōu)先級,使得 React 可以更好地管理和分配任務(wù),以實(shí)現(xiàn)更平滑的用戶體驗(yàn)。
concurrent mode主要具備以下幾個特性:異步渲染、優(yōu)先級調(diào)度、遞增式渲染
補(bǔ)充:concurrent mode 主要工作在渲染流程的 Compute Phase 及 Render Phase,因?yàn)樗鼈兪羌兇獾?JS 計(jì)算意味著可以被拆分,而 commit 階段由于帶有 DOM 更新,不可能 DOM 變更到一半中斷,因此必須一次性執(zhí)行完成
1. 優(yōu)先級調(diào)度:
concurrent mode 通過對任務(wù)進(jìn)行優(yōu)先級劃分,React 可以根據(jù)優(yōu)先級動態(tài)地分配和重新分配任務(wù)?;诖薘eact 可以更好地響應(yīng)用戶交互和其他高優(yōu)先級的任務(wù),同時提供了 “useDeferredValue” 、“useTransition” 兩個hooks用于調(diào)度作業(yè)任務(wù)的優(yōu)先級。
2. 遞增式渲染:
1)concurrent mode 下的渲染是逐步進(jìn)行的,React 將大量需要重新渲染的組件的工作基于時間片的理念劃分為多個小片段工作,在瀏覽器的每一幀的空閑時間中去執(zhí)行這些渲染工作,而不是一下子全部直接執(zhí)行,這樣有效的避免了掉幀情況的出現(xiàn)。
2)這里也就說明了為什么React官方說 componentWillMount 可能被調(diào)用多次的原因,正是因?yàn)榈蛢?yōu)先級任務(wù)的 render 階段可能被重復(fù)的中斷和重新執(zhí)行,而 componentWillMount 就包含在 render 階段中。
注意:工作拆分的最小單元應(yīng)該是一個fiber節(jié)點(diǎn),當(dāng)某個fiber節(jié)點(diǎn)本身的計(jì)算就十分巨大時依然會導(dǎo)致卡幀,不過我們可以通過調(diào)整工作的優(yōu)先級使得用戶的體驗(yàn)是平滑的
三、簡單模擬實(shí)現(xiàn) concurrent mode 的遞增式渲染
- 下面使用 requestIdleCallback 函數(shù)模擬時間片,在每一幀的空閑時間進(jìn)行js計(jì)算從而達(dá)到遞增式渲染的效果
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="index.js"></script>
</head>
<body>
<div id="root"></div>
<script>
// 調(diào)用render提供掛載容器 "root"
render(document.getElementById('root'))
</script>
</body>
</html>index.js
// 頁面需要渲染的組件
function Counter() {
return {
type: 'span',
value: 'hello world',
next: {
type: 'p',
value: 'hello LiHua'
}
}
}
const CounterElementDescriptors = {
type: 'Function',
fn: Counter
}
// 記錄當(dāng)前工作
let presentWork = null
// 記錄根元素
let rootElementDescriptor = null
// 記錄掛載容器
let elementsContainer = null
// 處理單元任務(wù)
function performUnitOfWork(deadline) {
// 判斷當(dāng)前是否還有待執(zhí)行任務(wù)
if (presentWork == null) return commitRoot(rootElementDescriptor)
// 當(dāng)前幀超時,調(diào)用 requestIdleCallback 把任務(wù)推到下一幀空閑時間執(zhí)行
if (deadline.didTimeout) return requestIdleCallback(executeWorkLoop)
// 若是組件則處理依賴關(guān)系、若是元素則生成真實(shí)dom
if (presentWork.type === "Function") {
rootElementDescriptor = presentWork
const firstChildren = presentWork.fn()
firstChildren.parent = presentWork
presentWork.children = firstChildren
presentWork = firstChildren
performUnitOfWork(deadline)
} else {
const dom = document.createElement(presentWork.type)
dom.innerHTML = presentWork.value
presentWork.dom = dom
presentWork = presentWork.next
performUnitOfWork(deadline)
}
}
// 控制循環(huán)執(zhí)行工作
function executeWorkLoop(deadline) {
performUnitOfWork(deadline)
}
// 提供render函數(shù),用于獲取掛載容器和開始渲染計(jì)算工作
function render(element) {
elementsContainer = element
presentWork = CounterElementDescriptors
requestIdleCallback(executeWorkLoop)
}
// 模擬commit階段
function commitRoot(rootElement) {
let renderCHildrenElements = rootElement.children
do {
elementsContainer.appendChild(renderCHildrenElements.dom)
renderCHildrenElements = renderCHildrenElements.next
}while(renderCHildrenElements)
}四、與優(yōu)先級調(diào)度有關(guān)的兩個hooks
1. useTransition
官方解釋:useTransition 是一個讓你在不阻塞 UI 的情況下來更新狀態(tài)的 React Hook。
- 通過 useTransition 我們可以將一部分的狀態(tài)更新工作劃分為低優(yōu)先級的異步任務(wù),使它不阻塞主要任務(wù)的執(zhí)行
- 同時我們可以依據(jù) useTransition 返回的標(biāo)志狀態(tài)在渲染期間優(yōu)雅地展示加載狀態(tài),從而提高用戶界面的交互體驗(yàn)和流暢性
- useTransition 主要語法如下:
import { useTransition } from "react";
function TabContainer() {
// isPending 標(biāo)志,告訴你是否存在待處理的低優(yōu)先級工作。
// startTransition 函數(shù) 允許你將該部分的狀態(tài)更新標(biāo)記為低優(yōu)先級。
const [isPending, startTransition] = useTransition();
function handle() {
startTransition(() => {
// 低優(yōu)先級的狀態(tài)更新工作
{......}
});
}
return (
{......}
)
}2. useDeferredValue
官方解釋:useDeferredValue 是一個 React Hook,可以讓你延遲更新 UI 的某些部分。
- 通過 useDeferredValue 我們可以將一部分的UI更新工作劃分為低優(yōu)先級的任務(wù),使它不阻塞主要任務(wù)的執(zhí)行
- useTransition 主要語法如下:
import { useDeferredValue, useState, } from "react";
function TabContainer() {
const [query, setQuery] = useState('');
// 定義的 deferredQuery 獲取的是query的延遲版本
const deferredQuery = useDeferredValue(query);
function handle(data) {
setQuery(data)
}
return (
<>
<List listData={deferredQuery} />
{ ......}
</>
)
}3. useTransition 與 useDeferredValue 的區(qū)別
- useTransition 用于控制過渡狀態(tài),可以在過渡狀態(tài)中執(zhí)行任務(wù),并提供過渡狀態(tài)的布爾值來判斷是否處于過渡狀態(tài)。
- useDeferredValue 用于延遲某個值的更新,以避免在渲染過程中處理昂貴的計(jì)算或數(shù)據(jù)獲取,確保界面的流暢性。
- 雖然它們都與并發(fā)模式相關(guān),但用途和作用略有不同,具體使用哪一個需要看具體場景。
4. 應(yīng)用場景
1)長列表渲染:當(dāng)渲染大量列表項(xiàng)時,可以對列表項(xiàng)的渲染任務(wù)調(diào)節(jié)為低優(yōu)先級異步任務(wù),以保證用戶界面的響應(yīng)性能。
2)大型表單處理:對于包含大量輸入字段的表單,可以使用合理使用對于hooks將表單提交和驗(yàn)證等任務(wù)進(jìn)行優(yōu)化調(diào)節(jié),以避免阻塞用戶界面。
3)圖片懶加載:當(dāng)頁面中包含大量圖片時,可以使用 useTransition 將圖片的加載劃分為多個低優(yōu)先級異步任務(wù),在渲染期間逐步加載圖片,以減少對用戶界面的阻塞。
4)異步數(shù)據(jù)加載:當(dāng)頁面中的數(shù)據(jù)需要從后端異步加載時,可以使用 useTransition 將數(shù)據(jù)的加載劃分為多個異步任務(wù),以保證用戶界面的響應(yīng)性能。
五、一個小例子
- 以下以長列表渲染為例子做演示
基礎(chǔ)代碼,未作優(yōu)化處理:
import React, { useCallback, useState } from 'react'
const index: React.FC = () => {
const [list, setList] = useState<any[]>([])
const handleSearch = useCallback((value: string) => {
const newList = []
for (let i = 0; i < 5000; i++) {
newList.push(value + '-' + i)
}
setList(newList)
}, [])
return (
<>
<input onChange={(e) => handleSearch(e.target.value)} type='text' />
<div>
{list.map(item => <div key={item}>數(shù)據(jù)項(xiàng):{item}</div>)}
</div>
</>
)
}
export default index當(dāng)我們進(jìn)行持續(xù)的輸入時是十分的卡頓的,效果如下:

1. 下面使用 useTransition 進(jìn)行優(yōu)化
- 降低 “setList(newList)” 的優(yōu)先級,使其不阻塞用戶輸入事件的觸發(fā)
代碼修改如下:
import React, { useCallback, useState, useTransition } from 'react'
const index: React.FC = () => {
const [list, setList] = useState<any[]>([])
const [isPending, startTransition] = useTransition()
const handleSearch = useCallback((value: string) => {
startTransition(() => {
const newList = []
for (let i = 0; i < 5000; i++) {
newList.push(value + '-' + i)
}
setList(newList)
})
}, [])
return (
<>
<input onChange={(e) => handleSearch(e.target.value)} type='text' />
<div>
{isPending? '加載中。。。' : list.map(item => <div key={item}>數(shù)據(jù)項(xiàng):{item}</div>)}
</div>
</>
)
}
export default index優(yōu)化后效果如下:

2. 使用 useDeferredValue 進(jìn)行優(yōu)化
- 降低 “列表部分UI” 更新渲染的優(yōu)先級,使其不阻塞用戶輸入事件的觸發(fā)
代碼修改如下:
import React, { memo, useDeferredValue, useState } from 'react'
const Item = ({ text }: any) => {
return (
<div>
數(shù)據(jù)項(xiàng):{text}
</div>
)
}
const List = memo(({ inputValue }: { inputValue: string }) => {
let items = [];
for (let i = 0; i < 5000; i++) {
items.push(<Item key={i} text={inputValue + '-' + i} />);
}
return (
<>
{items}
</>
);
})
const index: React.FC = () => {
const [inputValue, setInputValue] = useState('')
const deferredInputValue = useDeferredValue(inputValue)
return (
<>
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} type='text' />
<List inputValue={deferredInputValue} />
</>
)
}
export default index優(yōu)化后效果如下:

補(bǔ)充:為什么VUE不需要設(shè)計(jì) Concurrent Mode
- 出于vue響應(yīng)式系統(tǒng)的設(shè)計(jì)實(shí)現(xiàn)思路的不同,也就體現(xiàn)了為什么。
1)在vue中,響應(yīng)式系統(tǒng)通過 proxy 實(shí)現(xiàn)對 render函數(shù) 的依賴收集和觸發(fā)更新,基于追蹤組件依賴的響應(yīng)式數(shù)據(jù)的變化,可以更為精準(zhǔn)的實(shí)現(xiàn)組件的更新,大大避免了不必要的渲染和更新操作,規(guī)避了react中狀態(tài)更新就會觸發(fā)組件及該組件下所有子組件無腦更新的問題。
2)同時vue的異步更新策略也有助于提高性能和響應(yīng)能力。Vue會在下一個事件循環(huán)周期中批量更新組件,這樣可以避免頻繁的DOM操作和重復(fù)渲染,提高渲染效率。
3)但vue中暫時沒有 useTransition 和 useDeferredValue 類似的功能操作,無法調(diào)度控制作業(yè)的優(yōu)先級
以上就是React渲染機(jī)制及相關(guān)優(yōu)化方案的詳細(xì)內(nèi)容,更多關(guān)于React渲染機(jī)制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react?express實(shí)現(xiàn)webssh?demo解析
這篇文章主要為大家介紹了詳解react?express實(shí)現(xiàn)webssh?demo解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
解決React報(bào)錯Expected an assignment or funct
這篇文章主要為大家介紹了React報(bào)錯Expected an assignment or function call and instead saw an expression解決方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
react-router JS 控制路由跳轉(zhuǎn)實(shí)例
這篇文章主要介紹了react-router JS 控制路由跳轉(zhuǎn)實(shí)例,react實(shí)現(xiàn)路由可以直接使用react-router。有興趣的可以了解一下2017-06-06
詳解create-react-app 2.0版本如何啟用裝飾器語法
這篇文章主要介紹了詳解create-react-app 2.0版本如何啟用裝飾器語法,cra2.0時代如何啟用裝飾器語法呢? 我們依舊采用的是react-app-rewired, 通過劫持webpack cofig對象, 達(dá)到修改的目的2018-10-10
react純函數(shù)組件setState更新頁面不刷新的解決
在開發(fā)過程中,經(jīng)常遇到組件數(shù)據(jù)無法更新,本文主要介紹了react純函數(shù)組件setState更新頁面不刷新的解決,感興趣的可以了解一下2021-06-06

