這是我參與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是不可變的,我們可能經常會看到這樣的提示:
不可變,意思就是建立之後就不能在執行時改變它,但是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.