目录

非健全的空安全

一个 Dart 程序可以同时包含已经是 空安全 和未迁移至空安全的库。这些 混合模式的程序 会运行在 非健全的空安全 下。

混合模式版本 的空安全,让软件包的维护者可以迁移至空安全的同时,未迁移至空安全的使用者也可以享受新的问题修复和其他改进。然而,混合模式的程序无法拥有空安全带来的所有优势。

本文将描述健全和非健全的空安全之间的区别,让你可以为何时进行空安全迁移下定论。

健全和非健全的空安全

#

Dart 通过一系列的静态和运行时检查来提供健全的空安全。每一个使用了空安全的 Dart 库都会拥有所有的 静态 检查和更严格的编译期的错误提醒。对于包含了空安全库的混合模式程序也是如此。代码一旦开始迁移,已迁移的部分就能立刻享有到它带来的好处。

然而,混合模式的程序无法获得与空安全的程序的 运行时 健全性一致的保证。 null 很可能从非空安全的库污染到空安全的代码,因为一旦它被阻止,就会对现有的代码行为造成破坏。

为了在保持与传统库运行时的兼容性的同时,能为健全的空安全程序提供健全性, Dart 工具提供了以下两种模式的支持:

  • 非健全的空安全 运行的混合模式的程序。在运行时有可能出现 null 引用错误,但这只是因为一些 null 值和可空类型在非空安全的库中污染了空安全的代码。

  • 当程序完全迁移至空安全,且它所依赖的库 全部 都迁移完成后,它就在以 健全的空安全 运行,拥有所有由健全性带来的保证和编译优化。

健全的空安全唾手可得。当你的程序入口的库已经迁移至空安全,Dart 会自动以健全的空安全运行你的代码。如果你导入了非空安全的库,会有一条提示告诉你,你的程序只能以非健全的空安全运行。

逐步迁移

#

因为 Dart 支持混合模式的空安全,所以你可以一个个迁移你的库(通常是一个文件),同时能正常运行程序和测试。

我们推荐你 优先迁移最下层的库——指的是没有导入其他包的库。接着迁移直接依赖了下层库的依赖库。最后再迁移依赖项最多的库。

举个例子,假设你的 lib/src/util.dart 导入了其他(空安全)的软件包和核心库,但它没有包含任何 import '<本地路径>' 的引用。那么你应当优先考虑迁移 util.dart,然后迁移依赖了 util.dart 的文件。如果有一些循环引用的库(例如 A 引用了 B,B 引用了 C,C 引用了 A),建议同时对它们进行迁移。

使用迁移工具

#

你可以使用 迁移工具 进行渐进迁移。如果你需要排除部分文件或文件夹,勾选绿色的勾选框。下方的截图中,bin 文件夹的所有文件都已被排除。

Screenshot of file viewer in migration tool

每个不迁移的文件都会加上 2.9 语言版本的注释。你可以之后再次运行 dart migrate 继续迁移。已迁移的文件将显示为禁用的勾选框,它们无法撤销迁移更改。

手动进行迁移

#

手动对 package 进行迁移时,请参考以下步骤:

  1. 编辑 package 的 pubspec.yaml 文件,将最低 SDK 版本设置到 2.12.0

    yaml
    environment:
      sdk: '>=2.12.0 <3.0.0'
  2. 重新生成 Package 的配置文件

    $ dart pub get

    在版本低于 2.12.0 的 SDK 上运行 dart pub get 时,会将每个 package 的默认 SDK 版本设定为 2.12,并且默认它们已经迁移至空安全。

  3. 在你的 IDE 上打开 package。
    你也许会看到很多错误,没关系,让我们继续。

  4. 在所有你不考虑进行迁移的 Dart 文件顶部加上 语言版本注释

    dart
    // @dart=2.9

    在 2.12 的 package 中为库指定 2.9 的语言版本可以减少一些未迁移的分析错误。然而,非健全的空安全减少了分析器中可用的信息。例如,就算 2.9 版本的文件中一个参数可能会传入空值,分析器也可能会假定参数类型不为空,

  5. 利用分析器来辨析静态错误,逐个迁移 Dart 文件。
    按需添加 ?!required 以及 late 来消除静态错误。

测试或运行混合版本的程序

#

想要测试或运行混合版本的代码,你需要禁用健全的空安全。有两种方式可以进行操作:

  • dartflutter 命令里,加入 --no-sound-null-safety 标记禁用。例如:

    $ dart --no-sound-null-safety run
    $ flutter run --no-sound-null-safety
  • 或者,设定包含 main() 函数的文件程序入口的语言版本为 2.9。在 Flutter 应用中,一般是 lib/main.dart。在命令行应用中,一般是 bin/<package 名称>.dart。同时你也可以设定 test 下的文件,因为它们也包含程序入口。例如:

    dart
    // @dart=2.9
    import 'src/my_app.dart';
    
    void main() {
      //...
    }

以上两种方式的规避,对于 正在 增量迁移的过程非常有用,但这样做意味着你并未在完全启用空安全的情况下测试你的代码。当你完成增量迁移后,也请记得将测试代码 重新 迁移至空安全。