天天看點

React diff算法diff算法

文章目錄

  • diff算法
    • 三大政策
    • key的作用
    • index和id作為key的差別

diff算法

傳統diff 對比 react diff:

傳統的diff算法追求的是“完全”以及“最小”,而react diff則是放棄了這兩種追求

在傳統的diff算法下,對比前後兩個節點,如果發現節點改變了,會繼續去比較節點的子節點,一層一層去對比。就這樣循環遞歸去進行對比,複雜度就達到了o(n3),n是樹的節點數,想象一下如果這棵樹有1000個節點,得執行上十億次比較,這種量級的對比次數,時間基本要用秒來做計數機關。

其實 React 的 virtual dom 的性能好也離不開它本身特殊的diff算法。傳統的diff算法時間複雜度達到o(n³),而 react 的 diff算法 時間複雜度隻是o(n),react的 diff 能減少到o(n)依靠的是react diff的三大政策。

三大政策

1、tree diff:Web UI 中DOM節點跨層級的移動操作特别少,可以忽略不計

React 對 Virtual DOM樹 進行層級控制,隻會對 相同層級的DOM節點進行比較,即同一個父元素下的所有子節點,當發現節點已經不存在了,則會删除掉該節點下所有的子節點,不會再進行比較。這樣隻需要對DOM樹進行一次周遊,就可以完成整個樹的比較。複雜度變為O(n)

如果DOM節點出現了跨層級操作,diff處理方式:

如下圖所示,A節點及其子節點被整個移動到D節點下面去,由于React隻會簡單的考慮同級節點的位置變換,而對于不同層級的節點,隻有建立和删除操作,是以當根節點發現A節點消失了,就會删除A節點及其子節點,當D發現多了一個子節點A,就會建立新的A作為其子節點。

React diff算法diff算法

由此可以發現,當出現節點跨層級移動時,并不會出現想象中的移動操作,而是會進行删除,重新建立的動作,這是一種很影響React性能的操作。是以官方也不建議進行DOM節點跨層級的操作。

2、component diff:擁有相同類的兩個元件将會生成相似的樹形結構,擁有不同類的兩個元件将會生成不同的樹形結構

最核心的政策還是看結構是否發生改變。React是基于元件建構應用的,對于元件間的比較所采用的政策也是非常簡潔和高效的

  • 如果是 同一個類型 的元件,則按照原政策進行Virtual DOM比較。
  • 如果是 不同類型 的元件,則将其判斷為dirty component,進而替換整個組價下的所有子節點。
  • 如果是 同一個類型 的元件,有可能經過一輪Virtual DOM比較下來,并沒有發生變化。如果能夠提前确切知道這一點,那麼就可以省下大量的diff運算時間。是以,React允許使用者通過shouldComponentUpdate() 來判斷該元件是否需要進行 diff算法分析。

如下圖所示,當元件D變為元件G時,哪怕這兩個元件結構相似,一旦React判斷D和G是不用類型的元件,就不會比較兩者的結構,而是直接删除元件D,重新建立元件G及其子節點。也就是說,如果當兩個元件是不同類型但結構相似時,其實進行diff算法分析會影響性能,但是畢竟不同類型的元件存在相似DOM樹的情況在實際開發過程中很少出現,是以這種極端因素很難在實際開發過程中造成重大影響。

React diff算法diff算法

3、element diff:對于同一層級的一組子節點,它們可以通過唯一id或者key 進行區分

當節點屬于同一層級時,diff提供了3種節點操作,分别為INSERT_MARKUP(插入), MOVE_EXISTING(移動),REMOVE_NODE(删除)

操作 描述
插入 新節點不存在于舊集合當中,即全新的節點,就會執行插入操作
移動 新節點在舊集合中存在,并且隻做了位置上的更新,就會複用之前的節點,做移動操作,可以複用以前的DOM節點。
删除 新節點在舊集合中存在,但節點做出了更改不能直接複用,做出删除操作。或者舊節點不在新集合裡的,也需要執行删除操作

key的作用

問題:react中的 key 有什麼作用?(key的内部原理是什麼?)

