亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

React更新渲染原理深入分析

 更新時(shí)間:2022年12月23日 15:14:22   作者:前端開(kāi)發(fā)小司機(jī)  
什么是re-render(重新渲染)?哪些是必要的re-render?哪些是非必要的re-render?如果你對(duì)這些問(wèn)題還不是很明白,那么可以在這篇文章中找到答案

當(dāng)我們調(diào)用 setState 之后發(fā)生了什么?react經(jīng)歷了怎樣的過(guò)程將新的 state 渲染到頁(yè)面上?

一次react更新,核心就是對(duì)虛擬dom進(jìn)行diff,找出最少的需要變化的dom節(jié)點(diǎn),然后對(duì)其進(jìn)行相應(yīng)的dom操作,用戶即可在頁(yè)面上看到更新。但 react 作為廣泛使用的框架,需要考慮更多的因素,考慮多個(gè)更新的優(yōu)先級(jí),考慮主線程占用時(shí)長(zhǎng),考慮diff算法復(fù)雜度,考慮性能。。等等,本文就來(lái)探討一下react在其內(nèi)部是如何處理數(shù)據(jù)更新的。

react在內(nèi)部使用fiber這種數(shù)據(jù)結(jié)構(gòu)來(lái)作為虛擬dom【react16+】,它與dom tree一一對(duì)應(yīng),形成fiber tree,一次react更新,本質(zhì)是fiber tree結(jié)構(gòu)的更新變化。而fiber tree結(jié)構(gòu)的更新,用更專(zhuān)業(yè)的術(shù)語(yǔ)來(lái)講,其實(shí)就是fiber tree的協(xié)調(diào)(Reconcile)。Reconcile中文意思是調(diào)和、使一致,協(xié)調(diào)fiber tree,就是調(diào)整fiber tree的結(jié)構(gòu),使其和更新后的jsx模版結(jié)構(gòu)、dom tree保持一致。

react從16起,將更新機(jī)制分為三個(gè)模塊,也可以說(shuō)是三個(gè)步驟,分別是Schedule【調(diào)度】、Reconcile【協(xié)調(diào)】、render【渲染】

Schedule

為什么需要Schedule?

首先我們要知道react在進(jìn)行協(xié)調(diào)時(shí),提供了兩種模式:Legacy mode 同步阻塞模式和 Concurrent mode 并行模式。

不同上下文中的更新會(huì)觸發(fā)不同的模式,如果是在 eventsetTimeoutnetwork requestcallback 中觸發(fā)更新,react 會(huì)采用 Legacy 模式。如果更新與 SuspenseuseTransition、OffScreen 相關(guān),那么 react 會(huì)采用 Concurrent 模式。

Legacy mode

Legacy mode在協(xié)調(diào)時(shí)會(huì)啟動(dòng) workLoopSyncworkLoopSync 開(kāi)始工作以后,要等到所有 fiber node 都處理完畢以后,才會(huì)結(jié)束工作,也就是 fiber tree 的協(xié)調(diào)過(guò)程不可中斷。

Legacy mode存在的問(wèn)題:如果 fiber tree 的結(jié)構(gòu)很復(fù)雜,那么協(xié)調(diào) fiber tree 可能會(huì)占用大量的時(shí)間,導(dǎo)致主線程會(huì)一直被 js 引擎占用,渲染引擎無(wú)法在規(guī)定時(shí)間(瀏覽器刷新頻率 - 16.7ms)內(nèi)完成工作,使得頁(yè)面出現(xiàn)卡頓(掉幀),影響用戶體驗(yàn)。

Concurrent mode

鑒于Legacy mode存在的問(wèn)題,react團(tuán)隊(duì)在react 16中提出了 Concurrent mode的概念,并在react 18中開(kāi)放使用。react16、17一直為此做準(zhǔn)備。

Concurrent 模式最大的意義在于,使用Concurrent 模式以后的react的應(yīng)用可以做到:

  • 協(xié)調(diào)可以中斷、恢復(fù);不會(huì)長(zhǎng)時(shí)間阻塞瀏覽器渲染
  • 高優(yōu)先級(jí)更新可以中斷低優(yōu)先級(jí)更新,優(yōu)先渲染

那么,怎么做到這兩點(diǎn)呢?

事實(shí)上,Schedule就是用來(lái)完成這個(gè)任務(wù)的,調(diào)度任務(wù)的優(yōu)先級(jí),使高優(yōu)先級(jí)任務(wù)優(yōu)先進(jìn)入Reconcile,并且提供中斷和恢復(fù)機(jī)制。

時(shí)間切片

react采用時(shí)間切片的方式來(lái)實(shí)現(xiàn)協(xié)調(diào)的中斷和恢復(fù),Concurrent mode在協(xié)調(diào)時(shí)會(huì)啟動(dòng) workLoopConcurrent。 workLoopConcurrent 開(kāi)始工作以后,每次協(xié)調(diào) fiber node 時(shí),都會(huì)判斷當(dāng)前時(shí)間片是否到期。如果時(shí)間片到期,會(huì)停止當(dāng)前 workLoopConcurrent,讓出主線程,然后請(qǐng)求下一個(gè)時(shí)間片繼續(xù)協(xié)調(diào)。

協(xié)調(diào)的中斷及恢復(fù),類(lèi)似于瀏覽器的eventloop,js引擎和渲染引擎互斥,在主線程中交替工作。

我們可以通過(guò)模擬 eventLoop來(lái)實(shí)現(xiàn)時(shí)間分片以及重新請(qǐng)求時(shí)間片。一段 js 程序,如果在規(guī)定時(shí)間內(nèi)沒(méi)有結(jié)束,那我們可以主動(dòng)結(jié)束它,然后請(qǐng)求一個(gè)新的時(shí)間片,在下一個(gè)時(shí)間片內(nèi)繼續(xù)處理上一次沒(méi)有結(jié)束的任務(wù)。

let taskQueue = [];   // 任務(wù)列表
let shouldTimeEnd = 5ms;   // 一個(gè)時(shí)間片定義為 5ms
let channel = new MessageChannel();  // 創(chuàng)建一個(gè) MessageChannel 實(shí)例
function wookLoop() {
    let beginTime = performance.now();  // 記錄開(kāi)始時(shí)間
    while(true) { // 循環(huán)處理 taskQueue 中的任務(wù)
        let currentTime = performance.now();  // 記錄下一個(gè)任務(wù)開(kāi)始時(shí)的時(shí)間
        if (currentTime - beginTime >= shouldTimeEnd) break;  // 時(shí)間片已經(jīng)到期,結(jié)束任務(wù)處理
        processTask();  // 時(shí)間片沒(méi)有到期,繼續(xù)處理任務(wù)
  }
    if (taskQueue.length) { // 時(shí)間片到期,通過(guò)調(diào)用 postMessage,請(qǐng)求下一個(gè)時(shí)間片
        channel.port2.postMessage(null); 
  }
}
channel.port1.onmessage = wookLoop;  // 在下一個(gè)時(shí)間片內(nèi)繼續(xù)處理任務(wù)
workLoop(); 

和瀏覽器的消息隊(duì)列 一樣, react 也會(huì)維護(hù)一個(gè)任務(wù)隊(duì)列 taskQueue,然后通過(guò) workLoop 遍歷 taskQueue,依次處理 taskQueue 中的任務(wù)。

taskQueue 中收集任務(wù)是有先后處理順序的,workLoop 每次處理 taskQueue 中的任務(wù)時(shí),都會(huì)挑選優(yōu)先級(jí)最高的任務(wù)進(jìn)行處理。

每觸發(fā)一次 react 更新,意味著一次 fiber tree 的協(xié)調(diào),但協(xié)調(diào)并不會(huì)在更新觸發(fā)時(shí)立刻同步進(jìn)行。相反,react 會(huì)為這一次更新,生成一個(gè) task,并添加到 taskQueue 中,fiber tree 的協(xié)調(diào)方法會(huì)作為新建 taskcallback。當(dāng) wookLoop 開(kāi)始處理該 task 時(shí),才會(huì)觸發(fā) taskcallback,開(kāi)始 fiber tree 的協(xié)調(diào)。

