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 的状态,这是一种"降维打击",但开销也更大,需要谨慎使用。
相关推荐
swipe4 分钟前
05|(前端转后全栈)不手写一堆 SQL,后端怎么操作数据库?MyBatis-Plus 入门
前端·后端·全栈
swipe9 分钟前
04|(前端转后全栈)前端状态为什么不够用?从页面数据到 MySQL 持久化
前端·后端·全栈
橘子星10 分钟前
别光看教程!手把手拆解 React Todo 项目,一文吃透 5 个核心概念
前端·javascript
大白要努力!16 分钟前
纯前端实现 PDF 加水印工具 —— 零后端、支持中文、实时预览
前端·pdf·html
石小石Orz18 分钟前
TRAE SOLO实战:实现一个桌面3D助手
前端·人工智能
蜡台23 分钟前
使用 uni-popup 实现数据选择器Data-Picker
前端·javascript·html·uniapp·uni-popup·data-picker
拆房老料25 分钟前
BaseMetas FileView 1.2.0 发布:Office/WPS 大文件预览与内存安全优化实测
前端·产品运营·开源软件
blns_yxl28 分钟前
Promise封装Fetch + 重试机制(HTML+JS)
前端·javascript·html
Elastic 中国社区官方博客33 分钟前
使用重新设计的 AutoOps 更快地进行 Elasticsearch 问题排查
大数据·运维·前端·人工智能·elasticsearch·搜索引擎·全文检索