欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React diff算法原理詳細(xì)分析

 更新時(shí)間:2022年11月17日 08:50:09   作者:xiaofeng123aazz  
經(jīng)典的diff算法中,將一棵樹(shù)轉(zhuǎn)為另一棵樹(shù)的最低時(shí)間復(fù)雜度為 O(n^3),其中n為樹(shù)種節(jié)點(diǎn)的個(gè)數(shù)。假如采用這種diff算法,一個(gè)應(yīng)用有1000個(gè)節(jié)點(diǎn)的情況下,需要比較十億次才能將dom樹(shù)更新完成,顯然這個(gè)性能是無(wú)法讓人接受的

拋磚引玉

React通過(guò)引入Virtual DOM的概念,極大地避免無(wú)效的Dom操作,已使我們的頁(yè)面的構(gòu)建效率提到了極大的提升。但是如何高效地通過(guò)對(duì)比新舊Virtual DOM來(lái)找出真正的Dom變化之處同樣也決定著頁(yè)面的性能,React用其特殊的diff算法解決這個(gè)問(wèn)題。Virtual DOM+React diff的組合極大地保障了React的性能,使其在業(yè)界有著不錯(cuò)的性能口碑。diff算法并非React首創(chuàng),React只是對(duì)diff算法做了一個(gè)優(yōu)化,但卻是因?yàn)檫@個(gè)優(yōu)化,給React帶來(lái)了極大的性能提升,不禁讓人感嘆React創(chuàng)造者們的智慧!接下來(lái)我們就探究一下React的diff算法。

傳統(tǒng)diff算法

在文章開(kāi)頭我們提到React的diff算法給React帶來(lái)了極大的性能提升,而之前的React diff算法是在傳統(tǒng)diff算法上的優(yōu)化。下面我們先看一下傳統(tǒng)的diff算法是什么樣子的。

傳統(tǒng)diff算法通過(guò)循環(huán)遞歸對(duì)節(jié)點(diǎn)進(jìn)行依次對(duì)比,效率低下,算法復(fù)雜度達(dá)到 O(n^3),其中 n 是樹(shù)中節(jié)點(diǎn)的總數(shù)。

O(n^3) 到底有多可怕呢?這意味著如果要展示 1000 個(gè)節(jié)點(diǎn),就要依次執(zhí)行上十億次 的比較,這種指數(shù)型的性能消耗對(duì)于前端渲染場(chǎng)景來(lái)說(shuō)代價(jià)太高了。而React卻這個(gè)diff算法時(shí)間復(fù)雜度從O(n3)降到O(n)。O(n3)到O(n)的提升有多大,我們通過(guò)一張圖來(lái)看一下。

從上面這張圖來(lái)看,React的diff算法所帶來(lái)的提升無(wú)疑是巨大無(wú)比的。接下來(lái)我們?cè)倏匆粡垐D:

從1979到2011,30多年的時(shí)間,才將時(shí)間復(fù)雜度搞到O(n^3),而React從開(kāi)源到現(xiàn)在不過(guò)區(qū)區(qū)幾年的時(shí)間,卻一下子干到O(n),到這里不禁再次膜拜一下React的創(chuàng)造者們。

那么React這個(gè)diff算法是如何做到的呢?

React diff原理

前面我們講到傳統(tǒng)diff算法的時(shí)間復(fù)雜度為O(n3),其中n為樹(shù)中節(jié)點(diǎn)的總數(shù),隨著n的增加,diff所耗費(fèi)的時(shí)間將呈現(xiàn)爆炸性的增長(zhǎng)。react卻利用其特殊的diff算法做到了O(n3)到O(n)的飛躍性的提升,而完成這一壯舉的法寶就是下面這三條看似簡(jiǎn)單的diff策略:

  • Web UI中DOM節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少,可以忽略不計(jì)。
  • 擁有相同類(lèi)的兩個(gè)組件將會(huì)生成相似的樹(shù)形結(jié)構(gòu),擁有不同類(lèi)的兩個(gè)組件將會(huì)生成不同的樹(shù)形結(jié)構(gòu)。
  • 對(duì)于同一層級(jí)的一組子節(jié)點(diǎn),它們可以通過(guò)唯一 id 進(jìn)行區(qū)分。