任務(wù)的優(yōu)先級(jí)

react在內(nèi)部定義了 5 種類(lèi)型的優(yōu)先級(jí),以及對(duì)應(yīng)的超時(shí)時(shí)間timeout

  • ImmediatePriority, 直接優(yōu)先級(jí),對(duì)應(yīng)用戶的 click、input、focus 等操作; timeout為 -1,表示任務(wù)要盡快處理;
  • UserBlockingPriority,用戶阻塞優(yōu)先級(jí),對(duì)應(yīng)用戶的 mousemove、scroll 等操作;timeout 為 250 ms;
  • NormalPriority,普通優(yōu)先級(jí),對(duì)應(yīng)網(wǎng)絡(luò)請(qǐng)求、useTransition 等操作; timeout 為 5000 ms;
  • LowPriority,低優(yōu)先級(jí)(未找到應(yīng)用場(chǎng)景);timeout 為 10000 ms;
  • IdlePriority,空閑優(yōu)先級(jí),如 OffScreen; timeout 為 1073741823 ms;

5 種優(yōu)先級(jí)的順序?yàn)? ImmediatePriority > UserBlockingPriority > NormalPriority > LowPriority > IdlePriority

在確定了任務(wù)的優(yōu)先級(jí)以后,react 會(huì)根據(jù)優(yōu)先級(jí)為任務(wù)計(jì)算一個(gè)過(guò)期時(shí)間 expirationTime,即 expirationTime = currentTime + timeout,然后根據(jù) expirationTime 時(shí)間來(lái)決定任務(wù)處理的先后順序。

expirationTime越小的任務(wù)會(huì)被排在task隊(duì)列的越前面,之所以需要timeout,而不是直接對(duì)比優(yōu)先級(jí)等級(jí),是為了避免低優(yōu)先級(jí)任務(wù)長(zhǎng)時(shí)間被 插隊(duì)而導(dǎo)致一直無(wú)響應(yīng);同時(shí),在時(shí)間分片到期時(shí),需要根據(jù)expirationTime判斷下一個(gè)要處理的任務(wù)是否過(guò)期,如果已過(guò)期,就不能讓出主線程,需要立即處理。

??注:react17中用Lanes重構(gòu)了優(yōu)先級(jí)算法,此處不展開(kāi)陳述,有興趣的同學(xué)可查閱相關(guān)文檔。

獲取最先處理的task

react 采用了小頂堆來(lái)存儲(chǔ)task,實(shí)現(xiàn)最小優(yōu)先隊(duì)列,即 taskQueue 是一個(gè)小頂堆,放在堆頂?shù)?code>task是需要最先處理的。

使用最小堆時(shí),有三個(gè)操作:push、pop、peek。

push,入堆操作,即將 task 添加到 taskQueue 中。添加一個(gè)新創(chuàng)建的 task 時(shí),會(huì)將 task 添加到最小堆的堆底,然后對(duì)最小堆做自底向上的調(diào)整。調(diào)整時(shí),會(huì)比較堆節(jié)點(diǎn)(task) 的 expirationTime,將 expirationTime 較小的 task 向上調(diào)整。* peek,獲取堆頂元素,即獲取需要最先處理的 task,執(zhí)行 taskcallback,開(kāi)始 fiber tree 的協(xié)調(diào)。* pop,堆頂元素出堆,即 task 處理完畢,從 taskQueue 中移除。移除堆頂元素以后,會(huì)對(duì)最小堆做自頂向下的調(diào)整。調(diào)整時(shí),也是比較堆節(jié)點(diǎn)(task) 的 expirationTime,將 expirationTime 較大的 task 向下調(diào)整。### 高優(yōu)先級(jí)的更新中斷低優(yōu)先級(jí)的更新

Concurrent 模式下,如果在低優(yōu)先級(jí)更新的協(xié)調(diào)過(guò)程中,有高優(yōu)先級(jí)更新進(jìn)來(lái),那么高優(yōu)先級(jí)更新會(huì)中斷低優(yōu)先級(jí)更新的協(xié)調(diào)過(guò)程。

