Dart 2.13 版现已发布

做者 / Kevin Moore & Michael Thomsen前端

Dart 2.13 版现已发布,其中新增了类型别名功能,这是目前用户呼声第二高的语言功能。Dart 2.13 还改进了 Dart FFI 以及更好的性能,而且咱们还为 Dart 提供了新的官方镜像。本文将为您奉上 2.12 版中推出的空安全功能的最新信息,介绍 2.13 版本的新特性,以及 Docker 和 Google Cloud 对 Dart 后端支持的新消息。另外,还会预告在后续版本中的其余变化。react

空安全更新

在今年 3 月份发布的 Dart 2.12 中,咱们推出了 健全的空安 全功能。空安全可谓是 Dart 最近推出的一项重要功能,旨在帮助您避免空值错误 (这类错误常常难以发现),有效提高工做效率。咱们但愿发布 package 的开发者可以及时跟进这项发布,更新 pub.dev 上分享的 package 以支持空安全。git

咱们极其欣喜地看到,在发布后的短短几个月内,空安全就已被普遍采用,目前 pub.dev 上前 500 个最受欢迎的 package 中,93% 的 package 已经支持空安全。 在此,谨向如此迅速跟进的全部 package 开发者致以最诚挚的谢意,感谢你们帮助推进整个生态系统不断向前!github

有了这么多 package 支持空安全,您就能够开始考虑着手将本身的应用迁移到使用空安全的环境。要开始迁移,请首先使用 dart pub outdated 检查应用的依赖项。详细步骤,请参阅 空安全迁移指南。咱们还调整了 dart createflutter create 模板,如今它们在新的应用程序和 package 中默认启用空安全。docker

推出类型别名功能

类型别名是 2.13 版中新增的语言功能,也是广大开发者翘首以盼的功能,曾在语言问题的反馈中高居 第二位。有了这一功能,开发者就可以建立函数类型的别名,但不能建立其余任何类型。json

利用类型别名您能够为任何现有的类型建立新的名称,而后将新建立的名称用在原始类型能够出现的任何地方。建立新名称并不会真的定义一个新类型,只不过是引入一个简短的别名而已。该别名甚至能经过类型等同测试:后端

typedef Integer = int;

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

那么,类型别名能够怎么用?一种常见的用法是给某类型指定一个更短或更具描述性的名称,以便您的代码更易于理解和维护。数组

好比,给 JSON 类型指定别名就是种不错的用法 (此示例由 GitHub 用户 Levi-Lesches 提供,特此感谢)。在下列示例中,咱们能够定义一个新的类型别名 Json,它将一个 JSON 文档描述为一个 map,其键为 String,值为任意值 (使用动态类型)。这样,当咱们定义名为 fromJson 的构造函数和 json get函数时,就能使用该 Json 类型别名。安全

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

您也能够对指代某个类的类型别名调用构造函数,好比如下示例就很是合规:服务器

main() {
  var j = Json();
  j['name'] = 'Michael';
}

经过使用类型别名来指代复杂类型,可让读者更容易理解您代码的不变量。例如,如下代码定义了一个类型别名来描述键值为泛型类型 X、值为类型 List<X> 的映射。若是给该类型指定一个具备单一类型参数的名称,映射的常规结构在代码读者眼中会变得更为清晰。

typedef MapToList<X> = Map<X, List<X>>;
void main() {
  MapToList<int> m = {};
  m[7] = [7]; // OK
  m[8] = [2, 2, 2]; // OK
  for (var x in m.keys) {
    print('$x --> ${m[x]}');
  }
}

=>

7 --> [7]
8 --> [2, 2, 2]

若是您尝试使用不匹配的类型,将出现分析错误:

m[42] = ['The', 'meaning', 'of', 'life']; 


=>

The element type 'String' can't be assigned to the list type 'int'.

您甚至可使用类型别名来重命名公共库中的类。假设如今公共库中有一个 PoorlyNamedClass 类,您想要将它重命名为 BetterNamedClass。若是您只是重命名该类,那么您的 API 客户那边将会出现突发编译错误。而使用类型别名,则不会出现这一问题,您能够随意重命名,只不过要先为旧的类名称定义一个新的类型别名,再给旧名称添加几行 @Deprecated 注解。这样,使用 PoorlyNamedClass 的代码虽然会出现警告,但仍可继续编译并照旧正常运行,让用户有时间升级其代码。

mylibrary.dart:

class BetterNamedClass {}

@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

main.dart


import 'mylibrary.dart';


void main() {
  PoorlyNamedClass p;
}

=>

'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

下面介绍实现 BetterNamedClass 和弃用 PoorlyNamedClass 的方法 (在一个名为 mylibrary.dart 的文件中)。

class BetterNamedClass {...}

@Deprecated('Use BetterNamedClass instead')
typedef PoorlyNamedClass = BetterNamedClass;

下面是尝试使用 PoorlyNamedClass 时会发生的状况:

import 'mylibrary.dart';
void main() {
 PoorlyNamedClass p;
}
=>
'PoorlyNamedClass' is deprecated and shouldn't be used. Use BetterNamedClass instead.

类型别名功能从 Dart 2.13 版开始便可使用,要启用此功能,须要将您 pubspec 中版本较低的 Dart SDK 约束设置为最低 2.13 版,以下所示:

environment:
  sdk: ">=2.13.0 <3.0.0"

此功能支持向后兼容,这要归功于 语言的版本管理。也就是说,SDK 约束版本低于 2.13 的 package 能够安全地引用 2.13 版 package 中定义的类型别名,尽管 2.13 版以前的 package 不能定义其本身的类型别名。

Dart 2.13 FFI 的变化

咱们还在 Dart FFI (这是用来调用 C 语言代码的互操做机制) 中引入了一些新功能。

