由于元素树按类型将其元素映射到小部件树中的小部件,如果新小部件替换了旧小部件,并且新小部件与旧小部件具有相同的类型,则元素树中与旧小部件对应的元素现在被分配给新小部件。但是,如果我们使用键,则元素树中的元素将通过类型和键映射到小部件树中的小部件,这消除了当新旧小部件具有相同类型时旧小部件的状态被重用为新小部件的状态的问题。
为什么Flutter不为元素默认创建一个键,而是要求我们指定一个键?
首先,我想纠正这句话:
为什么Flutter不为默认元素创建一个键
Flutter实际上在默认情况下为所有小部件创建一个Key
,来自key的官方文档:
如果两个小部件的runtimeType和key属性分别为operator==,则新小部件通过更新底层元素(即通过调用element)来替换旧小部件。使用新的小部件进行更新)。
所以默认情况下,部件的Key
与其runtimeType
直接相关,以及在更新状态时导致问题的原因,其中两个或更多具有相同Key
的小部件(我们说的是相同的,并且基于小部件runtimeType
)发生冲突,从而导致该行为。
你现在可能会问,为什么Flutter甚至使用runtimeType
,为什么它不使用hashcode
或uuid
或Random()
之类的东西作为Key
…
答案是,当我们将小部件添加到小部件树并尝试更新其状态时,Flutter不会从树中取出整个小部件并再次插入它,它查找具有该键的东西,这是使用它的小部件,然后比较State
对象,然后将一个替换为另一个。
这样工作的算法的目的是获得最高性能和轻量级的状态更新,这使得Flutter非常快速地重建小部件。
阅读更多从这个,和这个。
在这种情况下,会导致旧部件的奇怪行为被新部件更新,Flutter提供了多个Key
解决方案,以便框架知道每个部件都与其他部件分开,即使它们具有相同的runtimeType
,即UniqueKey()
,GlobalKey()
,valueKey()
,PageStorageKey()
…
,每一个都是不同的,有它最好的用例。
对于旧部件在同一屏幕上由新部件更新的示例,可以通过仅分配其中的任何键来固定,但是ValueKey()
基于任何对象的值返回Key
,例如ValueKey(1)
,或者UniqueKey()
,其中UniqueKey() == UniqueKey()
始终是false
。
在另一种情况下,你将在应用程序的两个不同的屏幕/路由中使用相同的小部件,并且你希望它们具有相同的状态,那么GlobalKey()
将适合这种情况。
在另一种情况下,你有多个可滚动的部件,如ListView.builder()
或SingleChildScrollView()
…,你想滚动到某个位置,然后导航或改变选项卡(作为一个例子)…,当你回到滚动位置时,它会被记住,然后PageStorageKey()
将适合这种情况。
您可以从:
中阅读更多关于键的信息。官方文档
和