每次拿到新的時(shí)間片以后,workLoopConcurrent 都會(huì)判斷本次協(xié)調(diào)對(duì)應(yīng)的優(yōu)先級(jí)和上一次時(shí)間片到期中斷的協(xié)調(diào)的優(yōu)先級(jí)是否一樣。如果一樣,說(shuō)明沒(méi)有更高優(yōu)先級(jí)的更新產(chǎn)生,可以繼續(xù)上次未完成的協(xié)調(diào);如果不一樣,說(shuō)明有更高優(yōu)先級(jí)的更新進(jìn)來(lái),此時(shí)要清空之前已開(kāi)始的協(xié)調(diào)過(guò)程,從根節(jié)點(diǎn)開(kāi)始重新協(xié)調(diào)。等高優(yōu)先級(jí)更新處理完成以后,再次從根節(jié)點(diǎn)開(kāi)始處理低優(yōu)先級(jí)更新。

Reconcile

前面說(shuō)到,reconcile(協(xié)調(diào))就是fiber tree 結(jié)構(gòu)的更新,那么具體是怎樣更新的呢?本小節(jié)就來(lái)解答這個(gè)問(wèn)題。

前置知識(shí)

從jsx到dom

Step1: 從jsx生成react element

jsx 模板通過(guò) babel 編譯為 createElement 方法;執(zhí)行組件方法,觸發(fā) createElement 的執(zhí)行,返回 react element

Step2: 從react element生成fiber tree

  • fiber tree 中存在三種類(lèi)型的指針 child、sibling、return。其中,child 指向第一個(gè)子節(jié)點(diǎn),sibling 指向兄弟節(jié)點(diǎn),return 指針指向父節(jié)點(diǎn);* fiber tree 采用的深度優(yōu)先遍歷,如果節(jié)點(diǎn)有子節(jié)點(diǎn),先遍歷子節(jié)點(diǎn);子節(jié)點(diǎn)遍歷結(jié)束以后,再遍歷兄弟節(jié)點(diǎn);沒(méi)有子節(jié)點(diǎn)、兄弟節(jié)點(diǎn),就返回父節(jié)點(diǎn),遍歷父節(jié)點(diǎn)的兄弟節(jié)點(diǎn);* 當(dāng)節(jié)點(diǎn)的 return 指針?lè)祷?null 時(shí),fiber tree 的遍歷結(jié)束;Step3: fiber tree生成之后,從fiber tree到真實(shí)dom,就是處理fiber tree上對(duì)應(yīng)的副作用,包括:
  • 所有 dom 節(jié)點(diǎn)的新增;
  • componentDidMount、useEffectcallback 函數(shù)的觸發(fā);
  • ref 引用的初始化;

雙緩存fiber tree

react 做更新處理時(shí),會(huì)同時(shí)存在兩顆 fiber tree。一顆是已經(jīng)存在的 old fiber tree,對(duì)應(yīng)當(dāng)前屏幕顯示的內(nèi)容,稱(chēng)為 current fiber tree;另外一顆是更新過(guò)程中構(gòu)建的 new fiber tree,稱(chēng)為 workInProgress fiber tree。

current fiber treeworkInProgress fiber tree可以通過(guò)alternate指針互相訪問(wèn)

當(dāng)更新完成以后,使用 workInProgress fiber tree 替換掉 current fiber tree,作為下一次更新的 current fiber tree。

協(xié)調(diào)的過(guò)程

協(xié)調(diào)過(guò)程中主要做三件事情:

1.為 workInProgress fiber tree 生成 fiber node;

2.為發(fā)生變化的 fiber node標(biāo)記副作用 effect;

3.收集帶 effectfiber node;

生成workInProgress fiber tree

