React渲染機制及相關優化方案

    目錄 一、react渲染步驟 二、concurrent機制以及產生作用的機會 1. 優先級調度: 2. 遞增式渲染: 三、簡單模擬實現 concurrent mode 的遞增式渲染 四、與優先級調度有關的兩個hooks 1. useTran
    目錄
    • 一、react渲染步驟
    • 二、concurrent機制以及產生作用的機會
      • 1. 優先級調度:
      • 2. 遞增式渲染:
    • 三、簡單模擬實現 concurrent mode 的遞增式渲染
      • 四、與優先級調度有關的兩個hooks
        • 1. useTransition
        • 2. useDeferredValue
        • 3. useTransition 與 useDeferredValue 的區別
        • 4. 應用場景
      • 五、一個小例子
        • 1. 下面使用 useTransition 進行優化
        • 2. 使用 useDeferredValue 進行優化
      • 補充:為什么VUE不需要設計 Concurrent Mode

        一、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="http://news.558idc.com/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 = 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)
            }
        }
        // 控制循環執行工作
        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)
        }

        四、與優先級調度有關的兩個hooks

        1. 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

        當我們進行持續的輸入時是十分的卡頓的,效果如下:

        1. 下面使用 useTransition 進行優化

        • 降低 “setList(newList)” 的優先級,使其不阻塞用戶輸入事件的觸發

        代碼修改如下:

        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

        優化后效果如下:

        2. 使用 useDeferredValue 進行優化

        • 降低 “列表部分UI” 更新渲染的優先級,使其不阻塞用戶輸入事件的觸發

        代碼修改如下:

        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

        優化后效果如下:

        補充:為什么VUE不需要設計 Concurrent Mode

        • 出于vue響應式系統的設計實現思路的不同,也就體現了為什么。

        1)在vue中,響應式系統通過 proxy 實現對 render函數 的依賴收集和觸發更新,基于追蹤組件依賴的響應式數據的變化,可以更為精準的實現組件的更新,大大避免了不必要的渲染和更新操作,規避了react中狀態更新就會觸發組件及該組件下所有子組件無腦更新的問題。

        2)同時vue的異步更新策略也有助于提高性能和響應能力。Vue會在下一個事件循環周期中批量更新組件,這樣可以避免頻繁的DOM操作和重復渲染,提高渲染效率。

        3)但vue中暫時沒有 useTransition 和 useDeferredValue 類似的功能操作,無法調度控制作業的優先級

        以上就是React渲染機制及相關優化方案的詳細內容,更多關于React渲染機制的資料請關注技圈網其它相關文章!

        聲明:所有內容來自互聯網搜索結果,不保證100%準確性,僅供參考。如若本站內容侵犯了原著者的合法權益,可聯系我們進行處理。
        發表評論
        更多 網友評論0 條評論)
        暫無評論

        返回頂部

        主站蜘蛛池模板: 亚洲A∨精品一区二区三区下载| 精品无码人妻一区二区三区不卡| 国产无吗一区二区三区在线欢 | 无码人妻一区二区三区在线视频| 国产高清在线精品一区二区| 人妖在线精品一区二区三区| 无码一区二区三区在线| 亚洲熟女综合色一区二区三区 | 一区二区三区亚洲视频| 精品国产AⅤ一区二区三区4区| 亚洲精品日韩一区二区小说| 久久亚洲一区二区| 欧美日韩精品一区二区在线观看| 亚洲无人区一区二区三区| 中文字幕在线一区二区在线| 亚洲AV噜噜一区二区三区| 日本内射精品一区二区视频 | 亚洲AV无码一区二区二三区入口 | 日韩精品人妻一区二区中文八零 | 日韩精品成人一区二区三区| 免费无码一区二区三区| 国产一区二区三区乱码| 国产在线精品一区二区不卡麻豆| 精品国产a∨无码一区二区三区| 亚洲熟妇av一区二区三区漫画 | 精品国产免费一区二区| 国产福利视频一区二区| 国模精品一区二区三区| 少妇无码AV无码一区| av无码人妻一区二区三区牛牛 | 91精品国产一区二区三区左线| 国产一区二区三区免费观看在线| 精品视频一区二区三区四区五区| 中文字幕一区二区在线播放| 日本大香伊一区二区三区| 无码囯产精品一区二区免费 | 久久久国产精品无码一区二区三区| 国产高清一区二区三区| 精品国产一区在线观看| 日韩精品一区二区三区中文字幕| 日韩免费视频一区二区|