在上面三個(gè)策略的基礎(chǔ)上,React 分別將對(duì)應(yīng)的tree diff、component diff 以及 element diff 進(jìn)行算法優(yōu)化,極大地提升了diff效率。

tree diff

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

既然 DOM 節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作少到可以忽略不計(jì),針對(duì)這一現(xiàn)象,React只會(huì)對(duì)相同層級(jí)的 DOM 節(jié)點(diǎn)進(jìn)行比較,即同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在時(shí),則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉,不會(huì)用于進(jìn)一步的比較。這樣只需要對(duì)樹(shù)進(jìn)行一次遍歷,便能完成整個(gè) DOM 樹(shù)的比較。

策略一的前提是Web UI中DOM節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少,但并沒(méi)有否定DOM節(jié)點(diǎn)跨層級(jí)的操作的存在,那么當(dāng)遇到這種操作時(shí),React是如何處理的呢?

接下來(lái)我們通過(guò)一張圖來(lái)展示整個(gè)處理過(guò)程:

A 節(jié)點(diǎn)(包括其子節(jié)點(diǎn))整個(gè)被移動(dòng)到 D 節(jié)點(diǎn)下,由于 React 只會(huì)簡(jiǎn)單地考慮同層級(jí)節(jié)點(diǎn)的位置變換,而對(duì)于不 同層級(jí)的節(jié)點(diǎn),只有創(chuàng)建和刪除操作。當(dāng)根節(jié)點(diǎn)發(fā)現(xiàn)子節(jié)點(diǎn)中 A 消失了,就會(huì)直接銷(xiāo)毀 A;當(dāng) D 發(fā)現(xiàn)多了一個(gè)子節(jié)點(diǎn) A,則會(huì)創(chuàng) 建新的 A(包括子節(jié)點(diǎn))作為其子節(jié)點(diǎn)。此時(shí),diff 的執(zhí)行情況:create A → create B → create C → delete A。

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

在開(kāi)發(fā)組件時(shí),保持穩(wěn)定的 DOM 結(jié)構(gòu)會(huì)有助于性能的提升。例如,可以通過(guò) CSS 隱藏或顯示節(jié)點(diǎn),而不是真正地移

除或添加 DOM 節(jié)點(diǎn)。

component diff

React 是基于組件構(gòu)建應(yīng)用的,對(duì)于組件間的比較所采取的策略也是非常簡(jiǎn)潔、高效的。

如果是同一類(lèi)型的組件,按照原策略繼續(xù)比較 Virtual DOM 樹(shù)即可。

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

對(duì)于同一類(lèi)型的組件,有可能其 Virtual DOM 沒(méi)有任何變化,如果能夠確切知道這點(diǎn),那么就可以節(jié)省大量的 diff 運(yùn)算時(shí)間。因此,React允許用戶(hù)通過(guò)shouldComponentUpdate()來(lái)判斷該組件是否需進(jìn)行diff算法分析,但是如果調(diào)用了forceUpdate方法,shouldComponentUpdate則失效。

參考React實(shí)戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)

接下來(lái)我們看下面這個(gè)例子是如何實(shí)現(xiàn)轉(zhuǎn)換的:

轉(zhuǎn)換流程如下:

當(dāng)組件D變?yōu)榻M件G時(shí),即使這兩個(gè)組件結(jié)構(gòu)相似,一旦React判斷D和G是不同類(lèi)型的組件,就不會(huì)比較二 者的結(jié)構(gòu),而是直接刪除組件D,重新創(chuàng)建組件G及其子節(jié)點(diǎn)。雖然當(dāng)兩個(gè)組件是不同類(lèi)型但結(jié)構(gòu)相似時(shí),diff會(huì)影響性能,但正如React官方博客所言:不同類(lèi)型的組件很少存在相似DOM樹(shù)的情況,因此這種極端因素很難在實(shí)際開(kāi)發(fā)過(guò)程中造成重大的影響。

element diff