workInProgress fiber tree 作為一顆新樹(shù),生成 fiber node 的方式有三種:

  • 克隆(淺拷貝) current fiber node,意味著原來(lái)的 dom 節(jié)點(diǎn)可以復(fù)用,只需要更新 dom 節(jié)點(diǎn)的屬性,或者移動(dòng) dom 節(jié)點(diǎn);
  • 新建一個(gè) fiber node,意味著需要新增加一個(gè) dom 節(jié)點(diǎn);
  • 直接復(fù)用 current fiber node,表示對(duì)應(yīng)的 dom 節(jié)點(diǎn)完全不用做任何處理;

復(fù)用的場(chǎng)景:當(dāng)子組件的渲染方法(類(lèi)組件的 render、函數(shù)組件方法)沒(méi)有觸發(fā),(比如使用了React.memo),沒(méi)有返回新的 react element,子節(jié)點(diǎn)就可以直接復(fù)用 current fiber node。

在日常開(kāi)發(fā)過(guò)程中,我們可以通過(guò)合理使用 ShouldComponentUpdate、React.memo,阻止不必要的組件重新 render,通過(guò)直接復(fù)用 current fiber node,加快 workInProgress fiber tree 的協(xié)調(diào),達(dá)到優(yōu)化的目的。

相反,只要組件的渲染方法被觸發(fā),返回新的 react element,那么就需要根據(jù)新的 react element 為子節(jié)點(diǎn)創(chuàng)建 fiber node(通過(guò)淺拷貝或新建)。

  • 如果能在 current fiber tree 中找到匹配節(jié)點(diǎn),那么可以通過(guò)克隆(淺拷貝) current fiber node 的方式來(lái)創(chuàng)建新的節(jié)點(diǎn);
  • 相反,如果無(wú)法在 current fiber tree 找到匹配節(jié)點(diǎn),那么就需要重新創(chuàng)建一個(gè)新的節(jié)點(diǎn);

我們常說(shuō)的diff算法就是發(fā)生在這一環(huán)節(jié)。

diff算法比較的雙方是 workInProgress fiber tree 中用于構(gòu)建 fiber nodereact elementcurrent fiber tree 中的 fiber node,比較兩者的 keytype,根據(jù)比較結(jié)果來(lái)決定如何為 workInProgress fiber tree 創(chuàng)建 fiber node

【 key 和 type 】:

key就是 jsx 模板中元素上的 key 屬性。如果不寫(xiě)默認(rèn)為undefinedjsx 模板轉(zhuǎn)化為 react element 后,元素的 key 屬性會(huì)作為 react elementkey 屬性。同樣的,react element 轉(zhuǎn)化為 fiber node 以后,react elementkey 屬性也會(huì)作為 fiber nodekey 屬性。

jsx 中不同的元素類(lèi)型,有不同的type

<Component name="xxxx" />  //type = Component, 是一個(gè)函數(shù)
<div></div>    // type = "div", 是一個(gè)字符串
<React.Fragment></React.Fragment>  // type = React.Fragment, 是一個(gè)數(shù)字(react 內(nèi)部定義的); 

jsx 模板轉(zhuǎn)化為 react element 以后,react elementtype 屬性會(huì)根據(jù) jsx 元素的類(lèi)型賦不同的值,可能是組件函數(shù),也可能是 dom 標(biāo)簽字符串,還可能是數(shù)字。 react element 轉(zhuǎn)化為 fiber node 以后,react elementtype 屬性也會(huì)作為 fiber nodetype 屬性。

綜上,判斷拷貝 current fiber node 的邏輯,概括來(lái)就是:

reactElement.key === currentFiberNode.key && reactElement.type === currentFiberNode.type, current fiber node //可以克隆;
reactElement.key !== currentFiberNode.key, current fiber node //不可克隆;
reactElement.key === currentFiberNode.key && reactElement.type !== currentFiberNode.type, current fiber node //不可克隆; 

