React渲染機制及相關優化方案
準備階段(Prepare Phase)在準備階段,React 會收集組件的依賴關系,建立組件樹的數據結構,確定組件的更新優先級,并生成用于渲染的工作單元。
計算階段(Compute Phase)在計算階段,React 會根據組件的更新優先級和調度策略,將工作單元分成多個批次進行處理。每個批次都會執行一小部分工作單元,以保證用戶界面的響應性。
渲染階段(Render Phase)在渲染階段,React 會根據工作單元的類型和優先級,執行相應的渲染操作。這包括創建新的虛擬 DOM 節點、更新現有的虛擬 DOM 節點,以及卸載不再需要的組件。
提交階段(Commit Phase)在提交階段,React 會將更新后的虛擬 DOM 節點映射到實際的 DOM,更新用戶界面。這個階段還會執行一些副作用操作,如執行useEffect。
二、concurrent機制以及產生作用的機會注:React 的并發模式(Concurrency Mode)是一種用于處理大型和復雜應用程序的特性,旨在提高應用程序的性能和響應能力。解決react中狀態更新就會觸發該組件及該組件下所有子組件無腦更新而引發的性能問題;同時提供部分控制作業調度優先級的能力給開發者使用
在傳統的 React 渲染模式中,更新操作是同步進行的,即在進行更新時,會立即進行組件的重新渲染,可能會阻塞主線程,導致頁面響應變慢或失去響應出現掉幀問題。
而concurrent mode通過引入一種新的調度算法和優先級機制,將更新操作劃分為多個優先級,使得 React 可以更好地管理和分配任務,以實現更平滑的用戶體驗。
concurrent mode主要具備以下幾個特性:異步渲染、優先級調度、遞增式渲染
補充:concurrent mode 主要工作在渲染流程的 Compute Phase 及 Render Phase,因為它們是純粹的 JS 計算意味著可以被拆分,而 commit 階段由于帶有 DOM 更新,不可能 DOM 變更到一半中斷,因此必須一次性執行完成
1. 優先級調度:concurrent mode 通過對任務進行優先級劃分,React 可以根據優先級動態地分配和重新分配任務。基于此React 可以更好地響應用戶交互和其他高優先級的任務,同時提供了 “useDeferredValue” 、“useTransition” 兩個hooks用于調度作業任務的優先級。
2. 遞增式渲染:1)concurrent mode 下的渲染是逐步進行的,React 將大量需要重新渲染的組件的工作基于時間片的理念劃分為多個小片段工作,在瀏覽器的每一幀的空閑時間中去執行這些渲染工作,而不是一下子全部直接執行,這樣有效的避免了掉幀情況的出現。
2)這里也就說明了為什么React官方說 componentWillMount 可能被調用多次的原因,正是因為低優先級任務的 render 階段可能被重復的中斷和重新執行,而 componentWillMount 就包含在 render 階段中。
注意:工作拆分的最小單元應該是一個fiber節點,當某個fiber節點本身的計算就十分巨大時依然會導致卡幀,不過我們可以通過調整工作的優先級使得用戶的體驗是平滑的
三、簡單模擬實現 concurrent mode 的遞增式渲染下面使用 requestIdleCallback 函數模擬時間片,在每一幀的空閑時間進行js計算從而達到遞增式渲染的效果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='https://www.jb51.net/javascript/index.js'></script></head><body> <div id='root'></div> <script>// 調用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}// 記錄當前工作let presentWork = null// 記錄根元素let rootElementDescriptor = null // 記錄掛載容器 let elementsContainer = null // 處理單元任務function performUnitOfWork(deadline) { // 判斷當前是否還有待執行任務 if (presentWork == null) return commitRoot(rootElementDescriptor) // 當前幀超時,調用 requestIdleCallback 把任務推到下一幀空閑時間執行 if (deadline.didTimeout) return requestIdleCallback(executeWorkLoop) // 若是組件則處理依賴關系、若是元素則生成真實dom if (presentWork.type === 'Function') {rootElementDescriptor = presentWorkconst firstChildren = presentWork.fn()firstChildren.parent = presentWorkpresentWork.children = firstChildrenpresentWork = firstChildrenperformUnitOfWork(deadline) } else {const dom = document.createElement(presentWork.type)dom.innerHTML = presentWork.valuepresentWork.dom = dompresentWork = presentWork.nextperformUnitOfWork(deadline) }}// 控制循環執行工作function executeWorkLoop(deadline) { performUnitOfWork(deadline)}// 提供render函數,用于獲取掛載容器和開始渲染計算工作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)}四、與優先級調度有關的兩個hooks1. useTransition官方解釋:useTransition 是一個讓你在不阻塞 UI 的情況下來更新狀態的 React Hook。
通過 useTransition 我們可以將一部分的狀態更新工作劃分為低優先級的異步任務,使它不阻塞主要任務的執行同時我們可以依據 useTransition 返回的標志狀態在渲染期間優雅地展示加載狀態,從而提高用戶界面的交互體驗和流暢性useTransition 主要語法如下:import { useTransition } from 'react';function TabContainer() { // isPending 標志,告訴你是否存在待處理的低優先級工作。 // startTransition 函數 允許你將該部分的狀態更新標記為低優先級。 const [isPending, startTransition] = useTransition(); function handle() {startTransition(() => { // 低優先級的狀態更新工作 {......}}); } return ({......} )}2. useDeferredValue官方解釋:useDeferredValue 是一個 React Hook,可以讓你延遲更新 UI 的某些部分。
通過 useDeferredValue 我們可以將一部分的UI更新工作劃分為低優先級的任務,使它不阻塞主要任務的執行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 的區別useTransition 用于控制過渡狀態,可以在過渡狀態中執行任務,并提供過渡狀態的布爾值來判斷是否處于過渡狀態。useDeferredValue 用于延遲某個值的更新,以避免在渲染過程中處理昂貴的計算或數據獲取,確保界面的流暢性。雖然它們都與并發模式相關,但用途和作用略有不同,具體使用哪一個需要看具體場景。4. 應用場景1)長列表渲染:當渲染大量列表項時,可以對列表項的渲染任務調節為低優先級異步任務,以保證用戶界面的響應性能。
2)大型表單處理:對于包含大量輸入字段的表單,可以使用合理使用對于hooks將表單提交和驗證等任務進行優化調節,以避免阻塞用戶界面。
3)圖片懶加載:當頁面中包含大量圖片時,可以使用 useTransition 將圖片的加載劃分為多個低優先級異步任務,在渲染期間逐步加載圖片,以減少對用戶界面的阻塞。
4)異步數據加載:當頁面中的數據需要從后端異步加載時,可以使用 useTransition 將數據的加載劃分為多個異步任務,以保證用戶界面的響應性能。
五、一個小例子以下以長列表渲染為例子做演示基礎代碼,未作優化處理:
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}>數據項:{item}</div>)} </div></> )}export default index當我們進行持續的輸入時是十分的卡頓的,效果如下:
代碼修改如下:
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}>數據項:{item}</div>)} </div></> )}export default index優化后效果如下:
代碼修改如下:
import React, { memo, useDeferredValue, useState } from 'react'const Item = ({ text }: any) => { return (<div> 數據項:{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優化后效果如下:
1)在vue中,響應式系統通過 proxy 實現對 render函數 的依賴收集和觸發更新,基于追蹤組件依賴的響應式數據的變化,可以更為精準的實現組件的更新,大大避免了不必要的渲染和更新操作,規避了react中狀態更新就會觸發組件及該組件下所有子組件無腦更新的問題。
2)同時vue的異步更新策略也有助于提高性能和響應能力。Vue會在下一個事件循環周期中批量更新組件,這樣可以避免頻繁的DOM操作和重復渲染,提高渲染效率。
3)但vue中暫時沒有 useTransition 和 useDeferredValue 類似的功能操作,無法調度控制作業的優先級
以上就是React渲染機制及相關優化方案的詳細內容,更多關于React渲染機制的資料請關注好吧啦網其它相關文章!
