网站访问流程设计,wordpress评论框样式,网站建设后台什么意思,ios开发工程师Flutter 的 Widget Key 提议大调整#xff1f;深入聊一聊 Key 的作用
在 Flutter 里#xff0c;Key 对象存在的目的主要是区分和维持 Widget 的状态#xff0c;它是控件在渲染树里的「复用」标识之一#xff0c;这一点在之前的《深入 Flutter 和 Compose 在 UI 渲染刷新时…Flutter 的 Widget Key 提议大调整深入聊一聊 Key 的作用
在 Flutter 里Key 对象存在的目的主要是区分和维持 Widget 的状态它是控件在渲染树里的「复用」标识之一这一点在之前的《深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比》 聊到过可以说 Key 的存在关乎了 Flutter 的性能因为它的作用就是提高 Element Tree 的复用效率例如减少匹配阶段所需的 Widget 比较次数。 另外通过 Key 还可以提高如 AnimatedList、ListView 里重新排序时对应 Item widget 的效率通过将 Key 分配给 Item Flutter 可以更有效地识别何时添加、删除或更新列表并执行动画在这个时候 Key 可以确保每个 Item 即使在对列表进行排序时也保持其状态。
大多数情况下无状态的 Widget 是不需要 Key而默认情况下我们在不主动配置 Key 的时候它会是 null 也就是在没有 Key 的情况下framewok 一般只判断 runtimeType 去决定是否「复用」举个很老的官方例子如下图片代码里的 StatelessColorfulTile 所示它是一个无状态的 StatelessWidget 显示了一个随机颜色的 200x200 大小的正方形通过点击右下角按键每次调整两个方块的位置可以看到方块可以正常切换 因为此时没有 Key 在 Element Tree 只需要判断 runtimeType 明显此时 Element 符合复用条件而代码里又是直接使用 StatelessColorfulTile 的 Widget 实例对象进行 tiles.insert(1, tiles.removeAt(0)) 所以在 Widget 切换位置之后Element 和 RenderObject 只需要 update 一下新位置 Widget 实例的颜色即可 但是如果我们修改为 StatefulWidget 此时我们再点击右下角按键可以看到此时颜色方块不会切换了 因为此时颜色 color 被保存在 State 下在 Widget 切换位置之后因为 runtimeType 符合条件所以 Element 复用但是颜色被保存在 State 下State 又是保存在 Element 里从而导致颜色并没有按照需求被更新切换 但是如果这时候我们给两个 StatefulWidget 添加上 Key 就可以看到它们可以被切换了因为 canUpdate 判断条件会增加 Key 判断 也就是在有了 Key 之后新 Widget 的 key 就可以在老 Element 列表里进行匹配从而更新 Element 的位置并刷新 RenderObject两个 Element 在状态保留的情况下被 Tree 里调换了位置进行更新从而实现了切换的效果 所以从这个简单的例子可以直观看到 Key 在有状态的情况下能够发挥的作用当然目前在 Flutter 里的 Key 类型很丰富但是大致可以简单分为两类 Local Keys 和 Global Keys 。 顾名思义就是它的作用范围举个例子如果我们给 StatelessColorfulTile 增加了一个 Padding 再点击切换按键可以看到此时点击后 Element 一直被重构 因为此时在 Row 里面此时处于“一级”位置 children 是两个 Padding而 Padding 没有 Key所以它在 runtimeType 条件的情况下是直接被复用 而对于 StatelessColorfulTile 而言它处于 Padding 之下Padding 不是一个 Multi Child 的控件所以在 canUpdate 为 false 的时候Flutter 内部会认为它需要被重新创建 从这里我们就可以很直观体验到 Local Keys 这个概念它只作用于标识同一父 Widget 中的 Widget不能用于识别其父 Widget 之外的 Widget。
同时我们也可以是直观感受到Multi Child 和 Single Child 的 Element 对于 Diff 更新时的策略差别。
另外我们还可以感受到 Widget 作为「配置文件」的存在要知道代码里我们操作的一直都是 tiles.insert(1, tiles.removeAt(0)); 也就是 Widget 的实例化都的对象虽然 Widget 实例没变但是 Element 层面还是会根据情况「重新创建」对应的 Element 由于颜色是在 State 里所以也就会跟着 Element 重新随机变化。
最后如下图所示对于 Local Keys 来说左侧这样的写法是可以的而右侧这样的写法是违规的 所以在 Widget 的 Key 注释里也有这样一句描述通常情况下作为另一个 widget 的唯一子项的 widget 不需要显式 Key。 GlobalKey
那么除开 Local Keys Flutter 里还有一个特殊的 GlobalKey允许开发者在 Widget 树里去「唯一」标识 Widget并提供 BuildContext(Element)/State 的全局访问 这里的「唯一」更多体现在当前这一帧里的「唯一」。 比如前面的例子我们只需要把对应的 Local Keys 换成 GlobalKey 就可以看到虽然 Key 所在的 StatelessColorfulTile 还是在 Padding 下的“二级” child 但是现在点击切换时它不会被「重新创建」导致颜色发生变化 这是因为虽然在 updateChild 的时候逻辑依然会走到 inflateWidget 去创建 Element 但是由于是 GlobalKey所以会从全局保存的 Map 里获取到当前 GlobalKey 绑定的 Element 从而 retake 复用 从这里可以看出来 如果 Element 在同一帧中移动或者删除并且它具有 GlobalKey那么它仍然可能被重新激活使用。 所以 GlobalKey 不仅可以作为 Key 区分 Widget 帧内还可以在 BuildOwner 里“全局”保持住 Element 、State 和关联 RenderObject 的“状态”即使它出现移动或者删除。
同时通过 GlobalKey 我们也可以访问对应的 BuildContext 和 State 数据甚至是直接给 MaterialApp 添加 GlobalKey 来操作导航 那么 GlobalKey 这么好用它又存在什么问题呢其实在注释里已经有对应说明 GlobalKey 在使用的过程中可能会出现需要重新设置 [Element] 父级的情况而这个操作会触发对关联的 [State] 及其所有后代 [State.deactivate] 的调用还会强制重建所有依赖于 [InheritedWidget] 的控件。 具体就体现在这下面两段代码
_retakeInactiveElement 内可能会触发所有关联 State 的 deactivate_activateWithParent 会触发 Element 的 activate 从而通过 didChangeDependencies 强制重建所有依赖于 [InheritedWidget] 的控件 当然GlobalKey 也有一些注意事项例如 使用 GlobalKey 不能频繁创建通常应该是让它和 State 对象拥有类似的“生命长度”因为新的 GlobalKey 会丢弃与旧 Key 关联的子树的状态并为新键创建一个新的子树频繁创建会导致状态丢失和性能损耗。 变更提议
前面我们主要介绍了 Key 的作用和分类下的职能而本次 PR 提议的调整则是在于打算简化 Local Keys 相关的实现上可以看到在以往的实现里关于 LocalKey 的实现有好几种类型但是其中一些职能其实「相对重复」 在 #159225 的 PR 里将打算把 Key 对象切换到 Object 从而“消灭”过往这些 Local Keys 的“重叠”让 Key API 更加灵活 另外除了灵活和简化之外针对目前存在的 Local Keys 它和 Dart 的 Extension Types 不同比如使用 ValueKey() 多多少少会有一点点点点点点 wrapper 成本而如果这个提议合并后大概会是如下所示的情况或多或少对性能还是有那么一点点点点点点帮助 事实上对于 LocalKey 大多数人应该都只会使用到 ValueKey 居多。 当然这个 PR 整体来说还是属于底层大调整而目前看起来提议应该是暂时搁置了不过就算推进落地相信对于大多数上层 Flutter 开发者来说应该也不会有明显的感知毕竟大多数时候 Flutter 开发者对 Key 并不敏感 所以你是喜欢现在的 Local Keys 分类还是提议里的 Object
参考链接
https://github.com/flutter/flutter/pull/159225https://api.flutter.dev/flutter/foundation/Key-class.html