跳至主内容

不健全的空安全

Dart 程序可能包含一些空安全的库,以及一些非空安全的库。这些混合版本程序依赖于不健全的空安全

混合语言版本的能力使包维护者可以自由地迁移其代码,同时知道即使是旧版用户也可以获得新的错误修复和其他改进。然而,混合版本程序无法获得空安全所带来的所有优势。

本页描述了健全空安全与不健全空安全之间的区别,旨在帮助您决定何时迁移到空安全。概念性讨论之后是增量迁移的说明,接着是测试和运行混合版本程序的详细信息。

健全与不健全的空安全

#

Dart 通过静态检查和运行时检查的结合提供了健全的空安全。每个选择启用空安全的 Dart 库都会获得所有静态检查,并伴随着更严格的编译时错误。即使在包含非空安全库的混合版本程序中,这也适用。一旦您开始将部分代码迁移到空安全,您就会立即开始获得这些好处。

然而,混合版本程序无法拥有完全空安全应用所具备的运行时健全性保证。`null` 值可能会从非空安全库中泄漏到空安全代码中,因为阻止这种情况会破坏未迁移代码的现有行为。

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

  • 混合版本程序以不健全的空安全模式运行。运行时可能会发生 `null` 引用错误,但这仅因为 `null` 或可空类型从某个非空安全库中“泄漏”并进入了空安全代码。

  • 当程序完全迁移并且所有库都空安全时,它将以健全的空安全模式运行,并享有健全性所带来的所有保证和编译器优化。

如果可能,您会希望使用健全的空安全。如果您的程序主入口库已选择启用空安全,Dart 工具会自动以健全模式运行您的程序。如果您导入了非空安全库,工具会打印警告,告知您它们只能以不健全的空安全模式运行。

增量迁移

#

由于 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` 以继续迁移。任何已迁移的文件都会显示一个禁用的复选框:文件一旦迁移,就无法撤销迁移。

手动迁移

#

如果您想手动增量迁移一个包,请遵循以下步骤:

  1. 编辑包的 `pubspec.yaml` 文件,将最低 SDK 约束设置为至少 `2.12.0`。

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

    dart pub get

    使用最低 SDK 约束为 `2.12.0` 运行 `dart pub get` 会将包中每个库的默认语言版本设置为 2.12,从而使它们全部选择启用空安全。

  3. 在您的 IDE 中打开该包。
    您可能会看到许多分析错误。没关系。

  4. 在您当前迁移中不想考虑的任何 Dart 文件的顶部添加一个语言版本注释

    dart
    // @dart=2.9

    在 2.12 包中使用 2.9 语言版本可以减少来自未迁移代码的分析错误(红色波浪线)。然而,不健全的空安全会减少分析器可以使用的信息。例如,分析器可能会假定参数类型是非空的,即使 2.9 文件可能会传入一个 `null` 值。

  5. 迁移每个 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/.dart`。您还可以选择排除 `test` 目录下的文件,因为它们也是入口点。例如:

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

使用这些机制之一排除测试对于您增量迁移过程的测试可能很有用,但这样做意味着您没有在完全启用空安全的情况下测试您的代码。当您完成库的增量迁移后,将测试重新启用空安全非常重要。