摘要:并且處理特殊屬性,比如事件綁定。之后根據差異對象操作元素位置變動,刪除,添加等。當節點數過大或者頁面更新次數過多時,頁面卡頓的現象會比較明顯。基于注意使用來減少組件不必要的更新。
1、什么是Diff算法
傳統Diff:diff算法即差異查找算法;對于Html DOM結構即為tree的差異查找算法;而對于計算兩顆樹的差異時間復雜度為O(n^3),顯然成本太高,React不可能采用這種傳統算法;
React Diff:
之前說過,React采用虛擬DOM技術實現對真實DOM的映射,即React Diff算法的差異查找實質是對兩個JavaScript對象的差異查找;
基于三個策略:
Web UI 中 DOM 節點跨層級的移動操作特別少,可以忽略不計。(tree diff)
擁有相同類的兩個組件將會生成相似的樹形結構,擁有不同類的兩個組件將會生成不同的樹形結(component diff)
對于同一層級的一組子節點,它們可以通過唯一 id 進行區分。(element diff)
首先需要明確,只有在React更新階段才會有Diff算法的運用;
React更新機制:
React Diff算法優化策略圖:
React更新階段會對ReactElement類型判斷而進行不同的操作;ReactElement類型包含三種即:文本、Dom、組件;
每個類型的元素更新處理方式:
自定義元素的更新,主要是更新render出的節點,做甩手掌柜交給render出的節點的對應component去管理更新。
text節點的更新很簡單,直接更新文案。
瀏覽器基本元素的更新,分為兩塊:
更新屬性,對比出前后屬性的不同,局部更新。并且處理特殊屬性,比如事件綁定。
子節點的更新,子節點更新主要是找出差異對象,找差異對象的時候也會使用上面的shouldUpdateReactComponent來判斷,如果是可以直接更新的就會遞歸調用子節點的更新,這樣也會遞歸查找差異對象。不可直接更新的刪除之前的對象或添加新的對象。之后根據差異對象操作dom元素(位置變動,刪除,添加等)。
事實上Diff算法只被調用于React更新階段的DOM元素更新過程;為什么這么說?
1、 如果為更新文本類型,內容不同就直接更新替換,并不會調用復雜的Diff算法:
ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) { //與之前保存的字符串比較 if (nextText !== this._currentElement) { this._currentElement = nextText; var nextStringText = "" + nextText; if (nextStringText !== this._stringText) { this._stringText = nextStringText; var commentNodes = this.getHostNode(); // 替換文本元素 DOMChildrenOperations.replaceDelimitedText( commentNodes[0], commentNodes[1], nextStringText ); } } }
2、對于自定義組件元素:
class Tab extends Component { constructor(props) { super(props); this.state = { index: 1, } } shouldComponentUpdate() { .... } render() { return () } }item1
item1
需要明確的是,何為組件,可以說組件只不過是一段Html結構的包裝容器,并且具備管理這段Html結構的狀態等能力;
如上述Tab組件:它的實質內容就是render函數返回的Html結構,而我們所說的Tab類就是這段Html結構的包裝容器(可以理解為一個包裝盒子);
在React渲染機制圖中可以看到,自定義組件的最后結合React Diff優化策略一(不同類的兩個組件具備不同的結構)
3、基本元素:
ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) { var prevElement = this._currentElement; this._currentElement = nextElement; this.updateComponent(transaction, prevElement, nextElement, context); } ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) { //需要多帶帶的更新屬性 this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag); //再更新子節點 this._updateDOMChildren( lastProps, nextProps, transaction, context ); // ...... }
在this._updateDOMChildren方法內部才調用了diff算法。
_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var removedNodes = {}; var mountImages = []; // 獲取新的子元素數組 var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context ); if (!nextChildren && !prevChildren) { return; } var updates = null; var name; var nextIndex = 0; var lastIndex = 0; var nextMountIndex = 0; var lastPlacedNode = null; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { // 同一個引用,說明是使用的同一個component,所以我們需要做移動的操作 // 移動已有的子節點 // NOTICE:這里根據nextIndex, lastIndex決定是否移動 updates = enqueue( updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) ); // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 更新component的.mountIndex屬性 prevChild._mountIndex = nextIndex; } else { if (prevChild) { // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); } // 添加新的子節點在指定的位置上 updates = enqueue( updates, this._mountChildAtIndex( nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ); nextMountIndex++; } // 更新nextIndex nextIndex++; lastPlacedNode = ReactReconciler.getHostNode(nextChild); } // 移除掉不存在的舊子節點,和舊子節點和新子節點不同的舊子節點 for (name in removedNodes) { if (removedNodes.hasOwnProperty(name)) { updates = enqueue( updates, this._unmountChild(prevChildren[name], removedNodes[name]) ); } } }
基于tree diff:
開發組件時,注意保持DOM結構的穩定;即,盡可能少地動態操作DOM結構,尤其是移動操作。
當節點數過大或者頁面更新次數過多時,頁面卡頓的現象會比較明顯。
這時可以通過 CSS 隱藏或顯示節點,而不是真的移除或添加 DOM 節點。
基于component diff:
注意使用 shouldComponentUpdate() 來減少組件不必要的更新。
對于類似的結構應該盡量封裝成組件,既減少代碼量,又能減少component diff的性能消耗。
基于element diff:
對于列表結構,盡量減少類似將最后一個節點移動到列表首部的操作,當節點數量過大或更新操作過于頻繁時,在一定程度上會影響 React 的渲染性能。
接下來手動實現一個簡單的Diff算法即將更新,敬請期待~~~
“積跬步、行千里”—— 持續更新中~,喜歡留下個贊哦!
往期經典好文:
團隊合作必備的Git操作
談談Js前端規范化
從React渲染流程分析Diff算法
相關專欄推薦:
React學習之路
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/52987.html
摘要:并且處理特殊屬性,比如事件綁定。之后根據差異對象操作元素位置變動,刪除,添加等。當節點數過大或者頁面更新次數過多時,頁面卡頓的現象會比較明顯。基于注意使用來減少組件不必要的更新。 1、什么是Diff算法 傳統Diff:diff算法即差異查找算法;對于Html DOM結構即為tree的差異查找算法;而對于計算兩顆樹的差異時間復雜度為O(n^3),顯然成本太高,React不可能采用這種...
摘要:算法的本質是對傳統遍歷算法的優化策略用三大策略將復雜度轉化為復雜度策略一中節點跨層級的移動操作特別少,可以忽略不計。當節點處于同一層級時,提供三種節點操作刪除插入移動。在舊的節點中的,它的,不滿足的條件,因此不做移動操作。 一、react diff算法 diff算法的作用 計算出Virtual DOM中真正變化的部分,并只針對該部分進行原生DOM操作,而非重新渲染整個頁面。 傳統di...
摘要:對同一層級的子節點進行處理時,會根據進行簡要的復用。二性能優化方案由于中性能主要耗費在于階段的算法,因此性能優化也主要針對算法。此時最常用的優化方案即為方法。或者直接使用,原理一致。 一、從React原理談起 react是什么? showImg(https://segmentfault.com/img/bVbcYvf?w=1140&h=384); react是用于構建用戶界面的JS框架...
摘要:對同一層級的子節點進行處理時,會根據進行簡要的復用。或者直接使用,原理一致。 一、從React原理談起 react是什么? showImg(https://segmentfault.com/img/bVbcYvf?w=1140&h=384); react是用于構建用戶界面的JS框架。因此react只負責解決view層的渲染。 react做了什么? Virtual Dom模型 生命周期...
閱讀 671·2023-04-25 18:59
閱讀 1211·2021-09-22 16:00
閱讀 1889·2021-09-22 15:42
閱讀 3594·2021-09-22 15:27
閱讀 1246·2019-08-30 15:54
閱讀 1104·2019-08-30 11:16
閱讀 2445·2019-08-29 16:24
閱讀 820·2019-08-29 12:14