OverlayEntries 和 Routes 进行了重建优化

概述

本次优化提高了路由切换时的性能,但可能会揭露出你的应用中没有显式调用 setState 的问题。

上下文

在此更改之前,当一个新的不透明 OverlayEntry(记作 A)被添加到另一个 OverlayEntry(记作 B)上,或者 A 从 B 上移除时,B 将会重新构建。这些重建是不必要的,因为它们不是由 OverlayEntry 内部状态发生的改变而触发的。这个破坏性的改动优化了我们对 OverlayEntry 进行添加和移除的场景,移除了不必要的重建以提高性能。

由于 Navigator 在内部会把每一个 Route 嵌套在 OverlayEntry 中,因此这个改动同样作用于 Route 的变换:如果一个不透明的 Route 被添加到栈顶,或是从另一个 Route 的上层被移除时,位于不透明的 Route 下面的 Route 将不再进行不必要的重建。

更改描述

在大多数情况下,本次优化不需要你对代码进行任何更改。然而,如果你的应用错误地依赖了隐式重建,你可能会发现问题,这可以通过调用 setState 进行状态的变更来解决。

此外,这一更改略微调整了 widget 树的层级结构:在此更改之前,OverlayEntry 集合嵌套在 Stack 中。更改后,Stack 将从 widget 树结构中移除。

迁移指南

如果你在升级到包含此次更改的 Flutter 版本后遇到了问题,请检查你的代码是否遗漏了 setState 的调用。在下面的例子中,Navigator.pushNamed 方法异步执行完后隐式地修改了 Text 所展示的字符串 buttonLabel,它应该在显式的 setState 中调用。

迁移前代码:

class FooState extends State<Foo> {
  String buttonLabel = 'Click Me';
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        // Illegal state modification that should be wrapped in setState.
        buttonLabel = await Navigator.pushNamed(context, '/bar');
      },
      child: Text(buttonLabel),
    );
  }
}

迁移后代码:

class FooState extends State<Foo> {
  String buttonLabel = 'Click Me';
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        final newLabel = await Navigator.pushNamed(context, '/bar');
        setState(() {
          buttonLabel = newLabel;
        });
      },
      child: Text(buttonLabel),
    );
  }
}

时间轴

发布于版本:1.16.3
发布于稳定版本:1.17

参考文献

API 文档:

相关 issues:

相关 PR: