成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

React 更新視圖過(guò)程

y1chuan / 697人閱讀

摘要:最終的更新策略都在中。按以上兩個(gè)狀態(tài)的,那么遍歷完就會(huì)記錄下需要做兩步變更新增一個(gè)節(jié)點(diǎn)插入到第二個(gè)位置,刪除原來(lái)第二個(gè)位置上的。例遍歷結(jié)果第二個(gè)節(jié)點(diǎn)新增一個(gè),第四個(gè)節(jié)點(diǎn)新增一個(gè),刪除第二個(gè)?;旧现蟮阶罱K的變化的過(guò)程就是這么結(jié)束了。

說(shuō)在前面,著重梳理實(shí)際更新組件和 dom 部分的代碼,但是關(guān)于異步,transaction,批量合并新?tīng)顟B(tài)等新細(xì)節(jié)只描述步驟。一來(lái)因?yàn)檫@些細(xì)節(jié)在讀源碼的時(shí)候只讀了部分,二來(lái)如果要把這些都寫(xiě)出來(lái)要寫(xiě)老長(zhǎng)老長(zhǎng)。

真實(shí)的 setState 的過(guò)程:

setState( partialState ) {
  // 1. 通過(guò)組件對(duì)象獲取到渲染對(duì)象
  var internalInstance = ReactInstanceMap.get(publicInstance);
  // 2. 把新的狀態(tài)放在渲染對(duì)象的 _pendingStateQueue 里面 internalInstance._pendingStateQueue.push( partialState )
  // 3. 查看下是否正在批量更新
  //   3.1. 如果正在批量更新,則把當(dāng)前這個(gè)組件認(rèn)為是臟組件,把其渲染對(duì)象保存到 dirtyComponents 數(shù)組中
  //   3.2. 如果可以批量更新,則調(diào)用 ReactDefaultBatchingStrategyTransaction 開(kāi)啟更新事務(wù),進(jìn)行真正的 vdom diff。
  //    |
  //    v
  // internalInstance.updateComponent( partialState )
}

updateComponent 方法的說(shuō)明:

updateComponent( partialState ) {
  // 源碼中 partialState 是從 this._pendingStateQueue 中獲取的,這里簡(jiǎn)化了狀態(tài)隊(duì)列的東西,假設(shè)直接從外部傳入
  var inst = this._instance;
  var nextState = Object.assign( {}, inst.state, partialState );
  // 獲得組件對(duì)象,準(zhǔn)備更新,先調(diào)用生命周期函數(shù)
      // 調(diào)用 shouldComponentUpdate 看看是否需要更新組件(這里先忽略 props 和 context的更新)
  if ( inst.shouldComponentUpdate(inst.props, nextState, nextContext) ) {
    // 更新前調(diào)用 componentWillUpdate
    isnt.componentWillUpdate( inst.props, nextState, nextContext );
    inst.state = nextState;
    // 生成新的 vdom
    var nextRenderedElement = inst.render();
    // 通過(guò)上一次的渲染對(duì)象獲取上一次生成的 vdom
    var prevComponentInstance = this._renderedComponent; // render 中的根節(jié)點(diǎn)的渲染對(duì)象
    var prevRenderedElement = prevComponentInstance._currentElement; // 上一次的根節(jié)點(diǎn)的 vdom
    // 通過(guò)比較新舊 vdom node 來(lái)決定是更新 dom node 還是根據(jù)最新的 vdom node 生成一份真實(shí) dom node 替換掉原來(lái)的
    if ( shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement) ) {
      // 更新 dom node
      prevComponentInstance.receiveComponent( nextRenderedElement )
    } else {
      // 生成新的 dom node 替換原來(lái)的(以下是簡(jiǎn)化版,只為了說(shuō)明流程)
      var oldHostNode = ReactReconciler.getHostNode( prevComponentInstance );
      // 根據(jù)新的 vdom 生成新的渲染對(duì)象
      var child = instantiateReactComponent( nextRenderedElement );
      this._renderedComponent = child;
      // 生成新的 dom node
      var nextMarkup = child.mountComponent();
      // 替換原來(lái)的 dom node
      oldHostNode.empty();
      oldHostNode.appendChild( nextMarkup )
    }
  }
}

接下來(lái)看下 shouldUpdateReactComponent 方法:

function shouldUpdateReactComponent(prevElement, nextElement) {
  var prevEmpty = prevElement === null || prevElement === false;
  var nextEmpty = nextElement === null || nextElement === false;
  if (prevEmpty || nextEmpty) {
    return prevEmpty === nextEmpty;
  }

  var prevType = typeof prevElement;
  var nextType = typeof nextElement;
  if (prevType === "string" || prevType === "number") {
    return (nextType === "string" || nextType === "number");
  } else {
    return (
      nextType === "object" &&
      prevElement.type === nextElement.type &&
      prevElement.key === nextElement.key
    );
  }
}

基本的思路就是比較當(dāng)前 vdom 節(jié)點(diǎn)的類(lèi)型,如果一致則更新,如果不一致則重新生成一份新的節(jié)點(diǎn)替換掉原來(lái)的。好了回到剛剛跟新 dom node這條路 prevComponentInstance.receiveComponent( nextRenderedElement ),即 render 里面根元素的渲染對(duì)象的 receiveComponent 方法做了最后的更新 dom 的工作。如果根節(jié)點(diǎn)的渲染對(duì)象是組件即 ReactCompositeComponent.receiveComponent,如果根節(jié)點(diǎn)是內(nèi)置對(duì)象(html 元素)節(jié)點(diǎn)即 ReactDOMComponent.receiveComponent。ReactCompositeComponent.receiveComponent 最終還是調(diào)用的上面提到的 updateComponent 循環(huán)去生成 render 中的 vdom,這里就先不深究了。最終 html dom node 的更新策略都在 ReactDOMComponent.receiveComponent 中。

class ReactDOMComponent {
  // @param {nextRenderedElement} 新的 vdom node
  receiveComponent( nextRenderedElement ) {
    var prevElement = this._currentElement;
    this._currentElement = nextRenderedElement;

    var lastProps = prevElement.props;
    var nextProps = this._currentElement.props;
    var lastChildren = lastProps.children;
    var nextChildren = nextProps.children;
    /*
      更新 props
      _updateDOMProperties 方法做了下面兩步
      1. 記錄下 lastProps 中有的,nextProps 沒(méi)有的,刪除
      2. 記錄下 nextProps 中有的,且與 lastProps中不同的屬性,setAttribute 之
    */
    this._updateDOMProperties(lastProps, nextProps, transaction);

    /*
      迭代更新子節(jié)點(diǎn),源代碼中是 this._updateDOMChildren(lastProps, nextProps, transaction, context);
      以下把 _updateDOMChildren 方法展開(kāi),對(duì)于子節(jié)點(diǎn)類(lèi)型的判斷源碼比較復(fù)雜,這里只針對(duì)string|number和非string|number做一個(gè)簡(jiǎn)單的流程示例
    */
    // 1. 如果子節(jié)點(diǎn)從有到無(wú),則刪除子節(jié)點(diǎn)
    if ( lastChildren != null && nextChildren == null ) {
    
      if ( typeof lastChildren === "string" | "number" /* 偽代碼 */ ) {
        this.updateTextContent("");
      } else {
        this.updateChildren( null, transaction, context );
      }
    }
    // 2. 如果新的子節(jié)點(diǎn)相對(duì)于老的是有變化的
    if ( nextChildren != null ) {
      if ( typeof lastChildren === "string" | "number" && lastChildren !== nextChildren /* 偽代碼 */ ) {
        this.updateTextContent("" + nextChildren);
      } else if ( lastChildren !== nextChildren ) {
        this.updateChildren( nextChildren, transaction, context );
      }
    }
  }
}

this.updateChildren( nextChildren, transaction, context ) 中是真正的 diff 算法,就不以代碼來(lái)說(shuō)了(因?yàn)楣饪看a很難說(shuō)明清楚)

先來(lái)看最簡(jiǎn)單的情況:
例A:

按節(jié)點(diǎn)順序開(kāi)始遍歷 nextChildren(遍歷的過(guò)程中記錄下需要對(duì)節(jié)點(diǎn)做哪些變更,等遍歷完統(tǒng)一執(zhí)行最終的 dom 操作),相同位置如果碰到和 prevChildren 中 tag 一樣的元素認(rèn)為不需要對(duì)節(jié)點(diǎn)進(jìn)行刪除,只需要更新節(jié)點(diǎn)的 attr,如果碰到 tag 不一樣,則按照新的 vdom 中的節(jié)點(diǎn)重新生成一個(gè)節(jié)點(diǎn),并把 prevChildren 中相同位置老節(jié)點(diǎn)刪除。按以上兩個(gè)狀態(tài)的 vdom tree,那么遍歷完就會(huì)記錄下需要做兩步 dom 變更:新增一個(gè) span 節(jié)點(diǎn)插入到第二個(gè)位置,刪除原來(lái)第二個(gè)位置上的 div。

