Dart 2.13 版现已发布

作者 / Kevin Moore & Michael Thomsen

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

空安全更新

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

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

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

推出类型别名功能

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

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

typedef Integer = int;

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

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

比如,给 JSON 类型指定别名就是种不错的用法 (此示例由 GitHub 用户 Levi-Lesches[5] 提供,特此感谢)。在下列示例中,我们可以定义一个新的类型别名 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"

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

Dart 2.13 FFI 的变化

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

首先,FFI 现在支持包含内联数组 (#35763[7]) 的结构。假设某 C 语言结构具有如下内联数组:

struct MyStruct {
  uint8_t arr[8];
}

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

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

其次,FFI 现在支持封装结构体 (#38158[8])。结构体通常都被放置在内存中,以便其位于地址边界内的成员能够被 CPU 更轻松地存取。使用 封装结构体[9] 时,为了减少整体内存占用量,经常会以平台特有的方式忽略一些填充字节。借助新的 @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[10] 标志将 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[11] 标志省略调试信息。请注意,一旦这么做,您需要使用 symbolize 命令[12] 来重新使堆栈轨迹可读。

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

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

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

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

您可以查看我们的 Google Cloud 官方文档[17] 以便开始使用。

后续更新预告

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

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

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

Dart 2.13 版现已发布

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

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

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

参考资料

[1]

Dart 2.12: https://flutter.cn/posts/announcing-dart-2-12

[2]

健全的空安: https://dart.cn/null-safety

[3]

空安全迁移指南: https://dart.cn/null-safety/migration-guide#step1-wait

[4]

第二位: https://github.com/dart-lang/language/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

[5]

Levi-Lesches: https://github.com/Levi-Lesches

[6]

语言的版本管理: https://dart.cn/guides/language/evolution#language-versioning

[7]

#35763: https://github.com/dart-lang/sdk/issues/35763

[8]

#38158: https://github.com/dart-lang/sdk/issues/38158

[9]

封装结构体: http://www.catb.org/esr/structure-packing/

[10]

--split-debug-info: https://flutter.cn/docs/perf/app-size#reducing-app-size

[11]

--split-debug-info: https://flutter.cn/docs/perf/app-size#reducing-app-size

[12]

symbolize 命令: https://flutter.cn/docs/deployment/obfuscate#reading-an-obfuscated-stack-trace

[13]

官方镜像: https://docs.docker.com/docker-hub/official_images/

[14]

新的 Dart 镜像: https://hub.docker.com/_/dart

[15]

Cloud Run: https://cloud.google.com/run

[16]

Dart 的函数框架: https://pub.dev/packages/functions_framework

[17]

Google Cloud 官方文档: https://dart.cn/server/google-cloud

[18]

language funnel: https://github.com/dart-lang/language/projects/1

[19]

静态分析: https://dart.cn//guides/language/analysis-options

[20]

lints: https://pub.dev/packages/lints

[21]

flutter_lints: https://pub.dev/packages/flutter_lints

[22]

#45451: https://github.com/dart-lang/sdk/issues/45451

[23]

Dart 2.13: https://dart.cn/get-dart

[24]

Flutter 2.2: https://dart.cn/get-dart

[25]

dart pub outdated: https://dart.cn/null-safety/migration-guide

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值