LocalKey与GlobalKey的区别

LocalKey 也会复用 ElementState。实际上,这正是 LocalKey 最主要、最核心的作用。

GlobalKeyLocalKey 的区别不在于是否 复用 Element/State,而在于在多大的范围内寻找 那个需要被复用的 Element


LocalKey 是如何处理 Element 和 State 的?

为了理解这一点,我们需要简单了解一下 Flutter 的更新机制。

  1. Widget 树 vs. Element 树

    • 你写的代码是 Widget 树,它是一个"配置"或"蓝图",描述了 UI 应该长什么样。Widget 是不可变的(immutable)。
    • Flutter 内部维护一个 Element 树 。Element 是 Widget 的一个实例化对象,它持有对 Widget 和 State(如果是 StatefulWidget)的引用,并且是连接到最终渲染对象的桥梁。Element 是可变的,并且生命周期更长。
  2. 更新过程(Reconciliation / Diffing)

    • 当你调用 setState() 时,Flutter 会标记对应的 Element 为 "dirty" (需要更新)。
    • 在下一帧,Flutter 会重新执行 build 方法,生成一个新的 Widget 树。
    • 然后,Flutter 会遍历新旧 Widget 树(实际上是遍历新的 Widget 树,并与旧的 Element 树进行比较),来决定如何最高效地更新 Element 树。

现在,LocalKey 在这个比较过程中扮演了至关重要的角色。

场景一:没有 Key 的情况

当 Flutter 比较一个父 Widget 的新旧子节点列表时,如果没有 Key,它会按顺序和类型进行匹配。

例子:一个可删除的列表项

假设我们有一个列表,包含三个带状态的 StatefulTile(比如每个都有自己随机生成的颜色)。

初始状态:

  • Widget 树 : [StatefulTile(A), StatefulTile(B), StatefulTile(C)]
  • Element 树 : [Element(A), Element(B), Element(C)] (每个 Element 都持有自己的 State,比如颜色)

操作:删除第一个元素 A

setState 后,新的 build 方法返回:

  • 新 Widget 树 : [StatefulTile(B), StatefulTile(C)]

Flutter 的比对过程 (无 Key):

  1. 比较位置 0:

    • 新 Widget 是 StatefulTile(B)
    • 旧 Element 是 Element(A)
    • 类型匹配吗? 是的,都是 StatefulTile
    • 结果 :Flutter 认为你只是想更新 这个位置的 Widget。于是,它用 StatefulTile(B) 的配置去更新 Element(A)Element(A) 和它内部的 State (A的颜色) 被保留了下来!
  2. 比较位置 1:

    • 新 Widget 是 StatefulTile(C)
    • 旧 Element 是 Element(B)
    • 类型匹配吗? 是的。
    • 结果 :用 StatefulTile(C) 的配置去更新 Element(B)Element(B) 和它的 State (B的颜色) 被保留。
  3. 比较结束:

    • 旧的 Element(C) 没有被匹配到,于是它被销毁。

最终效果: 屏幕上显示了两个 Tile。第一个 Tile 的内容是 B,但颜色是 A 的颜色。第二个 Tile 的内容是 C,但颜色是 B 的颜色。状态错乱了!

场景二:使用 LocalKey 的情况

现在,我们给每个 StatefulTile 一个基于其内容的 LocalKey(比如 ValueKey)。

初始状态:

  • Widget 树 : [StatefulTile(key: ValueKey('A')), StatefulTile(key: ValueKey('B')), ...]
  • Element 树 : [Element(key: ValueKey('A')), Element(key: ValueKey('B')), ...]

操作:删除第一个元素 A

setState 后,新的 build 方法返回:

  • 新 Widget 树 : [StatefulTile(key: ValueKey('B')), StatefulTile(key: ValueKey('C'))]

Flutter 的比对过程 (有 Key):

  1. Flutter 拿到新的 Widget 列表 [Widget(B), Widget(C)] 和旧的 Element 列表 [Element(A), Element(B), Element(C)]
  2. 它不再仅仅按顺序比较,而是优先使用 Key 来查找匹配项
  3. 对于新的第一个 Widget StatefulTile(key: ValueKey('B')),Flutter 会在旧的兄弟节点中 寻找一个 Key 也是 ValueKey('B') 的 Element。
  4. 它找到了!是旧列表中的第二个 Element,Element(B)
  5. 结果 :Flutter 复用 Element(B)(连同它正确的 State),并把它移动到新的位置(位置 0)。
  6. 同理,对于新的第二个 Widget StatefulTile(key: ValueKey('C')),Flutter 找到了旧的 Element(C) 并复用它。
  7. 旧的 Element(A) 在新 Widget 列表中没有找到匹配的 Key,于是它被正确地销毁。

最终效果: 屏幕上正确地显示了 Tile B 和 Tile C,并且它们都保留了自己原有的、正确的状态(颜色)。


LocalKey vs GlobalKey 的复用机制对比

特性 LocalKey (如 ValueKey, ObjectKey) GlobalKey
查找范围 仅在兄弟节点之间 (Siblings)。Flutter 只会在同一个父 Widget 的子节点中查找匹配的 Key。 全局 (Entire App)。Flutter 会在整个应用范围内查找匹配的 Key。
核心目的 在一个集合(如 ListView, Column)内高效地 识别、重排、添加、删除 元素,并正确复用其 State 1. 跨越 Widget 树的层级,从外部访问一个 Widget 的 State。 2. 将一个 Widget 连同其 State 在不同的父节点之间移动
性能 。查找范围小,非常高效。 较低。需要维护一个全局的 Map,查找开销更大。

总结

  • LocalKeyGlobalKey 都会 触发 ElementState 的复用。
  • LocalKey 的作用域是局部的(兄弟节点之间) 。它是 Flutter 在处理列表、集合等动态 UI 时,保证状态正确性和渲染效率的基石。当你遇到列表项重排、删除后状态不正确的问题时,第一个就应该想到使用 LocalKey
  • GlobalKey 的作用域是全局的。它处理的是更特殊的情况,比如跨父级移动 Widget 或从任意位置访问特定 Widget 的状态,这是一种"降维打击",但开销也更大,需要谨慎使用。
相关推荐
Burt3 分钟前
#🎉 unibest 3.0 发布了!看看都更新了啥好用的功能\~
前端·uni-app
星垂野7 分钟前
JavaScript 执行栈和执行上下文详解
前端·javascript
别叫我13 分钟前
Swift串行上传多个图片
前端
风铃喵游15 分钟前
核心骨架: 小程序双线程架构
前端·架构
天平24 分钟前
使用https-proxy-agent下载墙外资源
前端·javascript
每天吃饭的羊1 小时前
面试题-函数类型的重载是啥意思
前端
迷途小码农么么哒1 小时前
Element 分页表格跨页多选状态保持方案(十几行代码解决)
前端
前端付豪1 小时前
美团路径缓存淘汰策略全解析(性能 vs 精度 vs 成本的三难选择)
前端·后端·架构