OverlayEntries 和 Routes 进行了重建优化

概述

Summary

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

This optimization improves performance for route transitions, but it may uncover missing calls to setState in your app.

上下文

Context

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

Prior to this change, an OverlayEntry would rebuild when a new opaque entry was added on top of it or removed above it. These rebuilds were unnecessary because they were not triggered by a change in state of the affected OverlayEntry. This breaking change optimized how we handle the addition and removal of OverlayEntrys, and removes unnecessary rebuilds to improve performance.

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

Since the Navigator internally puts each Route into an OverlayEntry this change also applies to Route transitions: If an opaque Route is pushed on top or removed from above another Route, the Routes below the opaque Route no longer rebuilds unnecessarily.

更改描述

Description of change

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

In most cases, this change doesn’t require any changes to your code. However, if your app was erroneously relying on the implicit rebuilds you may see issues, which can be resolved by wrapping any state change in a setState call.

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

Furthermore, this change slightly modified the shape of the widget tree: Prior to this change, the OverlayEntrys were wrapped in a Stack widget. The explicit Stack widget was removed from the widget hierarchy.

迁移指南

Migration guide

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

If you’re seeing issues after upgrading to a Flutter version that included this change, audit your code for missing calls to setState. In the example below, assigning the return value of Navigator.pushNamed to buttonLabel is implicitly modifying the state and it should be wrapped in an explicit setState call.

迁移前代码:

Code before migration:

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),
    );
  }
}

迁移后代码:

Code after migration:

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),
    );
  }
}

时间轴

Timeline

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

Landed in version: 1.16.3
In stable release: 1.17

参考文献

References

API 文档:

API documentation:

相关 issues:

Relevant issues:

相关 PR:

Relevant PRs: