首頁  >  文章  >  web前端  >  React框架有哪些演算法? react框架的演算法詳解

React框架有哪些演算法? react框架的演算法詳解

寻∝梦
寻∝梦原創
2018-09-11 14:58:161702瀏覽

本篇文章主要的講述了關於react框架的原理詳解,下面還有很多關於react的深入了解,現在就讓我們一起來看看這篇文章吧

#React 搞了2年多了,對這門框架可謂又愛又恨,它的優勢大家都熟知,但是缺點也漸漸暴露,一個大型項目裡,配合ReduxReactRouter等三方框架後,結合複雜的業務程式碼量會變得非常大(前端程式碼常常是以前的1.5倍)。如果前期底層設計得不好,時常面臨開發效率低的問題。下面歸納了一些React框架的核心概念,希望對大家有幫助:

React diff 演算法

React的diff演算法是Virtual DOM之所以任性的最大依仗,大家知道頁面的性能一般是由渲染速度和渲染次數決定,如何最大程度地利用diff演算法進行開發?我們先看看它的原理。

傳統diff 演算法

計算一棵樹形結構轉換成另一棵樹形結構的最少操作,傳統diff 演算法透過循環遞歸對節點進行依次對比,效率低下,演算法複雜度達到O(n^3),其中n 是樹中節點的總數。也就是說如果要展示1000個節點,就要依序執行上十億次的比較。這個效能消耗對對於前端專案來說是不可接受的。

核心演算法

如上所見,傳統 diff 演算法的複雜度為 O(n^3),顯然這是無法滿足效能要求的。而React透過制定大膽的策略,將 O(n^3) 複雜度的問題轉換成 O(n) 複雜度的問題。他是怎麼做到的?

tree diff

Web UI 中 DOM 節點跨層級的移動操作特別少,可以忽略不計。 React 對樹的演算法進行了簡潔明了的最佳化,即對樹進行分層比較,兩棵樹只會對同一層次的節點進行比較。如下圖所示:

React框架有哪些演算法? 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();
      }
    }
  }
}

為什麼要減少DOM節點的跨層級運算?

如下圖,A 節點(包括其子節點)整個被移動到D 節點下,由於React 只會簡單的考慮同層級節點的位置變換,而對於不同層級的節點,只有建立和刪除操作。當根節點發現子節點中 A 消失了,就會直接銷毀 A;當 D 發現多了一個子節點 A,則會建立新的 A(包括子節點)作為其子節點。此時,React diff 的執行情況:create A -> create B -> create C -> delete A。

React框架有哪些演算法? react框架的演算法詳解

由此可發現,當節點跨層級移動時,並不會出現想像中的移動操作,而是以A 為根節點的樹被整個重新創建,這是一種影響React 效能的操作。

component diff

擁有相同類別的兩個元件將會產生相似的樹狀結構,擁有不同類別的兩個元件將會產生不同的樹形結構。

  • 如果是相同類型的元件,請依照原始策略繼續比較 virtual DOM tree

  • 如果不是,則將該元件判斷為 dirty component,從而替換整個元件下的所有子節點。

  • 對於同一類型的元件,有可能其Virtual DOM 沒有任何變化,如果能夠確切的知道這點那可以節省大量的diff 運算時間,因此React 允許使用者透過shouldComponentUpdate() 來判斷該元件是否需要進行diff。

React框架有哪些演算法? react框架的演算法詳解

如上圖,當component D 改變為component G 時,即使這兩個component 結構相似,一旦React 判斷D 和G 是不同類型的元件,就不會比較二者的結構,而是直接刪除component D,重新建立component G 以及其子節點。雖然當兩個component 是不同類型但結構相似時,React diff 會影響效能,但正如React 官方部落格所言:不同類型的component 是很少存在相似DOM tree 的機會,因此這種極端因素很難在實現開發過程中造成重大影響的。

element diff

對於同一層級的一組子節點,它們可以透過唯一 id 來區分。 React 提出最佳化策略:讓開發者對同一層級的同組子節點,增加唯一 key 進行區分,雖然只是小小的改動,但效能上卻發生了翻天覆地的變化!

新舊集合所包含的節點,如下圖所示,新舊集合進行diff 差異化對比,透過key 發現新舊集合中的節點都是相同的節點,因此無需進行節點刪除和創建,只需要將舊集合中節點的位置進行移動,更新為新集合中節點的位置,此時React 給出的diff 結果為:B、D 不做任何操作,A、C 進行移動操作,即可。

React框架有哪些演算法? react框架的演算法詳解

開發建議

(1)[基於tree diff] 開發元件時,保持穩定的DOM結構有助於維持整體的性能。換而言之,盡可能少地動態操作DOM結構,尤其是移動操作。當節點數過大或頁面更新次數過多時,頁面卡頓的現像比較明顯。可以透過 CSS 隱藏或顯示節點,而不是真的移除或新增 DOM 節點。

(2)[基於component diff] 開發元件時,請注意使用 shouldComponentUpdate() 來減少元件不必要的更新。除此之外,對於類似的結構應該盡量封裝成元件,既減少程式碼量,又能減少component diff的效能消耗。

(3)[基於element diff] 對於列表結構,盡量減少類似將最後一個節點移動到列表首部的操作,當節點數量過大或更新操作過於頻繁時,在一定程度上會影響React 的渲染效能。

React Lifecycle

React的生命週期具體可分為四種情況:

React框架有哪些演算法? react框架的演算法詳解

  • 當首次裝載元件時,依序執行getDefaultPropsgetInitialStatecomponentWillMountrendercomponentDidMount

  • ##當卸載元件時,執行componentWillUnmount

  • 當重新裝載元件時,此時會依序執行getInitialStatecomponentWillMountrendercomponentDidMount,但不執行getDefaultProps;

  • #當再次渲染元件時,元件接受到更新狀態,此時依序執行componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

React元件的3種狀態

狀態一:MOUNTING

mountComponent 負責管理生命週期中的 getInitialStatecomponentWillMountrendercomponentDidMount

React框架有哪些演算法? react框架的演算法詳解

狀態二:RECEIVE_PROPS

updateComponent 負責管理生命週期中的componentWillReceivePropsshouldComponentUpdate componentWillUpdaterendercomponentDidUpdate

React框架有哪些演算法? react框架的演算法詳解

狀態三:UNMOUNTING

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);
}

React生命周期总结

React框架有哪些演算法? react框架的演算法詳解

生命周期 调用次数 能否使用setState()
getDefaultProps 1
getInitialState 1
componentWillMount 1
render >=1
componentDidMount 1
componentWillReceiveProps >=0
shouldComponentUpdate >=0
componentWillUpdate >=0
componentDidUpdate >=0
componentWillUnmount 1
componentDidUnmount 1

setState实现机制

setStateReact框架的核心方法之一,下面介绍一下它的原理:

React框架有哪些演算法? 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 执行更新。

当状态不为 MOUNTINGRECEIVING_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
  );
}

如果在 shouldComponentUpdatecomponentWillUpdate 中调用 setState,此时的状态已经从 RECEIVING_PROPS -> NULL,则 performUpdateIfNecessary 就会调用 updateComponent 进行组件更新,但 updateComponent 又会调用 shouldComponentUpdatecomponentWillUpdate,因此造成循环调用,使得浏览器内存占满后崩溃。

开发建议

不建议在 getDefaultPropsgetInitialStateshouldComponentUpdatecomponentWillUpdaterendercomponentWillUnmount 中调用 setState,特别注意:不能在 shouldComponentUpdatecomponentWillUpdate中调用 setState,会导致循环调用。

本篇文章到这就结束了(想看更多就到PHP中文网React使用手册栏目中学习),有问题的可以在下方留言提问。

以上是React框架有哪些演算法? react框架的演算法詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn