React 源碼剖析系列 - 不可思議的 react diff

目前衷快,前端領(lǐng)域中 React 勢頭正盛买喧,使用者眾多卻少有能夠深入剖析內(nèi)部實現(xiàn)機(jī)制和原理业筏。本系列文章希望通過剖析 React 源碼憔杨,理解其內(nèi)部的實現(xiàn)原理,知其然更要知其所以然蒜胖。

React diff 作為 Virtual DOM 的加速器消别,其算法上的改進(jìn)優(yōu)化是 React 整個界面渲染的基礎(chǔ),以及性能提高的保障台谢,同時也是 React 源碼中最神秘寻狂、最不可思議的部分,本文從源碼入手朋沮,深入剖析 React diff 的不可思議之處蛇券。

  • 閱讀本文需要對 React 有一定的了解,如果你不知何為 React,請詳讀 React 官方文檔怀读。

  • 如果你對 React diff 存在些許疑惑诉位,或者你對算法優(yōu)化感興趣,那么本文值得閱讀和討論菜枷。

前言

React 中最值得稱道的部分莫過于 Virtual DOM 與 diff 的完美結(jié)合苍糠,特別是其高效的 diff 算法,讓用戶可以無需顧忌性能問題而”任性自由”的刷新頁面啤誊,讓開發(fā)者也可以無需關(guān)心 Virtual DOM 背后的運(yùn)作原理岳瞭,因為 React diff 會幫助我們計算出 Virtual DOM 中真正變化的部分,并只針對該部分進(jìn)行實際 DOM 操作蚊锹,而非重新渲染整個頁面瞳筏,從而保證了每次操作更新后頁面的高效渲染,因此 Virtual DOM 與 diff 是保證 React 性能口碑的幕后推手牡昆。

行文至此姚炕,可能會有讀者質(zhì)疑:React 無非就是引入 diff 這一概念,且 diff 算法也并非其首創(chuàng)丢烘,何必吹噓的如此天花亂墜呢柱宦?

其實,正是因為 diff 算法的普識度高播瞳,就更應(yīng)該認(rèn)可 React 針對 diff 算法優(yōu)化所做的努力與貢獻(xiàn)掸刊,更能體現(xiàn) React 開發(fā)者們的魅力與智慧!

傳統(tǒng) diff 算法

計算一棵樹形結(jié)構(gòu)轉(zhuǎn)換成另一棵樹形結(jié)構(gòu)的最少操作赢乓,是一個復(fù)雜且值得研究的問題忧侧。傳統(tǒng) diff 算法通過循環(huán)遞歸對節(jié)點(diǎn)進(jìn)行依次對比,效率低下牌芋,算法復(fù)雜度達(dá)到 O(n^3)蚓炬,其中 n 是樹中節(jié)點(diǎn)的總數(shù)。O(n^3) 到底有多可怕姜贡,這意味著如果要展示1000個節(jié)點(diǎn)试吁,就要依次執(zhí)行上十億次的比較。這種指數(shù)型的性能消耗對于前端渲染場景來說代價太高了楼咳!現(xiàn)今的 CPU 每秒鐘能執(zhí)行大約30億條指令熄捍,即便是最高效的實現(xiàn),也不可能在一秒內(nèi)計算出差異情況母怜。

如果 React 只是單純的引入 diff 算法而沒有任何的優(yōu)化改進(jìn)余耽,那么其效率是遠(yuǎn)遠(yuǎn)無法滿足前端渲染所要求的性能。

因此苹熏,想要將 diff 思想引入 Virtual DOM碟贾,就需要設(shè)計一種穩(wěn)定高效的 diff 算法币喧,而 React 做到了!

那么袱耽,React diff 到底是如何實現(xiàn)的呢杀餐?

詳解 React diff

傳統(tǒng) diff 算法的復(fù)雜度為 O(n^3),顯然這是無法滿足性能要求的朱巨。React 通過制定大膽的策略史翘,將 O(n^3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 復(fù)雜度的問題

diff 策略

  1. Web UI 中 DOM 節(jié)點(diǎn)跨層級的移動操作特別少冀续,可以忽略不計琼讽。

  2. 擁有相同類的兩個組件將會生成相似的樹形結(jié)構(gòu),擁有不同類的兩個組件將會生成不同的樹形結(jié)構(gòu)洪唐。

  3. 對于同一層級的一組子節(jié)點(diǎn)钻蹬,它們可以通過唯一 id 進(jìn)行區(qū)分。

基于以上三個前提策略凭需,React 分別對 tree diff问欠、component diff 以及 element diff 進(jìn)行算法優(yōu)化,事實也證明這三個前提策略是合理且準(zhǔn)確的功炮,它保證了整體界面構(gòu)建的性能溅潜。

  • tree diff

  • component diff

  • element diff

本文中源碼 ReactMultiChild.js

tree diff

基于策略一,React 對樹的算法進(jìn)行了簡潔明了的優(yōu)化薪伏,即對樹進(jìn)行分層比較,兩棵樹只會對同一層次的節(jié)點(diǎn)進(jìn)行比較粗仓。

既然 DOM 節(jié)點(diǎn)跨層級的移動操作少到可以忽略不計嫁怀,針對這一現(xiàn)象,React 通過 updateDepth 對 Virtual DOM 樹進(jìn)行層級控制借浊,只會對相同顏色方框內(nèi)的 DOM 節(jié)點(diǎn)進(jìn)行比較塘淑,即同一個父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在蚂斤,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會被完全刪除掉存捺,不會用于進(jìn)一步的比較。這樣只需要對樹進(jìn)行一次遍歷曙蒸,便能完成整個 DOM 樹的比較捌治。


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

分析至此,大部分人可能都存在這樣的疑問:如果出現(xiàn)了 DOM 節(jié)點(diǎn)跨層級的移動操作纽窟,React diff 會有怎樣的表現(xiàn)呢肖油?是的,對此我也好奇不已臂港,不如試驗一番森枪。

如下圖视搏,A 節(jié)點(diǎn)(包括其子節(jié)點(diǎn))整個被移動到 D 節(jié)點(diǎn)下,由于 React 只會簡單的考慮同層級節(jié)點(diǎn)的位置變換县袱,而對于不同層級的節(jié)點(diǎn)浑娜,只有創(chuàng)建和刪除操作。當(dāng)根節(jié)點(diǎn)發(fā)現(xiàn)子節(jié)點(diǎn)中 A 消失了式散,就會直接銷毀 A筋遭;當(dāng) D 發(fā)現(xiàn)多了一個子節(jié)點(diǎn) A,則會創(chuàng)建新的 A(包括子節(jié)點(diǎn))作為其子節(jié)點(diǎn)杂数。此時宛畦,React diff 的執(zhí)行情況:create A -> create B -> create C -> delete A

由此可發(fā)現(xiàn)揍移,當(dāng)出現(xiàn)節(jié)點(diǎn)跨層級移動時次和,并不會出現(xiàn)想象中的移動操作,而是以 A 為根節(jié)點(diǎn)的樹被整個重新創(chuàng)建那伐,這是一種影響 React 性能的操作踏施,因此 React 官方建議不要進(jìn)行 DOM 節(jié)點(diǎn)跨層級的操作

注意:在開發(fā)組件時罕邀,保持穩(wěn)定的 DOM 結(jié)構(gòu)會有助于性能的提升畅形。例如,可以通過 CSS 隱藏或顯示節(jié)點(diǎn)诉探,而不是真的移除或添加 DOM 節(jié)點(diǎn)日熬。


image

component diff

React 是基于組件構(gòu)建應(yīng)用的,對于組件間的比較所采取的策略也是簡潔高效肾胯。

  • 如果是同一類型的組件竖席,按照原策略繼續(xù)比較 virtual DOM tree。

  • 如果不是敬肚,則將該組件判斷為 dirty component毕荐,從而替換整個組件下的所有子節(jié)點(diǎn)。

  • 對于同一類型的組件艳馒,有可能其 Virtual DOM 沒有任何變化憎亚,如果能夠確切的知道這點(diǎn)那可以節(jié)省大量的 diff 運(yùn)算時間,因此 React 允許用戶通過 shouldComponentUpdate() 來判斷該組件是否需要進(jìn)行 diff弄慰。

如下圖第美,當(dāng) component D 改變?yōu)?component G 時,即使這兩個 component 結(jié)構(gòu)相似曹动,一旦 React 判斷 D 和 G 是不同類型的組件斋日,就不會比較二者的結(jié)構(gòu),而是直接刪除 component D墓陈,重新創(chuàng)建 component G 以及其子節(jié)點(diǎn)恶守。雖然當(dāng)兩個 component 是不同類型但結(jié)構(gòu)相似時第献,React diff 會影響性能,但正如 React 官方博客所言:不同類型的 component 是很少存在相似 DOM tree 的機(jī)會兔港,因此這種極端因素很難在實現(xiàn)開發(fā)過程中造成重大影響的庸毫。


image

element diff

當(dāng)節(jié)點(diǎn)處于同一層級時,React diff 提供了三種節(jié)點(diǎn)操作衫樊,分別為:INSERT_MARKUP(插入)飒赃、MOVE_EXISTING(移動)和 REMOVE_NODE(刪除)。

  • INSERT_MARKUP科侈,新的 component 類型不在老集合里载佳, 即是全新的節(jié)點(diǎn),需要對新節(jié)點(diǎn)執(zhí)行插入操作臀栈。

  • MOVE_EXISTING蔫慧,在老集合有新 component 類型,且 element 是可更新的類型权薯,generateComponentChildren 已調(diào)用 receiveComponent姑躲,這種情況下 prevChild=nextChild,就需要做移動操作盟蚣,可以復(fù)用以前的 DOM 節(jié)點(diǎn)黍析。

  • REMOVE_NODE,老 component 類型屎开,在新集合里也有阐枣,但對應(yīng)的 element 不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作奄抽,或者老 component 不在新集合里的侮繁,也需要執(zhí)行刪除操作。

function enqueueInsertMarkup(parentInst, markup, toIndex) {
  updateQueue.push({
    parentInst: parentInst,
    parentNode: null,
    type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
    markupIndex: markupQueue.push(markup) - 1,
    content: null,
    fromIndex: null,
    toIndex: toIndex,
  });
}

function enqueueMove(parentInst, fromIndex, toIndex) {
  updateQueue.push({
    parentInst: parentInst,
    parentNode: null,
    type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
    markupIndex: null,
    content: null,
    fromIndex: fromIndex,
    toIndex: toIndex,
  });
}

function enqueueRemove(parentInst, fromIndex) {
  updateQueue.push({
    parentInst: parentInst,
    parentNode: null,
    type: ReactMultiChildUpdateTypes.REMOVE_NODE,
    markupIndex: null,
    content: null,
    fromIndex: fromIndex,
    toIndex: null,
  });
}

如下圖如孝,老集合中包含節(jié)點(diǎn):A、B娩贷、C第晰、D,更新后的新集合中包含節(jié)點(diǎn):B彬祖、A茁瘦、D、C储笑,此時新老集合進(jìn)行 diff 差異化對比甜熔,發(fā)現(xiàn) B != A,則創(chuàng)建并插入 B 至新集合突倍,刪除老集合 A腔稀;以此類推盆昙,創(chuàng)建并插入 A、D 和 C焊虏,刪除 B淡喜、C 和 D。


image

React 發(fā)現(xiàn)這類操作繁瑣冗余诵闭,因為這些都是相同的節(jié)點(diǎn)炼团,但由于位置發(fā)生變化,導(dǎo)致需要進(jìn)行繁雜低效的刪除疏尿、創(chuàng)建操作瘟芝,其實只要對這些節(jié)點(diǎn)進(jìn)行位置移動即可。

針對這一現(xiàn)象褥琐,React 提出優(yōu)化策略:允許開發(fā)者對同一層級的同組子節(jié)點(diǎn)锌俱,添加唯一 key 進(jìn)行區(qū)分,雖然只是小小的改動踩衩,性能上卻發(fā)生了翻天覆地的變化嚼鹉!

新老集合所包含的節(jié)點(diǎn),如下圖所示驱富,新老集合進(jìn)行 diff 差異化對比锚赤,通過 key 發(fā)現(xiàn)新老集合中的節(jié)點(diǎn)都是相同的節(jié)點(diǎn),因此無需進(jìn)行節(jié)點(diǎn)刪除和創(chuàng)建褐鸥,只需要將老集合中節(jié)點(diǎn)的位置進(jìn)行移動线脚,更新為新集合中節(jié)點(diǎn)的位置,此時 React 給出的 diff 結(jié)果為:B叫榕、D 不做任何操作浑侥,A、C 進(jìn)行移動操作晰绎,即可寓落。


image

那么,如此高效的 diff 到底是如何運(yùn)作的呢荞下?讓我們通過源碼進(jìn)行詳細(xì)分析伶选。