當(dāng)節(jié)點(diǎn)處于同一層級(jí)時(shí),diff 提供了 3 種節(jié)點(diǎn)操作,分別為 INSERT_MARKUP (插入)、MOVE_EXISTING (移動(dòng))和 REMOVE_NODE (刪除)。

INSERT_MARKUP :新的組件類(lèi)型不在舊集合里,即全新的節(jié)點(diǎn),需要對(duì)新節(jié)點(diǎn)執(zhí)行插入操作。

MOVE_EXISTING :舊集合中有新組件類(lèi)型,且 element 是可更新的類(lèi)型,generateComponentChildren 已調(diào)用

receiveComponent ,這種情況下 prevChild=nextChild ,就需要做移動(dòng)操作,可以復(fù)用以前的 DOM 節(jié)點(diǎn)。

REMOVE_NODE :舊組件類(lèi)型,在新集合里也有,但對(duì)應(yīng)的 element 不同則不能直接復(fù)用和更新,需要執(zhí)行刪除操作,或者舊組件不在新集合里的,也需要執(zhí)行刪除操作。

舊集合中包含節(jié)點(diǎn)A、B、C和D,更新后的新集合中包含節(jié)點(diǎn)B、A、D和C,此時(shí)新舊集合進(jìn)行diff差異化對(duì)比,發(fā)現(xiàn)B!=A,則創(chuàng)建并插入B至新集合,刪除舊集合A;以此類(lèi)推,創(chuàng)建并插入A、D和C,刪除B、C和D。

我們發(fā)現(xiàn)這些都是相同的節(jié)點(diǎn),僅僅是位置發(fā)生了變化,但卻需要進(jìn)行繁雜低效的刪除、創(chuàng)建操作,其實(shí)只要對(duì)這些節(jié)點(diǎn)進(jìn)行位置移動(dòng)即可。React針對(duì)這一現(xiàn)象提出了一種優(yōu)化策略:允許開(kāi)發(fā)者對(duì)同一層級(jí)的同組子節(jié)點(diǎn),添加唯一 key 進(jìn)行區(qū)分。 雖然只是小小的改動(dòng),性能上卻發(fā)生了翻天覆地的變化!我們?cè)賮?lái)看一下應(yīng)用了這個(gè)策略之后,react diff是如何操作的。

通過(guò)key可以準(zhǔn)確地發(fā)現(xiàn)新舊集合中的節(jié)點(diǎn)都是相同的節(jié)點(diǎn),因此無(wú)需進(jìn)行節(jié)點(diǎn)刪除和創(chuàng)建,只需要將舊集合中節(jié)點(diǎn)的位置進(jìn)行移動(dòng),更新為新集合中節(jié)點(diǎn)的位置,此時(shí)React 給出的diff結(jié)果為:B、D不做任何操作,A、C進(jìn)行移動(dòng)操作即可。

具體的流程我們用一張表格來(lái)展現(xiàn)一下:

index節(jié)點(diǎn)oldIndexmaxIndex操作
0B10oldIndex(1)>maxIndex(0),maxIndex=oldIndex,maxIndex變?yōu)?
1A01oldIndex(0)<maxIndex(1),節(jié)點(diǎn)A移動(dòng)至index(1)的位置
2D31oldIndex(3)>maxIndex(1),maxIndex=oldIndex,maxIndex變?yōu)?
3C23oldIndex(2)<maxIndex(3),節(jié)點(diǎn)C移動(dòng)至index(3)的位置
  • index: 新集合的遍歷下標(biāo)。
  • oldIndex:當(dāng)前節(jié)點(diǎn)在老集合中的下標(biāo)。
  • maxIndex:在新集合訪問(wèn)過(guò)的節(jié)點(diǎn)中,其在老集合的最大下標(biāo)值。

操作一欄中只比較oldIndex和maxIndex:

  • 當(dāng)oldIndex>maxIndex時(shí),將oldIndex的值賦值給maxIndex
  • 當(dāng)oldIndex=maxIndex時(shí),不操作
  • 當(dāng)oldIndex<maxIndex時(shí),將當(dāng)前節(jié)點(diǎn)移動(dòng)到index的位置

