React Diff 過程

Diff 的作用

React Diff 會(huì)幫助我們計(jì)算出 Virtual DOM 中真正變化的部分,并只針對(duì)該部分進(jìn)行實(shí)際 DOM 操作,而非重新渲染整個(gè)頁面,從而保證了每次操作更新后頁面的高效渲染

Diff 策略

  • 只對(duì)同級(jí)的元素進(jìn)行 diff(同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)),如果某一個(gè)節(jié)點(diǎn)在一次更新中跨了層級(jí),React 就不會(huì)復(fù)用該節(jié)點(diǎn),而是重新創(chuàng)建生成新的節(jié)點(diǎn)
  • 兩個(gè)不同類型的元素會(huì)產(chǎn)生不同的樹,如果 div 變成了 p,那么 React 會(huì)銷毀 div 元素及其子孫節(jié)點(diǎn),創(chuàng)建 p 元素及其子孫節(jié)點(diǎn)
  • 對(duì)于兄弟元素,開發(fā)者可以通過 key 來暗示哪些元素在不同的渲染下可以保持穩(wěn)定

基于以上三個(gè)前提策略,React 分別對(duì) tree diff、component diff 以及 element diff 進(jìn)行算法優(yōu)化

tree diff

對(duì)樹進(jìn)行分層比較,兩棵樹只會(huì)對(duì)同一層次的節(jié)點(diǎn)進(jìn)行比較。即同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。這樣只需要對(duì)樹進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹的比較。

如果出現(xiàn)了 DOM 節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作,React diff 會(huì)有怎樣的表現(xiàn)呢?銷毀原來的節(jié)點(diǎn),重新創(chuàng)建新的節(jié)點(diǎn)

component diff

  • 如果是同一類型的組件(引用一致),按照原策略繼續(xù)比較 virtual DOM tree。

  • 如果不是,則將該組件判斷為 dirty component,從而替換整個(gè)組件下的所有子節(jié)點(diǎn)。

  • 對(duì)于同一類型的組件,有可能其 Virtual DOM 沒有任何變化,如果能夠確切的知道這點(diǎn)那可以節(jié)省大量的 diff 運(yùn)算時(shí)間,因此 React 允許用戶通過 shouldComponentUpdate() 來判斷該組件是否需要進(jìn)行 diff。

當(dāng) component D 改變?yōu)?component G 時(shí),即使這兩個(gè) component 結(jié)構(gòu)相似,一旦 React 判斷 D 和 G 是不同類型的組件,就不會(huì)比較二者的結(jié)構(gòu),而是直接刪除 component D,重新創(chuàng)建 component G 以及其子節(jié)點(diǎn)。

element diff

當(dāng)節(jié)點(diǎn)處于同一層級(jí)時(shí),React diff 提供了三種節(jié)點(diǎn)操作,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動(dòng))和 REMOVE_NODE(刪除)。

  • INSERT_MARKUP,新的 component 類型不在老集合里, 即是全新的節(jié)點(diǎn),需要對(duì)新節(jié)點(diǎn)執(zhí)行插入操作。

  • MOVE_EXISTING,新老集合中的 component 類型相同

  • REMOVE_NODE,老 component 類型,在新集合里也有,但對(duì)應(yīng)的 element 不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作,或者老 component 不在新集合里的,也需要執(zhí)行刪除操作。

移動(dòng)

首先對(duì)新集合的節(jié)點(diǎn)進(jìn)行循環(huán)遍歷,for (name in nextChildren),通過唯一 key 可以判斷新老集合中是否存在相同的節(jié)點(diǎn),if (prevChild === nextChild),如果存在相同節(jié)點(diǎn),則進(jìn)行移動(dòng)操作,但在移動(dòng)前需要將當(dāng)前節(jié)點(diǎn)在老集合中的位置與 lastIndex(初始值為 0) 進(jìn)行比較,if (child._mountIndex < lastIndex),則進(jìn)行節(jié)點(diǎn)移動(dòng)操作,否則不執(zhí)行該操作。這是一種順序優(yōu)化手段,lastIndex 一直在更新,表示訪問過的節(jié)點(diǎn)在老集合中最右的位置(即最大的位置),如果新集合中當(dāng)前訪問的節(jié)點(diǎn)比 lastIndex 大,說明當(dāng)前訪問節(jié)點(diǎn)在老集合中就比上一個(gè)節(jié)點(diǎn)位置靠后,則該節(jié)點(diǎn)不會(huì)影響其他節(jié)點(diǎn)的位置,不執(zhí)行移動(dòng)操作,只有當(dāng)訪問的節(jié)點(diǎn)比 lastIndex 小時(shí),才需要進(jìn)行移動(dòng)操作。

參考文章

React 源碼剖析系列 - 不可思議的 react diff

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容