首先對新集合的節(jié)點(diǎn)進(jìn)行循環(huán)遍歷,for (name in nextChildren)尖昏,通過唯一 key 可以判斷新老集合中是否存在相同的節(jié)點(diǎn)仰税,if (prevChild === nextChild),如果存在相同節(jié)點(diǎn)抽诉,則進(jìn)行移動操作陨簇,但在移動前需要將當(dāng)前節(jié)點(diǎn)在老集合中的位置與 lastIndex 進(jìn)行比較,if (child._mountIndex < lastIndex)迹淌,則進(jìn)行節(jié)點(diǎn)移動操作河绽,否則不執(zhí)行該操作己单。這是一種順序優(yōu)化手段,lastIndex 一直在更新葵姥,表示訪問過的節(jié)點(diǎn)在老集合中最右的位置(即最大的位置)荷鼠,如果新集合中當(dāng)前訪問的節(jié)點(diǎn)比 lastIndex 大,說明當(dāng)前訪問節(jié)點(diǎn)在老集合中就比上一個節(jié)點(diǎn)位置靠后榔幸,則該節(jié)點(diǎn)不會影響其他節(jié)點(diǎn)的位置允乐,因此不用添加到差異隊列中,即不執(zhí)行移動操作削咆,只有當(dāng)訪問的節(jié)點(diǎn)比 lastIndex 小時牍疏,才需要進(jìn)行移動操作。

以上圖為例拨齐,可以更為清晰直觀的描述 diff 的差異對比過程:

  • 從新集合中取得 B鳞陨,判斷老集合中存在相同節(jié)點(diǎn) B,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動操作瞻惋,B 在老集合中的位置 B._mountIndex = 1厦滤,此時 lastIndex = 0,不滿足 child._mountIndex < lastIndex 的條件歼狼,因此不對 B 進(jìn)行移動操作掏导;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),其中 prevChild._mountIndex 表示 B 在老集合中的位置羽峰,則 lastIndex = 1趟咆,并將 B 的位置更新為新集合中的位置prevChild._mountIndex = nextIndex,此時新集合中 B._mountIndex = 0梅屉,nextIndex++ 進(jìn)入下一個節(jié)點(diǎn)的判斷值纱。

  • 從新集合中取得 A,判斷老集合中存在相同節(jié)點(diǎn) A坯汤,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動操作虐唠,A 在老集合中的位置 A._mountIndex = 0,此時 lastIndex = 1惰聂,滿足 child._mountIndex < lastIndex的條件凿滤,因此對 A 進(jìn)行移動操作enqueueMove(this, child._mountIndex, toIndex),其中 toIndex 其實就是 nextIndex庶近,表示 A 需要移動到的位置;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex)眷蚓,則 lastIndex = 1鼻种,并將 A 的位置更新為新集合中的位置 prevChild._mountIndex = nextIndex,此時新集合中A._mountIndex = 1沙热,nextIndex++ 進(jìn)入下一個節(jié)點(diǎn)的判斷叉钥。

  • 從新集合中取得 D罢缸,判斷老集合中存在相同節(jié)點(diǎn) D,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動操作投队,D 在老集合中的位置 D._mountIndex = 3枫疆,此時 lastIndex = 1,不滿足 child._mountIndex < lastIndex的條件敷鸦,因此不對 D 進(jìn)行移動操作息楔;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),則 lastIndex = 3扒披,并將 D 的位置更新為新集合中的位置 prevChild._mountIndex = nextIndex值依,此時新集合中D._mountIndex = 2,nextIndex++ 進(jìn)入下一個節(jié)點(diǎn)的判斷碟案。

  • 從新集合中取得 C愿险,判斷老集合中存在相同節(jié)點(diǎn) C,通過對比節(jié)點(diǎn)位置判斷是否進(jìn)行移動操作价说,C 在老集合中的位置 C._mountIndex = 2辆亏,此時 lastIndex = 3,滿足 child._mountIndex < lastIndex 的條件鳖目,因此對 C 進(jìn)行移動操作 enqueueMove(this, child._mountIndex, toIndex)扮叨;更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex),則 lastIndex = 3疑苔,并將 C 的位置更新為新集合中的位置 prevChild._mountIndex = nextIndex甫匹,此時新集合中 C._mountIndex = 3,nextIndex++ 進(jìn)入下一個節(jié)點(diǎn)的判斷惦费,由于 C 已經(jīng)是最后一個節(jié)點(diǎn)兵迅,因此 diff 到此完成。

以上主要分析新老集合中存在相同節(jié)點(diǎn)但位置不同時薪贫,對節(jié)點(diǎn)進(jìn)行位置移動的情況恍箭,如果新集合中有新加入的節(jié)點(diǎn)且老集合存在需要刪除的節(jié)點(diǎn),那么 React diff 又是如何對比運(yùn)作的呢瞧省?