上面的例子僅僅是在新舊集合中的節(jié)點(diǎn)都是相同的節(jié)點(diǎn)的情況下,那如果新集合中有新加入的節(jié)點(diǎn)且舊集合存在 需要?jiǎng)h除的節(jié)點(diǎn),那么 diff 又是如何對(duì)比運(yùn)作的呢?

index節(jié)點(diǎn)oldIndexmaxIndex操作
0B10oldIndex(1)>maxIndex(0),maxIndex=oldIndex,maxIndex變?yōu)?
1E-1oldIndex不存在,添加節(jié)點(diǎn)E至index(1)的位置
2C21oldIndex(2)>maxIndex(1),maxIndex=oldIndex,maxIndex變?yōu)?
3A02oldIndex(0)<maxIndex(2),節(jié)點(diǎn)A移動(dòng)至index(3)的位置

注:最后還需要對(duì)舊集合進(jìn)行循環(huán)遍歷,找出新集合中沒(méi)有的節(jié)點(diǎn),此時(shí)發(fā)現(xiàn)存在這樣的節(jié)點(diǎn)D,因此刪除節(jié)點(diǎn)D,到此 diff 操作全部完成。

同樣操作一欄中只比較oldIndex和maxIndex,但是oldIndex可能有不存在的情況:

oldIndex存在

  • 當(dāng)oldIndex>maxIndex時(shí),將oldIndex的值賦值給maxIndex
  • 當(dāng)oldIndex=maxIndex時(shí),不操作
  • 當(dāng)oldIndex<maxIndex時(shí),將當(dāng)前節(jié)點(diǎn)移動(dòng)到index的位置

oldIndex不存在

新增當(dāng)前節(jié)點(diǎn)至index的位置

當(dāng)然這種diff并非完美無(wú)缺的,我們來(lái)看這么一種情況:

實(shí)際我們只需對(duì)D執(zhí)行移動(dòng)操作,然而由于D在舊集合中的位置是最大的,導(dǎo)致其他節(jié)點(diǎn)的oldIndex < maxIndex,造成D沒(méi)有執(zhí)行移動(dòng)操作,而是A、B、C全部移動(dòng)到D節(jié)點(diǎn)后面的現(xiàn)象。針對(duì)這種情況,官方建議:

在開(kāi)發(fā)過(guò)程中,盡量減少類(lèi)似將最后一個(gè)節(jié)點(diǎn)移動(dòng)到列表首部的操作。當(dāng)節(jié)點(diǎn)數(shù)量過(guò)大或更新操作過(guò)于頻繁時(shí),這在一定程度上會(huì)影響React的渲染性能。

由于key的存在,react可以準(zhǔn)確地判斷出該節(jié)點(diǎn)在新集合中是否存在,這極大地提高了diff效率。我們?cè)陂_(kāi)發(fā)過(guò)中進(jìn)行列表渲染的時(shí)候,若沒(méi)有加key,react會(huì)拋出警告要求開(kāi)發(fā)者加上key,就是為了提高diff效率。但是加了key一定要比沒(méi)加key的性能更高嗎?我們?cè)賮?lái)看一個(gè)例子:

現(xiàn)在有一集合[1,2,3,4,5],渲染成如下的樣子:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
---------------
現(xiàn)在我們將這個(gè)集合的順序打亂變成[1,3,2,5,4]。
1.加key
<div key='1'>1</div>             <div key='1'>1</div>     
<div key='2'>2</div>             <div key='3'>3</div>  
<div key='3'>3</div>  ========>  <div key='2'>2</div>  
<div key='4'>4</div>             <div key='5'>5</div>  
<div key='5'>5</div>             <div key='4'>4</div>  
操作:節(jié)點(diǎn)2移動(dòng)至下標(biāo)為2的位置,節(jié)點(diǎn)4移動(dòng)至下標(biāo)為4的位置。