diff 算法:

  • 已匹配的父節(jié)點(diǎn)的直接子節(jié)點(diǎn)進(jìn)行比較,不跨父節(jié)點(diǎn)比較;
  • 通過(guò)比較 keytype 來(lái)判斷是否需要克隆 current fiber node。只有 keytype 都相等,才克隆 current fiber node 作為新的節(jié)點(diǎn),否則就需要新建一個(gè)節(jié)點(diǎn)。key 值和節(jié)點(diǎn)類(lèi)型type,key 的優(yōu)先級(jí)更高。如果 key 值不相同,那么節(jié)點(diǎn)不可克隆。
  • 當(dāng)比較 single react elementcurrent fiber node list 時(shí),只需要遍歷 current fiber node list,比較每個(gè) current fiber nodereact elementkey 值和 type。只有 keytype 都相等,react elementcurrent fiber node 才能匹配。如果有匹配的,直接克隆current fiber node,作為 react element 對(duì)應(yīng)的 workInProgress fiber node。如果沒(méi)有匹配的 current fiber node,就需要為 react element 重新創(chuàng)建一個(gè)新的 fiber node 作為 workInProgress fiber node。
  • 當(dāng)比較react element listcurrent fiber node list 時(shí),還需要通過(guò)列表下標(biāo) index 判斷 wokrInProgress fiber node 是否相對(duì)于克隆的 current fiber node 發(fā)生了移動(dòng)。這也是diff中最復(fù)雜的地方。

為發(fā)生變化的fiber node標(biāo)記effect

判斷節(jié)點(diǎn)是否發(fā)生變化

  • 節(jié)點(diǎn)只要是重新創(chuàng)建的而不是克隆自 current fiber node,那么節(jié)點(diǎn)就百分之百發(fā)生了變化,需要更新;* 節(jié)點(diǎn)克隆自 current fiber node,需要比較 props 是否發(fā)生了變化,如果 props 發(fā)生了變化,節(jié)點(diǎn)需要更新;* 節(jié)點(diǎn)克隆自 current fiber node,且是組件類(lèi)型,還需要比較 state 是否發(fā)生了變化,如果 state 發(fā)生了變化,節(jié)點(diǎn)需要更新;常見(jiàn)的effect類(lèi)型:
  • Placement,放置,只針對(duì) dom 類(lèi)型的 fiber node,表示節(jié)點(diǎn)需要做移動(dòng)或者添加操作。
  • Update,更新,針對(duì)所有類(lèi)型的 fiber node,表示 fiber node 需要做更新操作。
  • PlacementAndUpdate,放置并更新,只針對(duì) dom 類(lèi)型的 fiber node,表示節(jié)點(diǎn)發(fā)生了移動(dòng)且 props 發(fā)生了變化。
  • Ref,表示節(jié)點(diǎn)存在 ref,需要初始化 / 更新 ref.current
  • Deletion,刪除,針對(duì)所有類(lèi)型的 fiber node,表示 fiber node 需要移除。
  • Snapshot,快照,主要是針對(duì)類(lèi)組件 fiber node。當(dāng)類(lèi)組件 fiber node 發(fā)生了 mount 或者 update 操作,且定義了 getSnapshotBeforeUpdate 方法,就會(huì)標(biāo)記 Snapshot。
  • Passive,主要針對(duì)函數(shù)組件 fiber node,表示函數(shù)組件使用了 useEffect。當(dāng)函數(shù)組件節(jié)點(diǎn)發(fā)生 mount 或者 update 操作,且使用了 useEffect hook,就會(huì)給 fiber node 標(biāo)記 Passive。
  • Layout,主要針對(duì)函數(shù)組件 fiber node,表示函數(shù)組件使用了 useLayoutEffect。當(dāng)函數(shù)組件節(jié)點(diǎn)發(fā)生 mount 或者 update 操作,且使用了 useLayoutEffect hook,就會(huì)給 fiber node 標(biāo)記 Layout。

react 使用二進(jìn)制數(shù)來(lái)聲明 effect,如 Placement 為 2 (0000 0010),Update 為 4 (0000 0100)。一個(gè) fiber node 可同時(shí)標(biāo)記多個(gè) effect,如函數(shù)組件 props 發(fā)生變化且使用了 useEffect hook,那么就可以使用 Placement | Update = 516(位運(yùn)算符) 來(lái)標(biāo)記。

