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 的状态,这是一种"降维打击",但开销也更大,需要谨慎使用。
相关推荐
太阳伞下的阿呆2 小时前
本地环境vue与springboot联调
前端·vue.js·spring boot
阳光是sunny2 小时前
走进微前端(1)手写single-spa核心原理
前端·javascript·vue.js
烛阴3 小时前
Ceil -- 从平滑到阶梯
前端·webgl
90后的晨仔3 小时前
🔍Vue 模板引用(Template Refs)全解析:当你必须操作 DOM 时
前端·vue.js
90后的晨仔3 小时前
👂 Vue 侦听器(watch)详解:监听数据的变化
前端·vue.js
90后的晨仔4 小时前
深入浅出 Vue 的 computed:不仅仅是“计算属性”那么简单!
前端·vue.js
Nan_Shu_6144 小时前
学习:入门uniapp Vue3组合式API版本(17)
前端·vue.js·学习·uni-app
止观止4 小时前
Remix框架:高性能React全栈开发实战
前端·react.js·前端框架·remix
萌萌哒草头将军5 小时前
🚀🚀🚀 深入探索 Node.js v22.18.0 新特性;默认支持运行 ts 文件了!
前端·typescript·node.js
安心不心安5 小时前
React ahooks——副作用类hooks之useThrottleFn
前端·javascript·react.js