React 的調(diào)和算法Diffing 算法策略詳解
算法策略
React的調(diào)和算法,主要發(fā)生在render階段,調(diào)和算法并不是一個特定的算法函數(shù),而是指在調(diào)和過程中,為提高構(gòu)建workInProcess樹的性能,以及Dom樹更新的性能,而采用的一種策略,又稱diffing算法。 在React 的官網(wǎng)上描述“Diffing” 算法時,提到了“diffing two trees”,但是在源碼實現(xiàn)時,并不是創(chuàng)建好兩棵樹后,再從上往下的diffing這兩棵樹。這個diffing發(fā)生在搭建子節(jié)點時, 實際是新生成的ReactElement 與 current樹上fibe節(jié)點的diffing。 為了將diffing算法的時間復(fù)雜度控制在O(n)(樹diff的時間復(fù)雜度涉及到樹的編輯距離,可以看這里), 采用了如下策略:
只比較同層級的節(jié)點,(貌似這一點沒有在官網(wǎng)中提到)
對于單節(jié)點比較,如果當(dāng)前節(jié)點type 和 key 不相同,不再比較其下子節(jié)點,直接刪掉該節(jié)點及其下整棵子樹,根據(jù)ReactElement重新生成節(jié)點樹。因為React認(rèn)為不同類型的組件生成的樹形結(jié)構(gòu)不一樣,不必復(fù)用。
如果子節(jié)點是數(shù)組,可根據(jù)唯一的key值定位節(jié)點進(jìn)行比較,這樣即使子節(jié)點順序發(fā)生變化,也可以根據(jù)key值進(jìn)行復(fù)用。
值得注意的是,所有節(jié)點的diffing都會比較key,key 默認(rèn)值為null。若是沒有設(shè)置,則null是恒等于null的,認(rèn)為key是相同的。
單節(jié)點diffing
單個節(jié)點進(jìn)行diffing時,會diffing兩組屬性:fibe.elementType vs ReactElement.type , fibe.key vs ReactElement.key, 如果這兩組屬性都相等,數(shù)據(jù)結(jié)構(gòu)的物理空間會有如下復(fù)用邏輯(詳見源碼reconcileSingleElement函數(shù)):
- 如果current fibe 存在 alternate 節(jié)點,(這意味著這個fibe節(jié)點之前參與過調(diào)和),則復(fù)用該alternate節(jié)點的物理空間;否則需要clone current fibe節(jié)點,占用新的物理空間
- 對應(yīng)的instance 或者 Dom 會被復(fù)用
- current fibe 下的child樹也會被直接掛載過來(下一步遞歸子節(jié)點時,會被使用)
如果兩組屬性有一個不相等:
- fibe 根據(jù)ReactElement重新創(chuàng)建
- 對應(yīng)的instance和Dom 也會重建
- child 樹全部刪除。
如果生成的ReactElement 是單節(jié)點,但是對應(yīng)的current樹上是多節(jié)點時,會從逐一查找有沒有匹配的,找到匹配的,其他的都刪除;找不到,全部刪除。例如Couter組件有如下邏輯:當(dāng)點擊次數(shù)大于10時,隱藏點擊按鈕。當(dāng)?shù)竭_(dá)第10次時,span節(jié)點會被復(fù)用。
class Counter extends React.Component{ state={ count:0 } addCount = ()=>{ const count = this.state.count+1; this.setState({count}) } componentDidUpdate(){ console.log("updated") } render(){ return this.state.count < 10 ? [ <button onClick={this.addCount}>點擊</button> <span>點擊次數(shù):{this.state.count}</span> ]:<span>點擊次數(shù):{this.state.count}</span>; } }
數(shù)組節(jié)點diffing
數(shù)組節(jié)點進(jìn)行diffing時,流程比較復(fù)雜:(源碼見reconcileChildrenArray函數(shù))
中間有兩次重要的遍歷,第一次按index遍歷,新舊節(jié)點依次比較,遇到key值不匹配的立即中斷遍歷。 第二次遍歷,對剩下的舊節(jié)點建立 “key - 節(jié)點”的Map表,遍歷剩下的新節(jié)點,按key值從表中查找舊節(jié)點進(jìn)行比較。以下是三種case下,新舊節(jié)點匹配比較的情況。
key值的使用要求
從單節(jié)點和數(shù)組節(jié)點的diffing上看,key值主要是為了減少新建。為了保證diffing時新建舊節(jié)點能匹配上,key值使用時有如下注意:
- 得穩(wěn)定,如果key值每次都變化(比如使用了隨機(jī)數(shù)),diffing時,新舊節(jié)全部匹配不上,將會引起大量的新建;
- 必須得唯一,如果key值不唯一,在建立“key - 節(jié)點”的Map表時,會遺漏和錯亂,導(dǎo)致頁面更新錯誤。
對于單節(jié)點,diffing時key值也會用到,不要認(rèn)為其沒用隨便亂設(shè)置。 也有一些不規(guī)范的用法,對單節(jié)點使用key值來實現(xiàn)組件的銷毀和重建,但這種用法是不符合React的設(shè)計理念的。
例如,有一個日志組件,內(nèi)部拉取日志數(shù)據(jù),對外部沒有數(shù)據(jù)依賴。但是在需求迭代時,在同頁面增加了審批操作,審批后,后端會新增一條日志數(shù)據(jù),這時候會希望前端頁面實時的展示新增的日志數(shù)據(jù),會需要觸發(fā)日志組件重新拉取數(shù)據(jù)。如果不使用向上提升數(shù)據(jù)的方式來解決問題,可以給該組件指定一個隨機(jī)的key,當(dāng)審批操作完成時,修改key值,則組件就會重新構(gòu)建。但這種方式的開銷較高,整個組件樹都會銷毀重建??梢圆捎闷渌鉀Q方案代替,例如可以給該組件指定ref,當(dāng)審批完成后,通過ref調(diào)用該組件的刷新函數(shù),重新獲取數(shù)據(jù),更新組件。
對于數(shù)組節(jié)點,key值主要是在子節(jié)點位置發(fā)生大的錯位時,會起到關(guān)鍵作用。 對于普通的末尾新增,和末尾刪除,第一次index遍歷就可以匹配完,不會進(jìn)入第二次key map的遍歷。 因為key值對“穩(wěn)定”和“唯一”性這兩個要求,一般前端會需要后端對list數(shù)據(jù)提供一個唯一標(biāo)識,對于聚合接口,會給后端增加額外工作,這種情況,可以先了解數(shù)據(jù)的變化特性,再決定是否真的需要設(shè)置key。
對于上面的日志組件,日志是一個列表,新增的日志,都是插在第一行,如果不設(shè)置key,基本上所有的子節(jié)點都要更新。如果設(shè)置key,就需要后端服務(wù)為每一條日志增加“永遠(yuǎn)”惟一的標(biāo)識符,例如id。曾經(jīng)經(jīng)歷過一次因為key值的唯一性變化,導(dǎo)致數(shù)據(jù)更新時頁面展示錯誤的案例。
到此這篇關(guān)于React 的調(diào)和算法(Diffing 算法)的文章就介紹到這了,更多相關(guān)React Diffing 算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react 權(quán)限樹形結(jié)構(gòu)實現(xiàn)代碼
這篇文章主要介紹了react 權(quán)限樹形結(jié)構(gòu)實現(xiàn)代碼,項目背景react + ant design,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2024-05-05在Create React App中啟用Sass和Less的方法示例
這篇文章主要介紹了在Create React App中啟用Sass和Less的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-01-01react native 原生模塊橋接的簡單說明小結(jié)
這篇文章主要介紹了react native 原生模塊橋接的簡單說明小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02React?Hooks的useState、useRef使用小結(jié)
React Hooks 是 React 16.8 版本引入的新特性,useState和useRef是兩個常用的Hooks,本文主要介紹了React?Hooks的useState、useRef使用,感興趣的可以了解一下2024-01-01React中使用TS完成父組件調(diào)用子組件的操作方法
由于在項目開發(fā)過程中,我們往往時需要調(diào)用子組件中的方法,這篇文章主要介紹了React中使用TS完成父組件調(diào)用子組件,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07關(guān)于getDerivedStateFromProps填坑記錄
這篇文章主要介紹了關(guān)于getDerivedStateFromProps填坑記錄,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06