2.不加key
<div>1</div>             <div>1</div>     
<div>2</div>             <div>3</div>  
<div>3</div>  ========>  <div>2</div>  
<div>4</div>             <div>5</div>  
<div>5</div>             <div>4</div>  
操作:修改第1個(gè)到第5個(gè)節(jié)點(diǎn)的innerText
---------------
如果我們對(duì)這個(gè)集合進(jìn)行增刪的操作改成[1,3,2,5,6]。
1.加key
<div key='1'>1</div>             <div key='1'>1</div>     
<div key='2'>2</div>             <div key='3'>3</div>  
<div key='3'>3</div>  ========>  <div key='2'>2</div>  
<div key='4'>4</div>             <div key='5'>5</div>  
<div key='5'>5</div>             <div key='6'>6</div>  
操作:節(jié)點(diǎn)2移動(dòng)至下標(biāo)為2的位置,新增節(jié)點(diǎn)6至下標(biāo)為4的位置,刪除節(jié)點(diǎn)4。

2.不加key
<div>1</div>             <div>1</div>     
<div>2</div>             <div>3</div>  
<div>3</div>  ========>  <div>2</div>  
<div>4</div>             <div>5</div>  
<div>5</div>             <div>6</div> 
操作:修改第1個(gè)到第5個(gè)節(jié)點(diǎn)的innerText
---------------
通過(guò)上面這兩個(gè)例子我們發(fā)現(xiàn):
由于dom節(jié)點(diǎn)的移動(dòng)操作開(kāi)銷(xiāo)是比較昂貴的,沒(méi)有key的情況下要比有key的性能更好。

通過(guò)上面的例子我們發(fā)現(xiàn),雖然加了key提高了diff效率,但是未必一定提升了頁(yè)面的性能。因此我們要注意這么一點(diǎn):

對(duì)于簡(jiǎn)單列表頁(yè)渲染來(lái)說(shuō),不加key要比加了key的性能更好

根據(jù)上面的情況,最后我們總結(jié)一下key的作用:

  • 準(zhǔn)確判斷出當(dāng)前節(jié)點(diǎn)是否在舊集合中
  • 極大地減少遍歷次數(shù)

應(yīng)用實(shí)踐

頁(yè)面指定區(qū)域刷新

現(xiàn)在有這么一個(gè)需求,當(dāng)用戶(hù)身份變化時(shí),當(dāng)前頁(yè)面重新加載數(shù)據(jù)。猛一看過(guò)去覺(jué)得非常簡(jiǎn)單,沒(méi)啥難度的,只要在componentDidUpdate這個(gè)生命周期里去判斷用戶(hù)身份是否發(fā)生改變,如果發(fā)生改變就重新請(qǐng)求數(shù)據(jù),于是就有了以下這一段代碼:

import React from 'react';
import {connect} from 'react-redux';
let oldAuthType = '';//用來(lái)存儲(chǔ)舊的用戶(hù)身份
@connect(
  state=>state.user
)
class Page1 extends React.PureComponent{
  state={
    loading:true
  }
  loadMainData(){
    //這里采用了定時(shí)器去模擬數(shù)據(jù)請(qǐng)求
    this.setState({
      loading:true
    });
    const timer = setTimeout(()=>{
      this.setState({
        loading:false
      });
      clearTimeout(timer);
    },2000);
  }
  componentDidUpdate(){
    const {authType} = this.props;
    //判斷當(dāng)前用戶(hù)身份是否發(fā)生了改變
    if(authType!==oldAuthType){
      //存儲(chǔ)新的用戶(hù)身份
      oldAuthType=authType;
      //重新加載數(shù)據(jù)
      this.loadMainData();
    }
  }
  componentDidMount(){
    oldAuthType=this.props.authType;
    this.loadMainData();
  }
  render(){
    const {loading} = this.state;
    return (
      <h2>{`頁(yè)面1${loading?'加載中...':'加載完成'}`}</h2>
    )
  }
}
export default Page1;

看上去我們僅僅通過(guò)加上一段代碼就完成了這一需求,但是當(dāng)我們頁(yè)面是幾十個(gè)的時(shí)候,那這種方法就顯得捉襟見(jiàn)肘了。哪有沒(méi)有一個(gè)很好的方法來(lái)實(shí)現(xiàn)這個(gè)需求呢?其實(shí)很簡(jiǎn)單,利用react diff的特性就可以實(shí)現(xiàn)它。對(duì)于這個(gè)需求,實(shí)際上就是希望當(dāng)前組件可以銷(xiāo)毀在重新生成,那怎么才能讓其銷(xiāo)毀并重新生成呢?通過(guò)上面的總結(jié)我發(fā)現(xiàn)兩種情況,可以實(shí)現(xiàn)組件的銷(xiāo)毀并重新生成。

當(dāng)組件類(lèi)型發(fā)生改變當(dāng)key值發(fā)生變化

接下來(lái)我們就結(jié)合這兩個(gè)特點(diǎn),用兩種方法去實(shí)現(xiàn)。

第一種:引入一個(gè)loading組件。切換身份時(shí)設(shè)置loading為true,此時(shí)loading組件顯示;切換身份完成,loading變?yōu)閒alse,其子節(jié)點(diǎn)children顯示。

<div className="g-main">{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->loading?<Loading/>:children}</div> 

第二種:在刷新區(qū)域加上一個(gè)key值就可以了,用戶(hù)身份一改變,key值就發(fā)生改變。

<div className="g-main" key={authType}>{children}</div>

第一種和第二種取舍上,個(gè)人建議的是這樣子的:

如果需要請(qǐng)求服務(wù)器的,用第一種,因?yàn)檎?qǐng)求服務(wù)器會(huì)有一定等待時(shí)間,加入loading組件可以讓用戶(hù)有感知,體驗(yàn)更好。如果是不需要請(qǐng)求服務(wù)器的情況下,選用第二種,因?yàn)榈诙N更簡(jiǎn)單實(shí)用。

更加方便地監(jiān)聽(tīng)props改變

針對(duì)這個(gè)需求,我們喜歡將搜索條件封裝成一個(gè)組件,查詢(xún)列表封裝成一個(gè)組件。其中查詢(xún)列表會(huì)接收一個(gè)查詢(xún)參數(shù)的屬性,如下所示:

import React from 'react';
import {Card} from 'antd';
import Filter from './components/filter';
import Teacher from './components/teacher';
export default class Demo2 extends React.PureComponent{
  state={
    filters:{
      name:undefined,
      height:undefined,
      age:undefined
    }
  }
  handleFilterChange=(filters)=>{
    this.setState({
      filters
    });
  }
  render(){
    const {filters} = this.state;
    return <Card>
      {/* 過(guò)濾器 */}      <Filter onChange={this.handleFilterChange}/>       {/* 查詢(xún)列表 */}      <Teacher filters={filters}/>
    </Card>
  }
}

現(xiàn)在我們面臨一個(gè)問(wèn)題,如何在組件Teacher中監(jiān)聽(tīng)filters的變化,由于filters是一個(gè)引用類(lèi)型,想監(jiān)聽(tīng)其變化變得有些復(fù)雜,好在lodash提供了比較兩個(gè)對(duì)象的工具方法,使其簡(jiǎn)單了。但是如果后期給Teacher加了額外的props,此時(shí)你要監(jiān)聽(tīng)多個(gè)props的變化時(shí),你的代碼將變得比較難以維護(hù)。針對(duì)這個(gè)問(wèn)題,我們依舊可以通過(guò)key值去實(shí)現(xiàn),當(dāng)每次搜索時(shí),重新生成一個(gè)key,那么Teacher組件就會(huì)重新加載了。代碼如下:

import React from 'react';
import {Card} from 'antd';
import Filter from './components/filter';
import Teacher from './components/teacher';
export default class Demo2 extends React.PureComponent{
  state={
    filters:{
      name:undefined,
      height:undefined,
      age:undefined
    },
    tableKey:this.createTableKey()
  }
  createTableKey(){
    return Math.random().toString(36).substring(7);
  }
  handleFilterChange=(filters)=>{
    this.setState({
      filters,
      //重新生成tableKey
      tableKey:this.createTableKey()
    });
  }
  render(){
    const {filters,tableKey} = this.state;
    return <Card>
      {/* 過(guò)濾器 */}      <Filter onChange={this.handleFilterChange}/>       {/* 查詢(xún)列表 */}      <Teacher key={tableKey} filters={filters}/>
    </Card>
  }
}

