React DOM Diff算法詳解

React中文文檔 doc.react-china.org/javascript

前端開發中,原生JS對DOM的頻繁操做每每是使人頭痛而且十分影響性能的; 而React在設計之初就考慮到了這種需求並將其做爲重點,因而也就有了這裏要說的Diff算法;能夠說Diff是React性能的保障html

什麼是Diff算法

Diff算法是一種經常使用的對比差別的算法,但傳統Diff算法對於DOM樹的比較是比較吃力的,下面附上一份簡易版Diff算法(源代碼來自:blog.csdn.net/qq_26708777…)前端

let result = [];
// 比較葉子節點
const diffLeafs = function (beforeLeaf, afterLeaf) {
    // 獲取較大節點樹的長度
    let count = Math.max(beforeLeaf.children.length, afterLeaf.children.length);
    // 循環遍歷
    for (let i = 0; i < count; i++) {
        const beforeTag = beforeLeaf.children[i];
        const afterTag = afterLeaf.children[i];
        // 添加 afterTag 節點
        if (beforeTag === undefined) {
            result.push({ type: "add", element: afterTag });
            // 刪除 beforeTag 節點
        } else if (afterTag === undefined) {
            result.push({ type: "remove", element: beforeTag });
            // 節點名改變時,刪除 beforeTag 節點,添加 afterTag 節點
        } else if (beforeTag.tagName !== afterTag.tagName) {
            result.push({ type: "remove", element: beforeTag });
            result.push({ type: "add", element: afterTag });
            // 節點不變而內容改變時,改變節點
        } else if (beforeTag.innerHTML !== afterTag.innerHTML) {
            if (beforeTag.children.length === 0) {
                result.push({
                    type: "changed",
                    beforeElement: beforeTag,
                    afterElement: afterTag,
                    html: afterTag.innerHTML
                });
            } else {
                // 遞歸比較
                diffLeafs(beforeTag, afterTag);
            }
        }
    }
    return result;
}

複製代碼

React中的Diff

既然傳統的Diff算法對於DOM操做有性能上的缺點,那麼React又是如何解決這一問題的呢?下面看一段React代碼java

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
	render() {
		return (
			<div className="app"> <h1>Find My Fruit</h1> </div>
		)
	}
}

ReactDOM.render(
    <App />, document.getElementById('root') ); 複製代碼

須要特別注意, render 執行的結果獲得的不是真正的 DOM 節點. 結果僅僅是輕量級的 JavaScript 對象, 咱們稱之爲 virtual DOM.react

Diff策略

· Web UI 中DOM節點跨層級的移動操做特別少,能夠忽略不計算法

· 擁有相同類的兩個組件將會生成類似的樹形結構,擁有不一樣類的兩個組件將會生成不一樣的樹形結構。app

· 對於同一層級的一組子節點,它們能夠經過惟一id進行區分。dom

tree diff

基於策略一,WebUI中DOM節點跨層級的移動操做少的能夠忽略不計,React對Virtual DOM樹進行了層級控制,只會對相同層級的DOM節點進行比較,即同一個父元素下的全部子節點,當發現節點已經不存在了,則會刪除掉該節點下全部的子節點,不會再進行比較。這樣只須要對DOM樹進行一次遍歷,就能夠完成整個樹的比較。複雜度變爲O(n);性能

那麼,若是真的出現跨層及操做,React是如何表現的呢? 以下圖所示,A節點及其子節點被整個移動到D節點下面去,因爲React只會簡單的考慮同級節點的位置變換,而對於不一樣層級的節點,只有建立和刪除操做,因此當根節點發現A節點消失了,就會刪除A節點及其子節點,當D發現多了一個子節點A,就會建立新的A做爲其子節點。 此時,diff的執行狀況是:ui

remove A-> add A-> add B ->add C

這種狀況天然是比較消耗性能的,因此在開發React組件時要儘可能保證的DOM結構的穩定,儘可能避免出現跨層級操做

Components diff

React 只會匹配相同 class 的 component.說白了也就是隻會對同一類型的組件進行比較,而對於不一樣的類型組件,則放棄比較,直接刪掉舊的添加新的

好比, 若是有個 Header 被 ExampleBlock 替換掉了, React 會刪除掉 header 再建立一個 example block. 咱們不須要化寶貴的時間去匹配兩個不大可能有類似之處的 component.

列表 diff

假設咱們有個 component, 一個循環渲染了 5 個 component, 隨後又在列表中間插入一個新的 component.這時候會如何操做呢?

默認操做以下:
即把C更新成F,D更新成C,E更新成D,最後再插入E,是否是很沒有效率?

因此咱們須要使用key來給每一個節點作一個惟一的標識,Diff算法就能夠正確的識別此節點,找到正確的位置區插入新的節點。同一個列表由舊變新有三種行爲,插入、移動和刪除,它的比較策略是對於每個列表指定key,先將全部列表遍歷一遍,肯定要新增和刪除的,再肯定須要移動的。如圖所示,第一步將D刪掉,第二步增長E,再次執行時A和B只須要移動位置便可
相關文章
相關標籤/搜索