升级到Dart 2.14

空安全

随着Flutter的不断迭代更新,Dart语言也在快速发展。其中最重要的变化,是Dart 2.12正式发布的空安全特性。空安全特性是个分水岭,导致Dart语言面貌发生了很大的变化,许多库甚至直接放弃了升级空安全,停止了维护。这里我们升级Dart编程指南课程,一起来看一下,什么是空安全,怎么使用空安全?

类型系统中的可空性

因为一切均建立于静态类型系统上,所以空安全也始于此处。你的 Dart 程序中包含了整个类型世界:基本类型(如 intString)、集合类型(如 List)以及你所使用的自定义类型。在空安全推出之前,静态类型系统允许所有类型的表达式中的每一处都可以有 null

从类型理论的角度来说,Null 类型被看作是所有类型的子类;

类型会定义一些操作对象,包括 getters、setters、方法和操作符,在表达式中使用。如果是 List 类型,你可以对其调用 .add()[]。如果是 int 类型,你可以对其调用 +。但是 null 并没有定义任何一个方法。所以当 null 传递至其他类型的表达式时,任何操作都有可能失败。这就是空引用的症结所在——所有错误都来源于尝试在 null 上查找一个不存在的方法或属性。

非空和可空类型

空安全通过修改了类型的层级结构,从根源上解决了这个问题。 Null 类型仍然存在,但它不再是所有类型的子类。现在的类型层级看起来是这样的:

既然 Null 已不再被看作所有类型的子类,那么除了特殊的 Null 类型允许传递 null 值,其他类型均不允许。我们已经将所有的类型设置为 默认不可空 的类型。如果你的变量是 String 类型,它必须包含 一个字符串。这样一来,我们就修复了所有的空引用错误。

// 使用空安全
makeCoffee(String coffee, [String? dairy]) {
  if (dairy != null) {
    print('$coffee with $dairy');
  } else {
    print('Black $coffee');
  }
}

此处我们希望 dairy 参数能传入任意字符串,或者一个 null 值。为了表达我们的想法,我们在原有类型 String 的尾部加上 ? 使得 dairy 成为可空的类型。本质上,这和定义了一个原有类型加 Null 的 组合类型 没有什么区别。String? 表达的意思相当于是 String|Null 的缩写。

要谨慎使用允许空值的情况,避免如下的代码:

bad(String? maybeString) {
  print(maybeString.length);
}

main() {
  bad(null);
}

以上代码将毫无疑问地崩溃。

关于Dart新特性空安全更多详细深入的理解,请转到Dart官方文档《深入理解空安全》

需要注意的是,如果使用Dart 2.12及以上版本,不仅我们自己写的代码需要支持不兼容的空安全新特性,所有依赖的第三方库也必须要支持空安全特性。

实际使用

变量中的空安全

class A {
  int a;
  String s;
  // late延迟初始化
  late bool b;

  // 加?修饰,label值可以为null
  String? label;

  A(this.a, this.s) {
    if (a > 0)
      b = true;
    else
      b = false;
  }
}

函数中的空安全

// name为空安全,使用时无需判空
void greet(String name) {
    print(name.length);
    print("hello," + name);
}

// 参数可为空方法中的使用
// 命名参数中增加了required关键字
void func({required String? abc, double? d}) {}

// 返回值为非空类型,不能返回null
String getName() {
   // return null;
   return '';
}

// 要返回包含null类型,也需要加?
String? user() {
    return null;
}

泛型中的空安全

// list1变量不为空,但是列表中元素可为null
List<String?> list1;
// list2变量和列表中元素都可为null
List<String?>? list2;
// list3变量和列表中元素都不能为null
List<String> list3;

其他

不能使用List默认的构造方法List()

// 使用filled方法替代
List<int> list = List.filled(19, 0);
// 静态代码分析不出来,主动给编译器一个提示
// 1.使用“!”告诉编译器,变量不为空,消除编译错误
// 2.使用局部变量
if (list != null) {
    if (list![0] != null) {}
}

使用布局变量消除编译错误

  A? a;
  if (a != null) {
    var lab = a.label;
    if (lab != null) {
      print(lab.length);
    }
  }

类型别名

Dart 2.13 版推出了一个新特性,类型别名。

typedef Integer = int;

void main() {
  print(int == Integer); // true
}

常见的用法是给某类型指定一个更短或更具描述性的名称,以便代码更易于理解和维护。例如:

// 定义一个类型别名
typedef Json = Map<String, dynamic>;

class User {
  final String name;
  final int age;

  User.fromJson(Json json) : 
    name = json['name'],
    age = json['age'];

  Json get json => {
    'name': name, 
    'age': age,
  };
}

工具

废弃stagehanddartfmtdart2native命令,统一使用dart命令。

之前我们介绍使用pub global run stagehand console-full命令生成Dart命令行项目,现在使用下面的命令替代:

# 创建一个命令行模版工程
dart create -t console-full my_app

cd my_app
# 运行项目
dart run bin/my_app.dart

可以使用dart -h查看其他支持的参数。

以前使用dart2native编译,现在可以使用dart compile命令将 Dart 程序编译到目标平台:

# 生成独立的可执行文件
dart compile exe bin/myapp.dart
# 生成 AOT 快照
dart compile aot-snapshot bin/myapp.dart
# 生成JIT快照
dart compile jit-snapshot bin/myapp.dart
# 生成内核快照(一种可移植的 二进制快照)
dart compile kernel bin/myapp.dart
# 生成JavaScript
dart compile js bin/myapp.dart
Copyright © Arcticfox 2020 all right reserved,powered by Gitbook文档修订于: 2024-06-09 20:22:55

results matching ""

    No results matching ""