前言
本文是我閱讀《深入React技術棧》所寫的總結筆記。如果您覺得本站的markdown代碼高亮不友好,建議您查看:原文
reconciliation
調和,是react
中最為核心的模塊,它指的是將virtual dom樹轉換成actual dom樹所耗費的最少操作。他需要進行diff
->patch
這兩個過程。diff
是計算virtual dom 樹轉換成另一棵樹進行的最少操作,而patch
是將差異更新到真實的dom
節點。
diff
tree diff
react為了讓運行效率更高,tree diff只對樹進行同層對比,不去比較跨層的節點。比如,在樹A中第一層有一個節點B,想要將他移動到第二層,但并不會直接移動。而是會在第二層創建節點B,接著創建節點B的子節點,創建節點B的子節點的子節點...,然后再把之前第一層的B節點刪除。
<A> <A>
<B/> <C>
<C/> <B/>
</A> -> </C>
</A>
component diff
因為react通過組件化開發,在對比組件差異上也采用上述算法。即,同一層只要出現不是同一類型的組件,就替換該組件的所有子節點。對于同一類型的組件,則通過shouldComponentUpdate
去判斷是否需要通過diff
進行分析。shouldComponentUpdate
默認為true。
element diff
element diff主要是根據mountIndex
和lastIndex
進行比較,在確定是否移動 ,mountIndex
是A節點在舊節點結合中的位置,lastIndex
指訪問過的節點,在舊集合中最右的位置,每次遍歷都有可能會更新。
算法描述
遍歷新節點集合
-
如果出現舊節點集合中有與當前指針所指新節點A相同的節點,則通過對比節點位置進行判斷操作,對比
mountIndex
和lastIndex
:如果
mountIndex>=lastIndex
:不做移動操作。并把lastIndex
更新為mountIndex
。如果
mountIndex<lastIndex
:移動。 如果新節點集合中有舊節點集合中不存在的節點,添加,更新
lastIndex
。最后遍歷舊節點集合,如果存在新節點集合上不存在的點,則將其刪除。
至于為什么要比較mountIndex
和lastIndex
,是因為要保證當前要進行移動操作的節點一定要比lastIndex
小,一是為了節約性能,二是為了使節點排序更有條理,如果不進行比較,看見有相同的節點就移動,整個隊列就亂了套了。
Tips:React中有提示說,要盡量避免將最后一個節點移動到第一個節點的操作。就是因為在一上來比較的時候,本來只需要將最后一個節點移動到第一個位置這一個操作。但按照
diff
算法的邏輯,mountIndex
為最大值,所以lastIndex
也更新為最大值,第一個節點之后的節點都需要進行移動操作。
不太明白的同學可以參考這篇文章->《React之diff算法》,里面有分步圖文描述,更便于理解。
差異隊列
在上一小節中,我們已經知道了diff是如何判斷哪些節點要移動,哪些節點要刪除或新增,這些修改的內容都被加入了差異隊列當中。其中這三種節點操作,分別對應三種type:(在這之前通過了flattenChildren方法將子節點扁平化,key值相同的只取最后一個節點)
INSERT_MARKUP: 舊集合中有不存在的組件類型或節點,需要對組件或節點進行插入操作
MOVE_EXISTING: 源碼中要對比prevChild===nextChild,即舊集合中有與新集合完全一樣的節點,書中說是類型相同且element是可更新的,復用以前的dom節點。
REMOVE_NODE: 舊組件類型在新集合中也存在,但對應的element不同,不能更新和復用。或者舊組件中存在新集合中不存在的,也需要進行刪除操作。
源碼中有三個函數makeInsertMarkup
,makeMove
和makeRemove
,用來返回上述三個操作對象。(大家可以把這里看作redux
中action
的概念)。如下,是進行新增操作的對象
{
type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
content: markup,
fromIndex: null,
fromNode: null,
toIndex: toIndex,
afterNode: afterNode,
}
在遍歷的過程中,react不會一發現需要更新的節點就立即更新到真實dom上,而是將所有的上述差異對象,全部放入差異隊列中,然后通過patch再將其更新到真實的dom上。
patch
patch是指遍歷差異隊列依次更新到真實dom上的操作。通過switch
去匹配差異對象的type,然后進行對應的操作。
=>patch源碼在這里