本篇文章主要的講述了關於react框架的原理詳解,下面還有很多關於react的深入了解,現在就讓我們一起來看看這篇文章吧
#React 搞了2年多了,對這門框架可謂又愛又恨,它的優勢大家都熟知,但是缺點也漸漸暴露,一個大型項目裡,配合
Redux
、ReactRouter
等三方框架後,結合複雜的業務程式碼量會變得非常大(前端程式碼常常是以前的1.5倍)。如果前期底層設計得不好,時常面臨開發效率低的問題。下面歸納了一些React框架的核心概念,希望對大家有幫助:
React的diff
演算法是Virtual DOM
之所以任性的最大依仗,大家知道頁面的性能一般是由渲染速度和渲染次數決定,如何最大程度地利用diff
演算法進行開發?我們先看看它的原理。
計算一棵樹形結構轉換成另一棵樹形結構的最少操作,傳統diff 演算法透過循環遞歸對節點進行依次對比,效率低下,演算法複雜度達到O(n^3)
,其中n 是樹中節點的總數。也就是說如果要展示1000個節點,就要依序執行上十億次的比較。這個效能消耗對對於前端專案來說是不可接受的。
如上所見,傳統 diff 演算法的複雜度為 O(n^3)
,顯然這是無法滿足效能要求的。而React
透過制定大膽的策略,將 O(n^3)
複雜度的問題轉換成 O(n)
複雜度的問題。他是怎麼做到的?
Web UI 中 DOM 節點跨層級的移動操作特別少,可以忽略不計。 React 對樹的演算法進行了簡潔明了的最佳化,即對樹進行分層比較,兩棵樹只會對同一層次的節點進行比較。如下圖所示:
React 透過updateDepth 對Virtual DOM 樹進行層級控制,只會對相同顏色方塊內的DOM 節點進行比較,即同一個父節點下的所有子節點。當發現節點已經不存在,則該節點及其子節點會被完全刪除掉,不會用於進一步的比較。這樣只需要對樹進行一次遍歷,便能完成整個 DOM 樹的比較。
// tree diff算法实现updateChildren: function(nextNestedChildrenElements, transaction, context) { updateDepth++; var errorThrown = true; try { this._updateChildren(nextNestedChildrenElements, transaction, context); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); } } } }
如下圖,A 節點(包括其子節點)整個被移動到D 節點下,由於React 只會簡單的考慮同層級節點的位置變換,而對於不同層級的節點,只有建立和刪除操作。當根節點發現子節點中 A 消失了,就會直接銷毀 A;當 D 發現多了一個子節點 A,則會建立新的 A(包括子節點)作為其子節點。此時,React diff
的執行情況:create A -> create B -> create C -> delete A。
由此可發現,當節點跨層級移動時,並不會出現想像中的移動操作,而是以A 為根節點的樹被整個重新創建,這是一種影響React
效能的操作。
擁有相同類別的兩個元件將會產生相似的樹狀結構,擁有不同類別的兩個元件將會產生不同的樹形結構。
如果是相同類型的元件,請依照原始策略繼續比較 virtual DOM tree
。
如果不是,則將該元件判斷為 dirty component
,從而替換整個元件下的所有子節點。
對於同一類型的元件,有可能其Virtual DOM
沒有任何變化,如果能夠確切的知道這點那可以節省大量的diff 運算時間,因此React
允許使用者透過shouldComponentUpdate()
來判斷該元件是否需要進行diff。
如上圖,當component D
改變為component G
時,即使這兩個component
結構相似,一旦React
判斷D 和G 是不同類型的元件,就不會比較二者的結構,而是直接刪除component D
,重新建立component G
以及其子節點。雖然當兩個component
是不同類型但結構相似時,React diff
會影響效能,但正如React
官方部落格所言:不同類型的component
是很少存在相似DOM tree
的機會,因此這種極端因素很難在實現開發過程中造成重大影響的。
對於同一層級的一組子節點,它們可以透過唯一 id 來區分。 React 提出最佳化策略:讓開發者對同一層級的同組子節點,增加唯一 key 進行區分,雖然只是小小的改動,但效能上卻發生了翻天覆地的變化!
新舊集合所包含的節點,如下圖所示,新舊集合進行diff 差異化對比,透過key 發現新舊集合中的節點都是相同的節點,因此無需進行節點刪除和創建,只需要將舊集合中節點的位置進行移動,更新為新集合中節點的位置,此時React 給出的diff 結果為:B、D 不做任何操作,A、C 進行移動操作,即可。
(1)[基於tree diff] 開發元件時,保持穩定的DOM結構有助於維持整體的性能。換而言之,盡可能少地動態操作DOM結構,尤其是移動操作。當節點數過大或頁面更新次數過多時,頁面卡頓的現像比較明顯。可以透過 CSS 隱藏或顯示節點,而不是真的移除或新增 DOM 節點。
(2)[基於component diff] 開發元件時,請注意使用 shouldComponentUpdate()
來減少元件不必要的更新。除此之外,對於類似的結構應該盡量封裝成元件,既減少程式碼量,又能減少component diff
的效能消耗。
(3)[基於element diff] 對於列表結構,盡量減少類似將最後一個節點移動到列表首部的操作,當節點數量過大或更新操作過於頻繁時,在一定程度上會影響React 的渲染效能。
React的生命週期具體可分為四種情況:
當首次裝載元件時,依序執行getDefaultProps
、getInitialState
、componentWillMount
、render
和componentDidMount
;
##當卸載元件時,執行componentWillUnmount
;
當重新裝載元件時,此時會依序執行getInitialState
、componentWillMount
、render
和componentDidMount
,但不執行getDefaultProps
;
#當再次渲染元件時,元件接受到更新狀態,此時依序執行componentWillReceiveProps
、shouldComponentUpdate
、componentWillUpdate
、render
和componentDidUpdate
。
mountComponent
負責管理生命週期中的 getInitialState
、componentWillMount
、render
和componentDidMount
。
updateComponent
負責管理生命週期中的componentWillReceiveProps
、shouldComponentUpdate
、componentWillUpdate
、render
和componentDidUpdate
。
unmountComponent
負責管理生命週期中的 componentWillUnmount
。 (想看更多就到PHP中文網React參考手冊欄位學習)
先將狀態設定為UNMOUNTING
,若有componentWillUnmount
,則執行;如果此時在componentWillUnmount
中呼叫setState
,是不會觸發reRender
。更新狀態為 NULL
,完成元件卸載作業。實作程式碼如下:
// 卸载组件unmountComponent: function() { // 设置状态为 UNMOUNTING this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; // 如果存在 componentWillUnmount,则触发 if (this.componentWillUnmount) { this.componentWillUnmount(); } // 更新状态为 null this._compositeLifeCycleState = null; this._renderedComponent.unmountComponent(); this._renderedComponent = null; ReactComponent.Mixin.unmountComponent.call(this); }
生命周期 | 调用次数 | 能否使用setState() |
---|---|---|
getDefaultProps | 1 | 否 |
getInitialState | 1 | 否 |
componentWillMount | 1 | 是 |
render | >=1 | 否 |
componentDidMount | 1 | 是 |
componentWillReceiveProps | >=0 | 是 |
shouldComponentUpdate | >=0 | 否 |
componentWillUpdate | >=0 | 否 |
componentDidUpdate | >=0 | 否 |
componentWillUnmount | 1 | 否 |
componentDidUnmount | 1 | 否 |
setState
是React
框架的核心方法之一,下面介绍一下它的原理:
// 更新 statesetState: function(partialState, callback) { // 合并 _pendingState this.replaceState( assign({}, this._pendingState || this.state, partialState), callback ); },
当调用 setState
时,会对 state
以及 _pendingState
更新队列进行合并操作,但其实真正更新 state
的幕后黑手是replaceState
。
// 更新 statereplaceState: function(completeState, callback) { validateLifeCycleOnReplaceState(this); // 更新队列 this._pendingState = completeState; // 判断状态是否为 MOUNTING,如果不是,即可执行更新 if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) { ReactUpdates.enqueueUpdate(this, callback); } },
replaceState
会先判断当前状态是否为 MOUNTING
,如果不是即会调用 ReactUpdates.enqueueUpdate
执行更新。
当状态不为 MOUNTING
或 RECEIVING_PROPS
时,performUpdateIfNecessary
会获取 _pendingElement
、_pendingState
、_pendingForceUpdate
,并调用 updateComponent
进行组件更新。
// 如果存在 _pendingElement、_pendingState、_pendingForceUpdate,则更新组件performUpdateIfNecessary: function(transaction) { var compositeLifeCycleState = this._compositeLifeCycleState; // 当状态为 MOUNTING 或 RECEIVING_PROPS时,则不更新 if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { return; } var prevElement = this._currentElement; var nextElement = prevElement; if (this._pendingElement != null) { nextElement = this._pendingElement; this._pendingElement = null; } // 调用 updateComponent this.updateComponent( transaction, prevElement, nextElement ); }
如果在
shouldComponentUpdate
或componentWillUpdate
中调用setState
,此时的状态已经从RECEIVING_PROPS -> NULL
,则performUpdateIfNecessary
就会调用updateComponent
进行组件更新,但updateComponent
又会调用shouldComponentUpdate
和componentWillUpdate
,因此造成循环调用,使得浏览器内存占满后崩溃。
不建议在 getDefaultProps
、getInitialState
、shouldComponentUpdate
、componentWillUpdate
、render
和 componentWillUnmount
中调用 setState,特别注意:不能在 shouldComponentUpdate
和 componentWillUpdate
中调用 setState
,会导致循环调用。
本篇文章到这就结束了(想看更多就到PHP中文网React使用手册栏目中学习),有问题的可以在下方留言提问。
以上是React框架有哪些演算法? react框架的演算法詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!