再來(lái)看兩個(gè)例子:
例B:

遍歷結(jié)果:第二個(gè)節(jié)點(diǎn)新增一個(gè)span,刪除第二個(gè)div和第四個(gè)div。

例C:

遍歷結(jié)果:第二個(gè)節(jié)點(diǎn)新增一個(gè)span,第四個(gè)節(jié)點(diǎn)新增一個(gè)div,刪除第二個(gè)div。

我們看到對(duì)于例C來(lái)說(shuō)其實(shí)最便利的方法就是把 span 插入到第二的位置上,然后其他div只要做 attr 的更新而不需要再進(jìn)行位置的增刪,如果 attr 都沒(méi)有變化,那么后兩個(gè) div 根本不需要變化。但是按例A里面的算法,我們需要進(jìn)行好幾步的 dom 操作。這是為算法減少時(shí)間復(fù)雜度,做了妥協(xié)。但是 react 對(duì)節(jié)點(diǎn)引入了 key 這個(gè)關(guān)鍵屬性幫助優(yōu)化這種情況。假設(shè)我們給所有節(jié)點(diǎn)都添加了唯一的 key 屬性,如下面例D:
例D:

我們?cè)诒闅v過(guò)程中對(duì)所要記錄的東西進(jìn)行優(yōu)化,在某個(gè)位置碰到有 key 的節(jié)點(diǎn)我們?nèi)?prevChildren 中找有沒(méi)有對(duì)應(yīng)的節(jié)點(diǎn),如果有,則我們會(huì)比較當(dāng)前節(jié)點(diǎn)在前后兩個(gè) tree 中相對(duì)位置。如果相對(duì)位置沒(méi)有變化,則不需要做dom的增刪移,而只需要更新。如果位置不一樣則需要記錄把這個(gè)節(jié)點(diǎn)從老的位置移動(dòng)到新的位置(具體算法需要借助前一次dom變化的記錄這里不詳述)。這樣從例C到例D的優(yōu)化減少了 dom 節(jié)點(diǎn)的增刪。

但是 react 的這種算法的優(yōu)化也帶來(lái)了一種極端的情況:
例E:

遍歷結(jié)果:3次節(jié)點(diǎn)位置移動(dòng):2到1,1到2,0到3。

但是其實(shí)這里只需要更新每個(gè)節(jié)點(diǎn)的 attr,他們的位置根本不需要做變化。所以如果要給元素指定 key 最好避免元素的位置有太多太大的躍遷變化。

基本上 setState 之后到最終的 dom 變化的過(guò)程就是這么結(jié)束了。

后記:
梳理的比較簡(jiǎn)單,很多細(xì)節(jié)我沒(méi)有精力作一一的總結(jié),因?yàn)槲易约嚎丛创a看了好久,代碼中涉及到很多異步,事務(wù)等等干擾項(xiàng),然后我自己又不想過(guò)多的借助現(xiàn)有的資料-_-。當(dāng)我快要把最后一點(diǎn)寫(xiě)完的時(shí)候發(fā)現(xiàn) pure render 專(zhuān)欄的作者陳屹出了一本《深入React技術(shù)棧》里面有相當(dāng)詳細(xì)的源碼分析,所以我感覺(jué)我這篇“白寫(xiě)”了,貼出這本書(shū)就可以了,不過(guò)陳屹的這本書(shū)是良心之作,必須安利下。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/83011.html

