1. 程式人生 > 程式設計 >React 的調和演算法Diffing 演算法策略詳解

React 的調和演算法Diffing 演算法策略詳解

目錄
  • 演算法策略
  • 單節點diffing
  • 陣列節點diffing
  • key值的使用要求

演算法策略

React的調和演算法,主要發生在render階段,調和演算法並不是一個特定的演算法函式,而是指在調和過程中,為提高構建workInProcess樹的效能,以及Dom樹更新的效能,而採用的一種策略,又稱diffing演算法。 在React 的官網上描述“Diffing” 演算法時,提到了“diffing two trees”,但是在原始碼實現時,並不是建立好兩棵樹後,再從上往下的diffing這兩棵樹。這個diffing發生在搭建子節點時,實際是新生成的ReactElement 與 current樹上fibe節點的diffing。 為了將diffing演算法的時間複雜度控制在O(n)(樹diff的時間複雜度涉及到樹的編輯距離,可以看這裡),採用瞭如下策略:

只比較同層級的節點,(貌似這一點沒有在官網中提到)

對於單節點比較,如果當前節點type 和 key 不相同,不再比較其下子節點,直接刪掉該節點及其下整棵子樹,根據ReactElement重新生成節點樹。因為React認為不同型別的元件生成的樹形結構不一樣,不必複用。

如果子節點是陣列,可根據唯一的key值定位節點進行比較,這樣即使子節點順序發生變化,也可以根據key值進行復用。

值得注意的是,所有節點的diffing都會比較key,key 預設值為null。若是沒有設定,則null是恆等於null的,認為key是相同的。

單節點diffing

單個節點進行diffing時,會diffing兩組屬性:fibe.elementType vs ReactElement.type , fibe.key vs ReactElement.key,如果這兩組屬性都相等,資料結構的物理

空間會有如下複用邏輯(詳見原始碼reconcileSi客棧ngleElement函式):

  1. 如果current fibe 存在 alternate 節點,(這意味著這個fibe節點之前參與過調和),則複用該alternate節點的物理空間;否則需要clone current fibe節點,佔用新的物理空間
  2. 對應的instance 或者 Dom 會被複用
  3. current fibe 下的child樹也會被直接掛載過來(下一步遞迴子節點時,會被使用)

如果兩組屬性有一個不相等:

  • fibe 根據ReactElement重新建立
  • 對應的instance和Dom 也會重建
  • child 樹全部刪除。

如果生成的ReactElement 是單節點,但是對應的current樹上是多節點時,會從逐一查詢有沒有匹配的,找到匹配的,其他的都刪除;找不到,全部刪除。例如Couter元件有如下邏輯:當點選次數大於10時,隱藏點選按鈕。當到達第10次時,span節點會被複用。

class Counter extends React.Component{
    state={
        count:0
    }
    addCount = ()=>{
        const count = this.state.count+1;
        this.setState({count})
    }
    componentDidUpdate(){
        console.log("updated")
    }
    render(){
        return this.state.count < 10 ? [
            <button onClick={this.addCount}>點選</button>
            <span>點選次數:{this.state.count}</span>
        ]:<span>點選次數:{this.state.count}</span>;
    }
}

陣列節點diffing

陣列節點進行diffing時,流程比較複雜:(原始碼見reconcileChildrenArray函式)

React 的調和演算法Diffing 演算法策略詳解

中間有兩次重要的遍歷,第一次按index遍歷,新舊節點依次比較,遇到key值不匹配的立即中斷遍歷。 第二次遍歷,對剩下的舊節點建立 “key - 節點”的Map表,遍歷剩下的新節點,按key值從表中查詢舊節點進行比較。以下是三種case下,新舊節點匹配比較的情況。

React 的調和演算法Diffing 演算法策略詳解

key值的使用要求

從單節點和陣列節點的diffing上看,key值主要是為了減少新建。為了保證diffing時新建舊節點能匹配上,key值使用時有如下注意:

  1. 得穩定,如果key值每次都變化(比如使用了隨機數vGOaN),diffing時,新舊節全部匹配不上,將會引http://www.cppcns.com起大量的新建;
  2. 必須得唯一,如果vGOaNkey值不唯一,在建立“key - 節點”的Map表時,會遺漏和錯亂,導致頁面更新錯誤。

對於單節點,diffing時key值也會用到,不要認為其沒用隨便亂設定。 也有一些不規範的用法,對單節點使用key值來實現元件的銷燬和重建,但這種用法是不符合React的設計理念的。

例如,有一個日誌元件,內部拉取日誌資料,對外部沒有資料依賴。但是在需求迭代時,在同頁面增加了審批操作,審批後,後端會新增一條日誌資料,這時候會希望前端頁面實時的展示新增的日誌資料,會需要觸發日誌元件重新拉取資料。如果不使用向上提升資料的方式來解決問題,可以給該元件指定一個隨機的key,當審批操作完成時,修改key值,則元件就會重新構建。但這種方式的開銷較高,整個元件樹都會銷燬重建。可以採用其他解決方案代替,例如可以給該元件指定ref,當審批完成後,通過ref呼叫該元件的重新整理函式,重新獲取資料,更新元件。

對於陣列節點,key值主要是在子節點位置發生大的錯位時,會起到關鍵作用。 對於普通的末尾新增,和末尾刪除,第一次index遍歷就可以匹配完,不會進入第二次key map的遍歷。 因為key值對“穩定”和“唯一”性這兩個要求,一般前端會需要後端對list資料提供一個唯一標識,對於聚合介面,會給後端增加額外工作,這種情況,可以先了解資料的變化特性,再決定是否真的需要設定key。

對於上面的日誌元件,日誌是一個列表,新增的日誌,都是插在第一行,如果不設定key,基本上所有的子節點都要更新。如果設定key,就需要後端服務為每一條日誌增加“永遠”惟一的識別符號,例如id。曾經經歷過一次因為key值的唯一性變化,導致資料更新時頁面展示錯誤的案例。

到此這篇關於React 的調和演算法(Diffing 演算法)的文章就介紹到這了,更多相關React Diffing 演算法內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!