即使后期給Teacher加入新的props,也沒(méi)有問(wèn)題,只需拼接一下key即可:

 <Teacher key={`${tableKey}-${prop1}-${prop2}`} filters={filters} prop1={prop1} prop2={prop2}/>

react-router中Link問(wèn)題

先看一下demo代碼:

import React from 'react';
import {Card,Spin,Divider,Row,Col} from 'antd';
import {Link} from 'react-router-dom';
const bookList = [{
  bookId:'1',
  bookName:'三國(guó)演義',
  author:'羅貫中'
},{
  bookId:'2',
  bookName:'水滸傳',
  author:'施耐庵'
}]
export default class Demo3 extends React.PureComponent{
  state={
    bookList:[],
    bookId:'',
    loading:true
  }
  loadBookList(bookId){
    this.setState({
      loading:true
    });
    const timer = setTimeout(()=>{
      this.setState({
        loading:false,
        bookId,
        bookList
      });
      clearTimeout(timer);
    },2000);
  }
  componentDidMount(){
    const {match} = this.props;
    const {params} = match;
    const {bookId} = params;
    this.loadBookList(bookId);
  }
  render(){
    const {bookList,bookId,loading} = this.state;
    const selectedBook = bookList.find((book)=>book.bookId===bookId);
    return <Card>
      <Spin spinning={loading}>
        {          selectedBook&&(<div>
            <img width="120" src={`/static/images/book_cover_${bookId}.jpeg`}/>
            <h4>書(shū)名:{selectedBook?selectedBook.bookName:'--'}</h4>
            <div>作者:{selectedBook?selectedBook.author:'--'}</div>
          </div>)        }        <Divider orientation="left">關(guān)聯(lián)圖書(shū)</Divider>
        <Row>
          {            bookList.filter((book)=>book.bookId!==bookId).map((book)=>{              const {bookId,bookName} = book;              return <Col span={6}>
                <img width="120" src={`/static/images/book_cover_${bookId}.jpeg`}/>
                <h4><Link to={`/demo3/${bookId}`}>{bookName}</Link></h4>
              </Col>
            })          }        </Row>
      </Spin>
    </Card>
  }
}

通過(guò)演示gif,我們看到了地址欄的地址已經(jīng)發(fā)生改變,但是并沒(méi)有我們想象中那樣從新走一遍componentDidMount去請(qǐng)求數(shù)據(jù),這說(shuō)明我們的組件并沒(méi)有實(shí)現(xiàn)銷(xiāo)毀并重新生成這么一個(gè)過(guò)程。解決這個(gè)問(wèn)題你可以在componentDidUpdate去監(jiān)聽(tīng)其改變:

  componentDidUpdate(){
    const {match} = this.props;
    const {params} = match;
    const {bookId} = params;
    if(bookId!==this.state.bookId){
      this.loadBookList(bookId);
    }
  }

前面我們說(shuō)過(guò)如果是后期需要監(jiān)聽(tīng)多個(gè)props的話,這樣子后期維護(hù)比較麻煩.同樣我們還是利用key去解決這個(gè)問(wèn)題,首頁(yè)我們可以將頁(yè)面封裝成一個(gè)組件BookDetail,并且在其外層再包裹一層,再去給BookDetail加key,代碼如下:

import React from 'react';
import BookDetail from './bookDetail';
export default class Demo3 extends React.PureComponent{
  render(){
    const {match} = this.props;
    const {params} = match;
    const {bookId} = params;
    return <BookDetail key={bookId} bookId={bookId}/>
  }
}

這樣的好處是我們代碼結(jié)構(gòu)更加清晰,后續(xù)拓展新功能比較簡(jiǎn)單。

結(jié)語(yǔ)

React的高效得益于其Virtual DOM+React diff的體系。diff算法并非react獨(dú)創(chuàng),react只是在傳統(tǒng)diff算法做了優(yōu)化。但因?yàn)槠鋬?yōu)化,將diff算法的時(shí)間復(fù)雜度一下子從O(n^3)降到O(n)。