收集帶effect的fiber node

如果一個(gè) fiber node 被標(biāo)記了 effect,那么 react 就會(huì)在這個(gè) fiber node 完成協(xié)調(diào)以后,將這個(gè) fiber node 收集到effectList中。當(dāng)整顆 fiber tree 完成協(xié)調(diào)以后,所有被標(biāo)記 effectfiber node 都被收集到一起。

收集fiber nodeeffectList 采用單鏈表結(jié)構(gòu)存儲(chǔ),firstEffect 指向第一個(gè)標(biāo)記 effectfiber node,lastEffect 標(biāo)記最后一個(gè) fiber node,節(jié)點(diǎn)之間通過(guò) nextEffect 指針連接。

由于 fiber tree 協(xié)調(diào)時(shí)采用的順序是深度優(yōu)先,協(xié)調(diào)完成的順序是子節(jié)點(diǎn)、子節(jié)點(diǎn)兄弟節(jié)點(diǎn)、父節(jié)點(diǎn),所以收集帶 effect 標(biāo)記的 fiber node 時(shí),順序也是子節(jié)點(diǎn)、子節(jié)點(diǎn)兄弟節(jié)點(diǎn)、父節(jié)點(diǎn)。

Render

render也稱(chēng)為commit,是對(duì)協(xié)調(diào)過(guò)程中標(biāo)記的effect 的處理

effect 的處理分為三個(gè)階段,這三個(gè)階段按照從前到后的順序?yàn)椋?/p>

1.before mutation 階段 (dom 操作之前)

2.mutation 階段 (dom 操作)

3.layout 階段 (dom 操作之后)

不同的階段,處理的 effect 種類(lèi)也不相同。在每個(gè)階段,react 都會(huì)從 effectList 鏈表的頭部 - firstEffect 開(kāi)始,按序遍歷 fiber node, 直到 lastEffect。

before mutation階段

before mutation 階段的主要工作是處理帶 Snapshot 標(biāo)記的 fiber node。 從 firstEffect 開(kāi)始遍歷 effect 列表,如果 fiber nodeSnapshot 標(biāo)記,觸發(fā) getSnapshotBeforeUpdate 方法。

mutation階段

mutation 階段的主要工作是處理帶 Deletion 、 Placement、PlacementAndUpdateUpdate 標(biāo)記的 fiber node。 在這一階段,涉及到 dom 節(jié)點(diǎn)的更新、新增、移動(dòng)、刪除,組件節(jié)點(diǎn)刪除導(dǎo)致的 componentWillUnmount、destory 方法的觸發(fā),以及刪除節(jié)點(diǎn)引發(fā)的 ref 引用的重置。

dom 節(jié)點(diǎn)的更新:

  • 通過(guò)原生的 API setAttribute、removeArrribute 修改 dom 節(jié)點(diǎn)的 attr;
  • 直接修改 dom 節(jié)點(diǎn)的 style
  • 直接修改 dom 節(jié)點(diǎn)的 innerHtml、textContent;

dom 節(jié)點(diǎn)的新增和移動(dòng):

  • 如果新增(移動(dòng))的節(jié)點(diǎn)是父節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn),那么可以直接使用 appendChild 方法。
  • 如果不是最后一個(gè)節(jié)點(diǎn),需要使用 insertBefore 方法。通過(guò)遍歷找到第一個(gè)沒(méi)有帶Placement標(biāo)記的節(jié)點(diǎn)作為insertBefore的定位元素。

dom節(jié)點(diǎn)的刪除:

  • 如果節(jié)點(diǎn)是 dom 節(jié)點(diǎn),通過(guò) removeChild 移除;
  • 如果節(jié)點(diǎn)是組件節(jié)點(diǎn),觸發(fā) componentWillUnmount、useEffectdestory 方法的執(zhí)行;
  • 如果標(biāo)記 Deletion 的節(jié)點(diǎn)的子節(jié)點(diǎn)中有組件節(jié)點(diǎn),深度優(yōu)先遍歷子節(jié)點(diǎn),依次觸發(fā)子節(jié)點(diǎn)的 componentWillUnmountuseEffectdestory 方法的執(zhí)行;
  • 如果標(biāo)記 Deletion 的節(jié)點(diǎn)及子節(jié)點(diǎn)關(guān)聯(lián)了 ref 引用,要將 ref 引用置空,及 ref.current = null(也是深度優(yōu)先遍歷);

