Flutter Key的原理和使用 (二) Widget 和 Element 的對應關係

__white發表於2021-08-28

這是我參與8月更文挑戰的28天,活動詳情檢視:8月更文挑戰

Flutter Key的原理和使用 (一) 沒有Key會發生什麼 (juejin.cn)

上一篇文章中,我們遇到了一個問題 , widget換位置的時候,出現了一些意外,並沒有按照我們所想去調換位置, 這時候我們就很困惑, 那是在我們看來,寫了一個widget,那這個widget就該按照我們設定的那樣展示, 但實際上, widget並不是最終出現在螢幕上的東西.

Widget Tree

可能有人聽說過Widget Tree , 就是巢狀著widget的東西. 比如現在這段程式碼:

Column(
  children: [
    Box(Colors.blue),
    Box(Colors.red),
  ],
)
複製程式碼

對應的Widget Tree就是下面這個樣子:

 - Column 
   - Box
   - Box
複製程式碼

但真正繪製在螢幕上的並不是這個Widget Tree, 我們都知道Wdiget是描述UI元素Element的配置,所以實際執行起來的時候,還會產生一個Element Tree, Element Tree就是Widget Tree例項化之後產生的物件.

Widget Tree ---例項化---> Element Tree

 - Column                             - ColumnElement
   - Box             -例項化->          - BoxElement
   - Box                                - BoxElement
複製程式碼

Widget負責如何渲染,比如顏色,大小,形狀等等.

Element負責管理裡面的狀態.

所以狀態是隨著Element來改變的,外觀是隨著Widget改變的.二者是分開的.

為啥要分開的呢?

因為Widget是不可變的,我們可能經常會看到這樣的提示:

image.png

不可變,意思就是建立之後就不能在執行時改變它,但是State是可以變的.通常我們會用setState通知flutter去重建一個新的widget,而不是去改變這個widget,因為widget是不可變的.但是丟掉這個widget,並沒有把狀態也一起丟掉.

因為大部分UI的設計,是通過我們改變Widget來實現的,所以如果能把Widget和State分開,在hot reload的時候,就能在改動widget的時候保留它的狀態. 如果widget每次有變化,state就丟掉的話,程式就沒法用了,沒有辦法支援Hot Reload和動畫效果了.

為什麼改變Widget的順序,UI上並沒有改變順序

所以當Wdiget改變的時候,我們儘量將它與原來的State關聯起來.

如何關聯的呢?

當Widget改變的時候,flutter會去看Wdiget和這個Element是不是同樣的的,比如我們把Columen換成Row,那它們就不一樣了,然後會去看Key是一樣,這也就是Key可以幫助Flutter把容易混淆的Widget區分清楚.當然,如果不傳key,它就沒辦法對比key了,如果widget不變,依然會使用之前的element.

有點類似判斷兩個物件是否相等,會先去比較它們的型別.

因此我們把Box調換位置的時候,比如上面有兩個Box, 改變位置的時候,對應的Widget Tree是原來的型別,所以BoxElement剛好可以和之前的Box對應上,所以Flutter會以為沒有改變.

所以當我們做如下改變的時候:

Column(                                 Column(
  children: [                              children: [
    Box(Colors.blue),         ->             Box(Colors.red),
    Box(Colors.red),                         Box(Colors.blue),
  ]                                         ]  
複製程式碼

在Flutter看來,就是將上面的Widget變成了紅色, 下面的變成了藍色而已. 這麼看似乎也沒有問題.

接下來,我們看看有Key的情況下

 - Column                             - ColumnElement
   - Box (Key1)            -例項化->              - BoxElement (Key1)   
   - Box (Key2)                                   - BoxElement (Key2)   
複製程式碼

將上下兩個Box調換位置, 在比較型別相同之後比較Key的時候,第一個BoxElement (Key1)發現對應位置的Box的key不一致,此時Flutter就會在同一級別去搜尋其他widget,看看有沒有一樣的key,然後找到了下面有一個對應的box,同理下面的也是一樣的找法.

那麼改變之後的樣子就是這樣的:

 - Column                                      - ColumnElement
   - Box (Key2)            -例項化->              - BoxElement (Key1)   
   - Box (Key1)                                   - BoxElement (Key2)   
複製程式碼

也就達到了狀態也會互換的作用,Flutter能夠藉助Key來跟蹤順序的變化.

那如果我們像之前一樣刪除其中一個呢?

Column(
  children: [
    Box(Colors.blue),
    Box(Colors.red),
  ],
)
複製程式碼

刪掉第一個之後變成:

Column(
  children: [
    Box(Colors.red),
  ],
)
複製程式碼

那麼紅盒子就變成了第一個. 原來的

 - ColumnElement
   - BoxElement
   - BoxElement
複製程式碼

第一個BoxElement就對應了這個紅色的Box,第二個沒找到,此時它就被丟棄了.

那有Key的情況下,第一個Element去找Widget的時候,第一個key對不上,同級又找不到對應的,那麼它自己就丟棄了,第二個Element就對應到了第一個,變化也就和我們預期的一致了.

增加一個Wdiget也是同樣的道理.因為Element是按順序從上到下找的,沒有key的情況下,新增的同型別widget,會替代之前位置上的widget與element對應,最後位置的widget因為沒有對應的element,它就會呼叫createState來建立一個新的Stete.

相關文章