相關(guān)文章

  • 玩轉(zhuǎn) React(五)- 組件的內(nèi)部狀態(tài)和生命周期

    摘要:另外本文中會(huì)介紹一個(gè)通過(guò)類(lèi)繼承方式定義的組件的生命周期,以及在各個(gè)生命周期函數(shù)中能做什么,不能或盡量不要做什么。各個(gè)生命周期函數(shù)介紹及使用經(jīng)驗(yàn)。獲取組件的初始內(nèi)部狀態(tài)在中。該聲明周期函數(shù)可能在兩種情況下被調(diào)用組件接收到了新的屬性。 文章標(biāo)題總算是可以正常一點(diǎn)了…… 通過(guò)之前的文章我們已經(jīng)知道:在 React 體系中所謂的 在 JavaScript 中編寫(xiě) HTML 代碼 指的是 Rea...

    Rocture 評(píng)論0 收藏0
  • 玩轉(zhuǎn) React(五)- 組件的內(nèi)部狀態(tài)和生命周期

    摘要:另外本文中會(huì)介紹一個(gè)通過(guò)類(lèi)繼承方式定義的組件的生命周期,以及在各個(gè)生命周期函數(shù)中能做什么,不能或盡量不要做什么。各個(gè)生命周期函數(shù)介紹及使用經(jīng)驗(yàn)。獲取組件的初始內(nèi)部狀態(tài)在中。該聲明周期函數(shù)可能在兩種情況下被調(diào)用組件接收到了新的屬性。 文章標(biāo)題總算是可以正常一點(diǎn)了…… 通過(guò)之前的文章我們已經(jīng)知道:在 React 體系中所謂的 在 JavaScript 中編寫(xiě) HTML 代碼 指的是 Rea...

    ASCH 評(píng)論0 收藏0
  • 淺談MVC,MVP,MVVM漸進(jìn)變化及React與Vue比較

    摘要:將注意力集中保持在核心庫(kù),而將其他功能如路由和全局狀態(tài)管理交給相關(guān)的庫(kù)。此示例使用類(lèi)似的語(yǔ)法,稱(chēng)為。執(zhí)行更快,因?yàn)樗诰幾g為代碼后進(jìn)行了優(yōu)化?;诘哪0迨沟脤⒁延械膽?yīng)用逐步遷移到更為容易。 前言 因?yàn)闆](méi)有明確的界定,這里不討論正確與否,只表達(dá)個(gè)人對(duì)前端MV*架構(gòu)模式理解看法,再比較React和Vue兩種框架不同.寫(xiě)完之后我知道這文章好水,特別是框架對(duì)比部分都是別人說(shuō)爛的,而我也是打算把...

    DrizzleX 評(píng)論0 收藏0
  • React學(xué)習(xí)之認(rèn)識(shí)Flux架構(gòu)模式

    摘要:是用戶(hù)建立客戶(hù)端應(yīng)用的前端架構(gòu),它通過(guò)利用一個(gè)單向的數(shù)據(jù)流補(bǔ)充了的組合視圖組件,這更是一種模式而非正式框架,你能夠無(wú)需許多新代碼情況下立即開(kāi)始使用。結(jié)構(gòu)和數(shù)據(jù)流一個(gè)單向數(shù)據(jù)流是模式的核心。 Flux是Facebook用戶(hù)建立客戶(hù)端Web應(yīng)用的前端架構(gòu),它通過(guò)利用一個(gè)單向的數(shù)據(jù)流補(bǔ)充了React的組合視圖組件,這更是一種模式而非正式框架,你能夠無(wú)需許多新代碼情況下立即開(kāi)始使用Flux。 ...

    EasonTyler 評(píng)論0 收藏0
  • React渲染流程分析Diff算法

    摘要:什么是虛擬在中,執(zhí)行的結(jié)果得到的并不是真正的節(jié)點(diǎn),結(jié)果僅僅是輕量級(jí)的對(duì)象,我們稱(chēng)之為。后來(lái)產(chǎn)出的架構(gòu)模式,期望從代碼組織方式來(lái)降低維護(hù)難度。 1、什么是虛擬DOM 在React中,render執(zhí)行的結(jié)果得到的并不是真正的DOM節(jié)點(diǎn),結(jié)果僅僅是輕量級(jí)的JavaScript對(duì)象,我們稱(chēng)之為virtual DOM。 簡(jiǎn)單的說(shuō),其實(shí)所謂的virtual DOM就是JavaScript對(duì)象到H...

    lykops 評(píng)論0 收藏0
  • React渲染流程分析Diff算法

    摘要:什么是虛擬在中,執(zhí)行的結(jié)果得到的并不是真正的節(jié)點(diǎn),結(jié)果僅僅是輕量級(jí)的對(duì)象,我們稱(chēng)之為。后來(lái)產(chǎn)出的架構(gòu)模式,期望從代碼組織方式來(lái)降低維護(hù)難度。 1、什么是虛擬DOM 在React中,render執(zhí)行的結(jié)果得到的并不是真正的DOM節(jié)點(diǎn),結(jié)果僅僅是輕量級(jí)的JavaScript對(duì)象,我們稱(chēng)之為virtual DOM。 簡(jiǎn)單的說(shuō),其實(shí)所謂的virtual DOM就是JavaScript對(duì)象到H...

    sutaking 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<