layout 階段

layout 階段的主要工作是處理帶 update 標(biāo)記的組件節(jié)點(diǎn)和帶 ref 標(biāo)記的所有節(jié)點(diǎn)。 工作內(nèi)容如下:

  • 如果類(lèi)組件節(jié)點(diǎn)是mount操作,觸發(fā) componentDidMount;如果是 update 操作,觸發(fā) componentDidUpdate;
  • 如果函數(shù)組件節(jié)點(diǎn)時(shí) mount 操作,觸發(fā) useLayoutEffectcallback;如果是 update 操作,先觸發(fā)上一次更新生成的 destory,再觸發(fā)這一次的 callback
  • 異步調(diào)度函數(shù)組件的 useEffect;
  • 如果組件節(jié)點(diǎn)關(guān)聯(lián)了 ref 引用,要初始化 ref.current;

到此這篇關(guān)于React更新渲染原理深入分析的文章就介紹到這了,更多相關(guān)React更新渲染內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • react的ui庫(kù)antd中form表單使用SelectTree反顯問(wèn)題及解決

    react的ui庫(kù)antd中form表單使用SelectTree反顯問(wèn)題及解決

    這篇文章主要介紹了react的ui庫(kù)antd中form表單使用SelectTree反顯問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • React冒泡和阻止冒泡的應(yīng)用詳解

    React冒泡和阻止冒泡的應(yīng)用詳解

    這篇文章主要介紹了React冒泡和阻止冒泡的應(yīng)用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • React組件的生命周期深入理解分析

    React組件的生命周期深入理解分析

    組件的生命周期就是React的工作過(guò)程,就好比人有生老病死,自然界有日月更替,每個(gè)組件在網(wǎng)頁(yè)中也會(huì)有被創(chuàng)建、更新和刪除,如同有生命的機(jī)體一樣
    2022-12-12
  • React中的useEffect useLayoutEffect到底怎么用

    React中的useEffect useLayoutEffect到底怎么用

    這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2023-02-02
  • React路由規(guī)則定義與聲明式導(dǎo)航及編程式導(dǎo)航分別介紹

    React路由規(guī)則定義與聲明式導(dǎo)航及編程式導(dǎo)航分別介紹

    這篇文章主要介紹了React路由規(guī)則的定義、聲明式導(dǎo)航、編程式導(dǎo)航,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-09-09
  • React Hooks使用方法全方位介紹

    React Hooks使用方法全方位介紹

    在react類(lèi)組件(class)寫(xiě)法中,有setState和生命周期對(duì)狀態(tài)進(jìn)行管理,但是在函數(shù)組件中不存在這些,故引入hooks(版本:>=16.8),使開(kāi)發(fā)者在非class的情況下使用更多react特性
    2023-03-03
  • react中路由和按需加載的問(wèn)題

    react中路由和按需加載的問(wèn)題

    這篇文章主要介紹了react中路由和按需加載的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React函數(shù)組件與類(lèi)的區(qū)別有哪些

    React函數(shù)組件與類(lèi)的區(qū)別有哪些

    函數(shù)式組件的基本意義就是,組件實(shí)際上是一個(gè)函數(shù),不是類(lèi),下面這篇文章主要給大家介紹了關(guān)于React中函數(shù)組件與類(lèi)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • react+antd+upload結(jié)合使用示例

    react+antd+upload結(jié)合使用示例

    這篇文章主要為大家介紹了react+antd+upload結(jié)合使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法

    React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法

    這篇文章主要介紹了React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn),主要有兩種跳轉(zhuǎn)方式一種是編程式跳轉(zhuǎn)另一種是NavLink鏈接式跳轉(zhuǎn),每種方式通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08

最新評(píng)論