非健全的空安全
一个 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
文件夹的所有文件都已被排除。
每个不迁移的文件都会加上 2.9 语言版本的注释。你可以之后再次运行 dart migrate
继续迁移。已迁移的文件将显示为禁用的勾选框,它们无法撤销迁移更改。
手动进行迁移
手动对 package 进行迁移时,请参考以下步骤:
-
编辑 package 的
pubspec.yaml
文件,将最低 SDK 版本设置到2.12.0
:environment: sdk: '>=2.12.0 <3.0.0'
-
重新生成 Package 的配置文件:
$ dart pub get
在版本低于
2.12.0
的 SDK 上运行dart pub get
时,会将每个 package 的默认 SDK 版本设定为 2.12,并且默认它们已经迁移至空安全。 -
在你的 IDE 上打开 package。
你也许会看到很多错误,没关系,让我们继续。 -
在所有你不考虑进行迁移的 Dart 文件顶部加上 语言版本注释。
// @dart=2.9
在 2.12 的 package 中为库指定 2.9 的语言版本可以减少一些未迁移的分析错误。然而,非健全的空安全减少了分析器中可用的信息。例如,就算 2.9 版本的文件中一个参数可能会传入空值,分析器也可能会假定参数类型不为空,
-
利用分析器来辨析静态错误,逐个迁移 Dart 文件。
按需添加?
、!
、required
以及late
来消除静态错误。
测试或运行混合版本的程序
想要测试或运行混合版本的代码,你需要禁用健全的空安全。有两种方式可以进行操作:
-
在
dart
和flutter
命令里,加入--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=2.9 import 'src/my_app.dart'; void main() { //... }
以上两种方式的规避,对于 正在 增量迁移的过程非常有用,但这样做意味着你并未在完全启用空安全的情况下测试你的代码。当你完成增量迁移后,也请记得将测试代码 重新 迁移至空安全。