淺談React diff算法與key

寫在前面

提到React時(shí)我們腦中可能第一想法就是虛擬DOM和diff算法,也正是因?yàn)檫@兩個(gè)東西的存在,才會(huì)讓我們不必?fù)?dān)心由于頻繁操作DOM帶來的性能上的瓶頸,React本身在這方面已經(jīng)做得足夠隔離了,我們其實(shí)不用過于關(guān)注也能寫出不錯(cuò)性能的代碼,要想深入扒開diff算法的具體實(shí)現(xiàn)不是易事,這里我們盡量形象的淺嘗輒止分析一下.

diff策略

傳統(tǒng)diff算法:當(dāng)我們?cè)趯?duì)比兩個(gè)對(duì)象樹的差異時(shí),傳統(tǒng)的diff算法是利用循環(huán)遞歸的方式對(duì)所有節(jié)點(diǎn)依次對(duì)比,算法的復(fù)雜度為O(n * n * n),其中n為節(jié)點(diǎn)總數(shù),怎么樣看起來很恐怖吧,試想一個(gè)大項(xiàng)目中DOM樹的節(jié)點(diǎn)樹的立方...所以顯然如果react不對(duì)傳統(tǒng)的diff算法進(jìn)行優(yōu)化的話那么在大項(xiàng)目中的性能將會(huì)存在極大瓶頸,這里額外說一下,diff算法最早不是臉書團(tuán)隊(duì)提出來的.
react-diff:
這里react團(tuán)隊(duì)對(duì)傳統(tǒng)diff算法優(yōu)化主要基于三個(gè)策略,而這些策略最后都是對(duì)比vdom(網(wǎng)上很多帖子,包括書里介紹這部分的時(shí)候可能會(huì)比較隱晦難理解,我這里通俗總結(jié)了下):
1.DOM結(jié)構(gòu)發(fā)生改變-----直接卸載并重新creat
2.DOM結(jié)構(gòu)一樣-----不會(huì)卸載,但是會(huì)update
3.所有同一層級(jí)的子節(jié)點(diǎn).他們都可以通過key來區(qū)分-----同時(shí)遵循1.2兩點(diǎn)

先看第一種情況,假設(shè)目前前后兩次DOM樹如下圖,那么diff算法具體怎么實(shí)現(xiàn)的呢?我們通過console.log更為直觀的看一下結(jié)果(console.log的狀態(tài)分別放在對(duì)應(yīng)組件的生命周期里,其中以B組件為例,其他組件也雷同,代碼如下,如果有疑惑的同學(xué)可以翻看我以前生命周期的文章或者觀看別人的文章)

class B extends React.Component{
    constructor(props){
        super(props)
        console.log('B is creat')

    }
    componentDidMount(){
        console.log('B is did')

    }
    componentDidUpdate() {
        console.log('B is updated.');
    }
    componentWillUnmount(){
        console.log('B is unmount')
    }

    render(){
        return(
            <div>
                B
                {this.props.children}

            </div>

        )
    }
}

我們看到react-diff算法是趨近于'暴力'的方式,并不是把A,B,C轉(zhuǎn)移到D節(jié)點(diǎn)下面,這也就印證了我們所說的第一個(gè)策略,也是通用的策略,react-diff如果檢測(cè)DOM樹不一樣的情況就會(huì)直接銷毀當(dāng)前節(jié)點(diǎn)(包括當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)).另外值得一提的根據(jù)打印的順序我們也可以知道react渲染原則也是和JS執(zhí)行的順序原則是一樣的:
從左至右,從上到下
接下來看第二種策略,這次我們不改變DOM結(jié)構(gòu),只改變A節(jié)點(diǎn)的顏色,打印結(jié)果如下圖:


結(jié)果很明顯,只是單純的update,沒有銷毀及重新創(chuàng)建任何DOM節(jié)點(diǎn)...
所以根據(jù)以上的結(jié)果我們可以知道,要想最大化的實(shí)現(xiàn)diff算法的性能,我們?cè)陧?xiàng)目中盡量不改變DOM結(jié)構(gòu).比如插入DOM,刪除DOM,最好用visibility:hidden這樣的屬性.但是理想總歸是美好的,實(shí)際項(xiàng)目中有很多情況需要做刪除節(jié)點(diǎn)等操作,那么應(yīng)該怎么辦呢?其實(shí)react團(tuán)隊(duì)早就想好了相關(guān)的解決方案,比如key.

不可小覷的key

我們最開始寫react的時(shí)候應(yīng)該都見過這樣一個(gè)warn:


這是由于我們?cè)谘h(huán)渲染列表時(shí)候(map)時(shí)候忘記標(biāo)記key值報(bào)的警告,既然是警告,就說明即使沒有key的情況下也不會(huì)影響程序執(zhí)行的正確性.其實(shí)這個(gè)key的存在與否只會(huì)影響diff算法的復(fù)雜度,換言之,你不加key的情況下,diff算法就會(huì)以暴力的方式去根據(jù)一二的策略更新,但是你加了key,diff算法會(huì)引入一些另外的操作,這里不做贅述,有興趣的可以自己查閱一下.
加了key的好處:
其實(shí)key值不一定要在map的時(shí)候才去加,即使不map得時(shí)候也可以加,而且正確的加上key值還會(huì)帶來一定程度上的性能優(yōu)化,我們回歸一下,對(duì)初始DOM樹種的B,C調(diào)換位置,以下是不加key值得情況:


以下是加key的情況,代碼和打印結(jié)果如下:

return(
                    <div>
                        <A>
                            <B key="B"/>
                            <C key="C"/>
                        </A>
                        <D />
                    </div>
                )

return(
                    <div>
                        <A>
                            <C key="C"/>
                            <B key="B"/>
                        </A>
                        <D />
                    </div>

                )

結(jié)果顯而易見...

我們?cè)摬辉摪裮ap的index作為key
我們?cè)诮o循環(huán)渲染時(shí)總是會(huì)把index值或者隨機(jī)一個(gè)值作為key,比如以下代碼:

arr.map((val,index)=>{
                   return(
                            <span key={index}>val</span>
                   )
})

那么這樣做好不好呢?我們可以思考下,key做為DOM節(jié)點(diǎn)標(biāo)識(shí),如果是前后兩次arr分別為[1,2,3,4]和[5,6,7,8]和前后兩次arr分別為[1,2,3,4]和[4,3,2,1]的情況,很明顯前者可以認(rèn)為是DOM改變了,后者可以認(rèn)為是DOM節(jié)點(diǎn)的位移操作,那么對(duì)于第一種情況來說index作為key和沒有key值無區(qū)別,但是第二種情況用index作為key值效果沒有比用數(shù)據(jù)本身作為key值好,這里大家可以按照以上方式打印去看一下.所以結(jié)論是如果你的數(shù)據(jù)能確保唯一性,就用數(shù)據(jù)本身作為key值吧....
key值必須唯一且不重復(fù)么
答案是未必,前提條件是是否同一父節(jié)點(diǎn),也就是是否同一data-reactId的節(jié)點(diǎn),也就是說如下代碼是沒問題的:

                    <div>
                        <A>
                            <C key="C"/>
                            <B key="B"/>
                        </A>
                        <D key="C"/>
                    </div>

寫到這里我們應(yīng)該粗略的大致了解diff算法的策略和key值一些作用了.如果想了解更多的話建議多查閱資料或者潛心直接扒源碼去看一下.

寫在最后

diff算法并不是萬能的,diff算法其實(shí)也有不夠完全盡如人意的重渲染方式.所以我們?cè)趯憆eact代碼想要最大化性能的時(shí)候還是要注意DOM樹的結(jié)構(gòu)以及靈活運(yùn)用一些不起眼的屬性

如果有不對(duì)的地方可以直接留言或者mail:tzn_goodjob@163.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末废麻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件像吻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)披诗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來立磁,“玉大人呈队,你說我怎么就攤上這事〕纾” “怎么了宪摧?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵粒竖,是天一觀的道長。 經(jīng)常有香客問我几于,道長蕊苗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任沿彭,我火速辦了婚禮朽砰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喉刘。我一直安慰自己瞧柔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布睦裳。 她就那樣靜靜地躺著造锅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廉邑。 梳的紋絲不亂的頭發(fā)上备绽,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音鬓催,去河邊找鬼肺素。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宇驾,可吹牛的內(nèi)容都是我干的倍靡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼课舍,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼塌西!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筝尾,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤捡需,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后筹淫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體站辉,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年损姜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饰剥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摧阅,死狀恐怖汰蓉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棒卷,我是刑警寧澤顾孽,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布祝钢,位于F島的核電站,受9級(jí)特大地震影響若厚,放射性物質(zhì)發(fā)生泄漏太颤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一盹沈、第九天 我趴在偏房一處隱蔽的房頂上張望龄章。 院中可真熱鬧,春花似錦乞封、人聲如沸做裙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锚贱。三九已至,卻和暖如春关串,著一層夾襖步出監(jiān)牢的瞬間拧廊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工晋修, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吧碾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓墓卦,卻偏偏與公主長得像倦春,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子落剪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容