React diff的三大策略:

  • Web UI中DOM節(jié)點(diǎn)跨層級(jí)的移動(dòng)操作特別少,可以忽略不計(jì)。
  • 擁有相同類(lèi)的兩個(gè)組件將會(huì)生成相似的樹(shù)形結(jié)構(gòu),擁有不同類(lèi)的兩個(gè)組件將會(huì)生成不同的樹(shù)形結(jié)構(gòu)。
  • 對(duì)于同一層級(jí)的一組子節(jié)點(diǎn),它們可以通過(guò)唯一 id 進(jìn)行區(qū)分。

在開(kāi)發(fā)組件時(shí),保持穩(wěn)定的 DOM 結(jié)構(gòu)會(huì)有助于性能的提升。

在開(kāi)發(fā)過(guò)程中,盡量減少類(lèi)似將最后一個(gè)節(jié)點(diǎn)移動(dòng)到列表首部的操作。key的存在是為了提升diff效率,但未必一定就可以提升性能,記住簡(jiǎn)單列表渲染情況下,不加key要比加key的性能更好。

懂得借助react diff的特性去解決我們實(shí)際開(kāi)發(fā)中的一系列問(wèn)題。

到此這篇關(guān)于React diff算法原理詳細(xì)分析的文章就介紹到這了,更多相關(guān)React diff算法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解在create-react-app使用less與antd按需加載

    詳解在create-react-app使用less與antd按需加載

    這篇文章主要介紹了詳解在create-react-app使用less與antd按需加載,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • react中fetch之cors跨域請(qǐng)求的實(shí)現(xiàn)方法

    react中fetch之cors跨域請(qǐng)求的實(shí)現(xiàn)方法

    本篇文章主要介紹了react中fetch之cors跨域請(qǐng)求的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • create-react-app常用自定義配置教程示例

    create-react-app常用自定義配置教程示例

    這篇文章主要為大家介紹了create-react-app常用自定義配置教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • React前端渲染優(yōu)化--父組件導(dǎo)致子組件重復(fù)渲染的問(wèn)題

    React前端渲染優(yōu)化--父組件導(dǎo)致子組件重復(fù)渲染的問(wèn)題

    本篇文章是針對(duì)父組件導(dǎo)致子組件重復(fù)渲染的優(yōu)化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React報(bào)錯(cuò)之Object?is?possibly?null的問(wèn)題及解決方法

    React報(bào)錯(cuò)之Object?is?possibly?null的問(wèn)題及解決方法

    這篇文章主要介紹了React報(bào)錯(cuò)之Object?is?possibly?null的問(wèn)題,造成 "Object is possibly null"的錯(cuò)誤是因?yàn)閡seRef()鉤子可以傳遞一個(gè)初始值作為參數(shù),而我們傳遞null作為初始值,本文給大家分享詳細(xì)解決方法,需要的朋友可以參考下
    2022-07-07
  • react build 后打包發(fā)布總結(jié)

    react build 后打包發(fā)布總結(jié)

    這篇文章主要介紹了react build 后打包發(fā)布總結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • React受控組件與非受控組件深入講解

    React受控組件與非受控組件深入講解

    具體來(lái)說(shuō)這是一種react非受控組件,其狀態(tài)是在input的react內(nèi)部控制,不受調(diào)用者控制??梢允褂檬芸亟M件來(lái)實(shí)現(xiàn)。下面就說(shuō)說(shuō)這個(gè)React中的受控組件與非受控組件的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2022-12-12
  • React四級(jí)菜單的實(shí)現(xiàn)

    React四級(jí)菜單的實(shí)現(xiàn)

    本文主要介紹了React四級(jí)菜單的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • 解決React報(bào)錯(cuò)Unexpected default export of anonymous function

    解決React報(bào)錯(cuò)Unexpected default export of an

    這篇文章主要為大家介紹了React報(bào)錯(cuò)Unexpected default export of anonymous function解決方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 歸納總結(jié)Remix?表單常用方法及示例詳解

    歸納總結(jié)Remix?表單常用方法及示例詳解

    這篇文章主要為大家歸納總結(jié)了Remix?表單常用方法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03

最新評(píng)論