文章來源于Flutter Widgets 101 Ep. 4,感興趣的同學可以直接看視頻,更便于理解。
首先,我們要明白Key的作用,Keys preserve state when widgets move around in your widget tree. They can be used to preserve the user's scroll location, or keeping state when modifying a collection. (當組件在組件樹中移動時使用Key可以保持組件之前的狀態(tài),比如在用戶滑動時或者集合改變時),這是一個抽象的定義,大家盡量理解原文,在后面的例子中可以很好的理解。
什么時候使用Key?
我們參照視頻,寫下了如下Demo,當點擊FloatingActionButton時,交換兩個Widget的位置。(你可以下載項目,運行一下,會更便于理解)

- 當我們使用Stateless Widget時,widget可以正常的移動。swap_color_1.dart
- 當我們把Widget改為Stateful時,交換出現(xiàn)了問題。swap_color_2.dart
- 當我們?yōu)镾tateful加入Key之后,程序又能正常的執(zhí)行swap_color_3.dart
從Demo中我們可以看出,當使用Stateless Widget時,我們并不需要使用key,當使用Stateful Widget時,集合內(nèi)有數(shù)據(jù)移動和改變并且需要展示到界面時才需要key。
Key應該用到哪?
首先我們來搞懂上面,為什么swap_color_2會出問題,要明白這個問題,我們需要明白Flutter的渲染策略。
我們在構建Flutter的UI時是以Widget的形式『拼接』出來的,組件樹作為UI每一個組件都對應一個元素(原文中是Slot),從而形成了『元素樹』(Element Tree),元素樹的內(nèi)容非常簡單,只包含了組件的類型和子元素的引用(Type),你可以把元素樹當做Flutter App中的骨架(skeleton),它只展現(xiàn)了App的結構,并不包含其他具體的信息。
當我們交換組件樹中的元素時,組件確實進行了交換,但是元素樹卻不一定。Flutter會先遍歷(walk)整個元素樹,從Row上的主元素,到主元素的子元素,查看整體的結構是否發(fā)生了變化,當然,它檢查的只能是元素的Type和Key,在給出的例子中,當我們不設置Key時,元素樹對比Type,發(fā)現(xiàn)Type并沒有發(fā)生變化,而Flutter卻是用元素樹和元素對應的狀態(tài)(可用或者不可用),來決定這個元素是否應該顯示出來,所以在界面中并沒有發(fā)生改變,但是當我們加入Key之后,對比的對象多了一個,并且是和之前不一樣的,F(xiàn)lutter察覺到之后,立即改變了元素的狀態(tài),讓它變?yōu)椤簾o用狀態(tài)』(deactivate),當遍歷完之后,F(xiàn)lutter會瀏覽(look through)這些不匹配的元素(non-matched children)通過相應的引用為之找到對應的組件。當所有的元素都匹配完成之后,F(xiàn)lutter會刷新界面,展現(xiàn)出我們預想的。

那么Key到底應該用到哪呢?
我們再來一個例子,swap_color_4,我們把色塊用Padding包裝一下。運行之后會發(fā)現(xiàn),色塊并沒有交換,而是以隨機的形式在變換顏色。為什么呢?

結合我們上面的理論,我們分析一下這次的Widget Tree 和 Element Tree,當我們交換元素后,F(xiàn)lutter element-to-widget matching algorithm,(元素-組件匹配算法),開始進行對比,算法每次只對比一層,即Padding這一層。顯然,Padding并沒有發(fā)生本質的變化。

于是開始進行第二層對比,在對比時Flutter發(fā)現(xiàn)元素與組件的Key并不匹配,于是,把它設置成不可用狀態(tài),但是這里所使用的Key只是本地Key(Local Key),F(xiàn)lutter并不能找到另一層里面的Key(即另外一個Padding Widget中的Key)所以,F(xiàn)lutter就創(chuàng)建了一個新的Widget,而這個Widget的顏色就成了我們看到的『隨機色』。

通過上面的示例,我們能明顯的看出,我們的Key要設置到組件樹的 頂層,而這一層在改變時,才能復用或者更新狀態(tài)。
用哪一種Key?
Flutter中有很多Key,但是總體分為兩種 Local Key和Global Key兩種。

對于Key的研究還不是特別多,有時間再補一篇,上面的例子,因為沒有數(shù)據(jù),所以使用了UniqueKey,在真實的開發(fā)中,我們可以用Model中的id作為ObjectKey。
GlobalKey其實是對應于LocalKey,上面我們說Padding中的就是LocalKey,Global即可以在多個頁面或者層級復用,比如兩個頁面也可也同時保持一個狀態(tài)。
還處在學習Flutter狀態(tài),如有錯誤請您及時指正,謝謝。