虛拟DOM中key的作用:

  • 簡單的說: key是虛拟DOM對象的辨別, 在更新顯示時key起着極其重要的作用
  • 詳細的說: 當狀态中的資料發生變化時,react會根據【新資料】生成【新的虛拟DOM】,随後React進行 【新虛拟DOM】與【舊虛拟DOM】的diff比較,比較規則如下:
    • 1、 舊虛拟DOM中找到與新虛拟DOM相同的key:
      • 若虛拟DOM中内容沒變, 直接使用之前的真實DOM
      • 若虛拟DOM中内容變了,則生成新的真實DOM,随後替換掉頁面中之前的真實DOM
    • 2、 舊虛拟DOM中未找到與新虛拟DOM相同的key:
      • 根據資料建立新的真實DOM,随後渲染到到頁面

index和id作為key的差別

class Person extends React.Component {
    state = {
        persons: [
            { id: 1, name: '小張', age: 18 },
            { id: 2, name: '小李', age: 19 },
        ]
    }
    add = () => {
        const { persons } = this.state
        const p = { id: persons.length + 1, name: '小王', age: 20 }
        this.setState({ persons: [p, ...persons] })
    }
    render() {
        return (
            <div>
                <h2>展示人員資訊</h2>
                <button onClick={this.add}>添加一個小王</button>
                <h3>使用index(索引值)作為key</h3>
                <ul>
                    {
                        this.state.persons.map((personObj, index) => {
                            return <li key={index}>{personObj.name}---{personObj.age}<input type="text" /></li>
                        })
                    }
                </ul>
                <hr />
                <hr />
                <h3>使用id(資料的唯一辨別)作為key</h3>
                <ul>
                    {
                        this.state.persons.map((personObj) => {
                            return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text" /></li>
                        })
                    }
                </ul>
            </div>
        )
    }
}

ReactDOM.render(<Person />, document.getElementById('test'))
           

執行結果:

React diff算法diff算法

慢動作回放----使用index索引值作為key:

初始資料:
            {id:1,name:'小張',age:18},
            {id:2,name:'小李',age:19},
初始的虛拟DOM:
            <li key=0>小張---18<input type="text"/></li>
            <li key=1>小李---19<input type="text"/></li>

更新後的資料:
            {id:3,name:'小王',age:20},
            {id:1,name:'小張',age:18},
            {id:2,name:'小李',age:19},
更新資料後的虛拟DOM:
            <li key=0>小王---20<input type="text"/></li>
            <li key=1>小張---18<input type="text"/></li>
            <li key=2>小李---19<input type="text"/></li>
id為1和2的資料進行重新渲染,效率降低,并引發錯誤的Dom更新問題
           

慢動作回放----使用id唯一辨別作為key:

初始資料:
            {id:1,name:'小張',age:18},
            {id:2,name:'小李',age:19},
初始的虛拟DOM:
            <li key=1>小張---18<input type="text"/></li>
            <li key=2>小李---19<input type="text"/></li>

更新後的資料:
            {id:3,name:'小王',age:20},
            {id:1,name:'小張',age:18},
            {id:2,name:'小李',age:19},
更新資料後的虛拟DOM:
            <li key=3>小王---20<input type="text"/></li>
            <li key=1>小張---18<input type="text"/></li>
            <li key=2>小李---19<input type="text"/></li>
id為1和2的資料進行複用,并沒有重新渲染,提高了效率
           

用index作為key可能會引發的問題:

  1. 若對資料進行 逆序添加、逆序删除等破壞順序操作:

    會産生 沒有必要的真實DOM更新 ==> 界面效果沒問題, 但效率低。

  2. 如果結構中還 包含輸入類的DOM:

    會産生 錯誤DOM更新 ==> 界面有問題。

  3. 如果不存在對資料的逆序添加、逆序删除等破壞順序操作,僅用于渲染清單用于展示,使用index作為key是沒有問題的。

開發中如何選擇key:

  1. 最好使用每條資料的唯一辨別作為key, 比如id、手機号、身份證号、學号等唯一值
  2. 如果确定隻是簡單的展示資料,用index也是可以的。

繼續閱讀