以下圖為例:

  • 從新集合中取得 B扯夭,判斷老集合中存在相同節(jié)點(diǎn) B,由于 B 在老集合中的位置 B._mountIndex = 1鞍匾,此時lastIndex = 0交洗,因此不對 B 進(jìn)行移動操作;更新 lastIndex = 1橡淑,并將 B 的位置更新為新集合中的位置B._mountIndex = 0构拳,nextIndex++進(jìn)入下一個節(jié)點(diǎn)的判斷。

  • 從新集合中取得 E,判斷老集合中不存在相同節(jié)點(diǎn) E置森,則創(chuàng)建新節(jié)點(diǎn) E斗埂;更新 lastIndex = 1,并將 E 的位置更新為新集合中的位置凫海,nextIndex++進(jìn)入下一個節(jié)點(diǎn)的判斷呛凶。

  • 從新集合中取得 C,判斷老集合中存在相同節(jié)點(diǎn) C行贪,由于 C 在老集合中的位置C._mountIndex = 2漾稀,lastIndex = 1,此時 C._mountIndex > lastIndex瓮顽,因此不對 C 進(jìn)行移動操作县好;更新 lastIndex = 2,并將 C 的位置更新為新集合中的位置暖混,nextIndex++ 進(jìn)入下一個節(jié)點(diǎn)的判斷缕贡。

  • 從新集合中取得 A,判斷老集合中存在相同節(jié)點(diǎn) A拣播,由于 A 在老集合中的位置A._mountIndex = 0晾咪,lastIndex = 2,此時 A._mountIndex < lastIndex贮配,因此對 A 進(jìn)行移動操作谍倦;更新 lastIndex = 2,并將 A 的位置更新為新集合中的位置泪勒,nextIndex++ 進(jìn)入下一個節(jié)點(diǎn)的判斷昼蛀。

  • 當(dāng)完成新集合中所有節(jié)點(diǎn) diff 時,最后還需要對老集合進(jìn)行循環(huán)遍歷圆存,判斷是否存在新集合中沒有但老集合中仍存在的節(jié)點(diǎn)叼旋,發(fā)現(xiàn)存在這樣的節(jié)點(diǎn) D,因此刪除節(jié)點(diǎn) D沦辙,到此 diff 全部完成夫植。

image
_updateChildren: function(nextNestedChildrenElements, transaction, context) {
  var prevChildren = this._renderedChildren;
  var nextChildren = this._reconcilerUpdateChildren(
    prevChildren, nextNestedChildrenElements, transaction, context
  );
  if (!nextChildren && !prevChildren) {
    return;
  }
  var name;
  var lastIndex = 0;
  var nextIndex = 0;
  for (name in nextChildren) {
    if (!nextChildren.hasOwnProperty(name)) {
      continue;
    }
    var prevChild = prevChildren && prevChildren[name];
    var nextChild = nextChildren[name];
    if (prevChild === nextChild) {
      // 移動節(jié)點(diǎn)
      this.moveChild(prevChild, nextIndex, lastIndex);
      lastIndex = Math.max(prevChild._mountIndex, lastIndex);
      prevChild._mountIndex = nextIndex;
    } else {
      if (prevChild) {
        lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        // 刪除節(jié)點(diǎn)
        this._unmountChild(prevChild);
      }
      // 初始化并創(chuàng)建節(jié)點(diǎn)
      this._mountChildAtIndex(
        nextChild, nextIndex, transaction, context
      );
    }
    nextIndex++;
  }
  for (name in prevChildren) {
    if (prevChildren.hasOwnProperty(name) &&
        !(nextChildren && nextChildren.hasOwnProperty(name))) {
      this._unmountChild(prevChildren[name]);
    }
  }
  this._renderedChildren = nextChildren;
},
// 移動節(jié)點(diǎn)
moveChild: function(child, toIndex, lastIndex) {
  if (child._mountIndex < lastIndex) {
    this.prepareToManageChildren();
    enqueueMove(this, child._mountIndex, toIndex);
  }
},
// 創(chuàng)建節(jié)點(diǎn)
createChild: function(child, mountImage) {
  this.prepareToManageChildren();
  enqueueInsertMarkup(this, mountImage, child._mountIndex);
},
// 刪除節(jié)點(diǎn)
removeChild: function(child) {
  this.prepareToManageChildren();
  enqueueRemove(this, child._mountIndex);
},

