Flutter中key的作用

文章来源于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可以保持组件之前的状态,比如在用户滑动时或者集合改变时),这是一个抽象的定义,大家尽量理解原文,在后面的例子中可以很好的理解。

什么时候使用Key?

我们参照视频,写下了如下Demo,当点击FloatingActionButton时,交换两个Widget的位置。(你可以下载项目,运行一下,会更便于理解)

image.png

  1. 当我们使用Stateless Widget时,widget可以正常的移动。swap_color_1.dart
  2. 当我们把Widget改为Stateful时,交换出现了问题。swap_color_2.dart
  3. 当我们为Stateful加入Key之后,程序又能正常的执行swap_color_3.dart

从Demo中我们可以看出,当使用Stateless Widget时,我们并不需要使用key,当使用Stateful Widget时,集合内有数据移动和改变并且需要展示到界面时才需要key。

Key应该用到哪?

首先我们来搞懂上面,为什么swap_color_2会出问题,要明白这个问题,我们需要明白Flutter的渲染策略。

我们在构建Flutter的UI时是以Widget的形式『拼接』出来的,组件树作为UI每一个组件都对应一个元素(原文中是Slot),从而形成了『元素树』(Element Tree),元素树的内容非常简单,只包含了组件的类型和子元素的引用(Type),你可以把元素树当做Flutter App中的骨架(skeleton),它只展现了App的结构,并不包含其他具体的信息。

当我们交换组件树中的元素时,组件确实进行了交换,但是元素树却不一定。Flutter会先遍历(walk)整个元素树,从Row上的主元素,到主元素的子元素,查看整体的结构是否发生了变化,当然,它检查的只能是元素的Type和Key,在给出的例子中,当我们不设置Key时,元素树对比Type,发现Type并没有发生变化,而Flutter却是用元素树和元素对应的状态(可用或者不可用),来决定这个元素是否应该显示出来,所以在界面中并没有发生改变,但是当我们加入Key之后,对比的对象多了一个,并且是和之前不一样的,Flutter察觉到之后,立即改变了元素的状态,让它变为『无用状态』(deactivate),当遍历完之后,Flutter会浏览(look through)这些不匹配的元素(non-matched children)通过相应的引用为之找到对应的组件。当所有的元素都匹配完成之后,Flutter会刷新界面,展现出我们预想的。


Widget Tree and Element Tree

那么Key到底应该用到哪呢?
我们再来一个例子,swap_color_4,我们把色块用Padding包装一下。运行之后会发现,色块并没有交换,而是以随机的形式在变换颜色。为什么呢?

Padding Stateful

结合我们上面的理论,我们分析一下这次的Widget Tree 和 Element Tree,当我们交换元素后,Flutter element-to-widget matching algorithm,(元素-组件匹配算法),开始进行对比,算法每次只对比一层,即Padding这一层。显然,Padding并没有发生本质的变化。
Padding Widget Tree

于是开始进行第二层对比,在对比时Flutter发现元素与组件的Key并不匹配,于是,把它设置成不可用状态,但是这里所使用的Key只是本地Key(Local Key),Flutter并不能找到另一层里面的Key(即另外一个Padding Widget中的Key)所以,Flutter就创建了一个新的Widget,而这个Widget的颜色就成了我们看到的『随机色』。


Stateful Widget Tree

通过上面的示例,我们能明显的看出,我们的Key要设置到组件树的 顶层,而这一层在改变时,才能复用或者更新状态。

用哪一种Key?

Flutter中有很多Key,但是总体分为两种 Local Key和Global Key两种。


Key UML

对于Key的研究还不是特别多,有时间再补一篇,上面的例子,因为没有数据,所以使用了UniqueKey,在真实的开发中,我们可以用Model中的id作为ObjectKey。

GlobalKey其实是对应于LocalKey,上面我们说Padding中的就是LocalKey,Global即可以在多个页面或者层级复用,比如两个页面也可也同时保持一个状态。

还处在学习Flutter状态,如有错误请您及时指正,谢谢。

推荐阅读更多精彩内容