首先,FFI 如今支持包含内联数组 (#35763) 的结构。假设某 C 语言结构具备以下内联数组:

struct MyStruct {
  uint8_t arr[8];
}

如今,只需将包含一个类型实参的元素类型指定给 Array,便可直接将该结构体封装在 Dart 中,以下所示:

class StructInlineArray extends Struct {
  @Array(8)
  external Array<Uint8> arr;
}

其次,FFI 如今支持封装结构体 (#38158)。结构体一般都被放置在内存中,以便其位于地址边界内的成员可以被 CPU 更轻松地存取。使用 封装结构体 时,为了减小总体内存占用量,常常会以平台特有的方式忽略一些填充字节。借助新的 @Packed(<alignment>) 注解,您能够轻松指定填充字节。例如,下列代码建立的结构体就指定其在内存中时的字节对齐为 4。

@Packed(4)
class TASKDIALOGCONFIG extends Struct {
  @Uint32()
  external int cbSize;
  @IntPtr()
  external int hwndParent;
  @IntPtr()
  external int hInstance;
  @Uint32()
  external int dwFlags;
…
}

Dart 2.13 在性能方面的提高

咱们一直在不断努力下降 Dart 代码的应用体量和内存占用量。在大型 Flutter 应用中,通过 AOT 编译 Dart 程序的元数据的内部结构可能要占用很是可观的内存。这些元数据的存在大可能是为了实现热重载、交互式调试,以及格式化可读堆栈轨迹等功能,这些功能在须要部署的应用中从不会用到。过去几年来,咱们一直在重构 Dart 原生运行时环境,以便尽量多地消除这种开销。其中一些改进适用于全部以版本模式构建的 Flutter 应用,而有些则须要使用 --split-debug-info 标志将 AOT 编译应用中的调试信息拆分出来,从而放弃可读的堆栈轨迹。

Dart 2.13 在内存消耗上取得了很大的进步,在使用 --split-debug-info 时,程序元数据占用的空间量降幅显著。例如,Flutter Gallery 的空间占用降幅达到 30%: 在 --split-debug-info 模式下,程序元数据在 Dart 2.12 中要占用 5.7Mb,而在 Dart 2.13 中仅需 3.7Mb。以 Flutter Gallery 应用为例,在 Android 平台上,包含调试信息的发布 APK 大小为 112.4MB,不包含的状况下大小为 106.7MB (整体积减小了 5%)。该 APK 中包含了大量的资源。仅从 APK 内部的元数据体积来讲,从 Dart 2.12 平台上的 5.7MB 减小至 Dart 2.13 平台上的 3.7MB (减小了35%!)。

若是对您来讲应用体量和内存占用量比较重要,可使用 --split-debug-info 标志省略调试信息。请注意,一旦这么作,您须要使用 symbolize 命令 来从新使堆栈轨迹可读。

Dart 官方 Docker 镜像发布以及 Cloud 支持

Dart 如今在 官方镜像 中可用,虽然 Dart 早已提供了 Docker 镜像,但为了遵循最佳实践,这些 新的 Dart 镜像 是由 Docker 进行测试和验证的。它们还支持 AOT 编译,能够大大减小构建容器的大小,而且能够在容器环境中提高部署速度——如 Cloud Run

虽然 Dart 始终专一于使 Flutter 等应用框架在每一个屏幕上构建出色的界面,但咱们意识到,大多数用户体验的背后至少有一个托管服务。经过让 Dart 轻松构建后端服务来支持全栈体验,开发者可使用与前端 widget 相同的语言和业务逻辑,将他们的应用扩展到云端。

一般来讲,将 Dart 用于 Flutter 应用程序的后端,特别符合 Google 无服务器管理平台 Cloud Run 的简单性和可扩展性。这也包括零扩展,意味着当您的后端不处理任何请求时,就不会产生成本。咱们与 Google Cloud 团队合做,提供 Dart 的函数框架,这是一个 packages、工具和实例的集合,使开发者们可以轻松地编写 Dart 函数,以取代处理 HTTP 请求和 CloudEvents 的完整服务器部署。

您能够查看咱们的 Google Cloud 官方文档 以便开始使用。

后续更新预告

在接下来的版本中,还会有一些使人激动的改变。和以往同样,您可使用 language funnel 追踪器留意咱们的后续工做。

咱们一直努力改进的一个方面是,为 Dart 和 Flutter 定义一组新的 canonical lint。lint 是配置 Dart 静态分析 的一种高效方式,但因为可能有成百上千个 lint 要启用或禁用,有时可能会难以抉择。眼下,咱们正打算定义两组要在 Dart 和 Flutter 项目中默认应用的 canonical lint。预计这两组 lint 将在下一个稳定版中默认启用。若是您想要提早预览,请查看 lintsflutter_lints 这两个 package。

最后,若是您深度嵌套了 Dart VM 运行时环境,请注意,咱们打算弃用其现有的机制。咱们将用一个基于 Dart FFI 的更快、更灵活的模型取代它 (请参阅追踪问题 #45451)。

Dart 2.13 版现已发布

Dart 2.13 版现已在 Dart 2.13Flutter 2.2 SDK 中推出,此版本新增了类型别名功能,还改进了 FFI。

若是您一直在等待将本身的依赖项迁移到空安全环境的时机,不妨使用 dart pub outdated 再次检查一下。目前,前 500 个最受欢迎的 package 中,93% 的 package 都已迁移,如今没准就是您迁移的好时机。在此,谨向那些已经迁移的开发者致以最衷心的感谢!

欢迎试用本指南中介绍的新功能和改进后的功能,并将您使用后的感想告诉咱们。请在下方评论区留言。

相关文章
相关标签/搜索