_unmountChild: function(child) {
  this.removeChild(child);
  child._mountIndex = null;
},

_mountChildAtIndex: function(
  child,
  index,
  transaction,
  context) {
  var mountImage = ReactReconciler.mountComponent(
    child,
    transaction,
    this,
    this._nativeContainerInfo,
    context
  );
  child._mountIndex = index;
  this.createChild(child, mountImage);
},

當(dāng)然,React diff 還是存在些許不足與待優(yōu)化的地方油讯,如下圖所示详民,若新集合的節(jié)點(diǎn)更新為:D、A陌兑、B沈跨、C,與老集合對比只有 D 節(jié)點(diǎn)移動兔综,而 A谒出、B隅俘、C 仍然保持原有的順序,理論上 diff 應(yīng)該只需對 D 執(zhí)行移動操作笤喳,然而由于 D 在老集合的位置是最大的,導(dǎo)致其他節(jié)點(diǎn)的 _mountIndex < lastIndex碌宴,造成 D 沒有執(zhí)行移動操作杀狡,而是 A、B贰镣、C 全部移動到 D 節(jié)點(diǎn)后面的現(xiàn)象呜象。

在此,讀者們可以討論思考:如何優(yōu)化上述問題碑隆?

建議:在開發(fā)過程中恭陡,盡量減少類似將最后一個節(jié)點(diǎn)移動到列表首部的操作,當(dāng)節(jié)點(diǎn)數(shù)量過大或更新操作過于頻繁時上煤,在一定程度上會影響 React 的渲染性能休玩。


image

總結(jié)

  • React 通過制定大膽的 diff 策略,將 O(n3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 復(fù)雜度的問題劫狠;

  • React 通過分層求異的策略拴疤,對 tree diff 進(jìn)行算法優(yōu)化;

  • React 通過相同類生成相似樹形結(jié)構(gòu)独泞,不同類生成不同樹形結(jié)構(gòu)的策略呐矾,對 component diff 進(jìn)行算法優(yōu)化;

  • React 通過設(shè)置唯一 key的策略懦砂,對 element diff 進(jìn)行算法優(yōu)化蜒犯;

  • 建議,在開發(fā)組件時荞膘,保持穩(wěn)定的 DOM 結(jié)構(gòu)會有助于性能的提升罚随;

  • 建議,在開發(fā)過程中衫画,盡量減少類似將最后一個節(jié)點(diǎn)移動到列表首部的操作毫炉,當(dāng)節(jié)點(diǎn)數(shù)量過大或更新操作過于頻繁時,在一定程度上會影響 React 的渲染性能削罩。

參考資料

如果本文能夠為你解決些許關(guān)于 React diff 算法的疑惑瞄勾,請點(diǎn)個贊吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弥激,一起剝皮案震驚了整個濱河市进陡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌微服,老刑警劉巖趾疚,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡糙麦,警方通過查閱死者的電腦和手機(jī)辛孵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赡磅,“玉大人魄缚,你說我怎么就攤上這事》倮龋” “怎么了冶匹?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咆瘟。 經(jīng)常有香客問我嚼隘,道長,這世上最難降的妖魔是什么袒餐? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任飞蛹,我火速辦了婚禮,結(jié)果婚禮上匿乃,老公的妹妹穿的比我還像新娘桩皿。我一直安慰自己,他們只是感情好幢炸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布泄隔。 她就那樣靜靜地躺著,像睡著了一般宛徊。 火紅的嫁衣襯著肌膚如雪佛嬉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天闸天,我揣著相機(jī)與錄音暖呕,去河邊找鬼。 笑死苞氮,一個胖子當(dāng)著我的面吹牛湾揽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播笼吟,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼库物,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贷帮?” 一聲冷哼從身側(cè)響起戚揭,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撵枢,沒想到半個月后民晒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體精居,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年潜必,在試婚紗的時候發(fā)現(xiàn)自己被綠了靴姿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡磁滚,死狀恐怖空猜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恨旱,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布坝疼,位于F島的核電站搜贤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钝凶。R本人自食惡果不足惜仪芒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耕陷。 院中可真熱鬧掂名,春花似錦、人聲如沸哟沫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗜诀。三九已至猾警,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隆敢,已是汗流浹背发皿。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拂蝎,地道東北人穴墅。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像温自,于是被迫代替她去往敵國和親玄货。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345