Dart 2.7 发布: 更安全、更具表现力的 Dart

2020-12-14 09:57:35 浏览数 (1)

上周,我们发布了 Dart 2.7 SDK 的稳定版本,它可以为开发者提供多项新功能。Dart 语言经过了充实的一年,它是一种针对客户端优化的语言,适用于在任何平台上开发高效运行的应用。我们今年发布了 6 个新版本,数十项新功能。我们很欣喜地看到这些功能已经被 Dart 社区广泛使用。最近的 GitHub Octoverse 显示,根据多个参与方的评估结果,Dart 被认定为增长速度最快的编程语言 (排名第一),这一消息让我们备受鼓舞。

Dart 2.7 增加了对扩展方法的支持,此外还添加了一个新的代码包,用来处理带有特殊字符的字符串。我们更新了空安全 (已经实现类型安全的可空和非空类型),还通过 DartPad 带来了全新的代码体验环境 (而且支持空安全)。在生态系统层级,pub.dev 现在加入了新的点赞 (Like) 功能,用户们喜欢代码包如今更加一目了然。Dart 2.7 现在就可以从 dart.dev 下载并作为 SDK 使用,并且它也包含在发布的 Flutter 1.12 中

  • GitHub Octoverse https://octoverse.github.com/
  • 增长速度最快的编程语言 https://octoverse.github.com/#top-languages
  • 下载 Dart 2.7 http://dart.dev/

扩展方法

Dart 2.7 加入了一个长期以来备受期待的强大新语言功能: 扩展方法。扩展方法可以让您给任何类型 (包括您无法控制的类型) 添加新功能,并依然享受和常规方法一样的简洁输入体验以及代码自动补全功能。

我们来看一个简单的例子: 如何从为 String 添加解析 int 和 double 的方法。作为应用开发者,我们无法更改 String 类,因为这个类是在 dart:core 代码库中定义的,但是在扩展方法的帮助下,我们就可以亲手扩展它!在定义了扩展方法之后,我们就可以在 String 上调用新的 parseInt 方法,就如同这个方法是在 String 类中被原生定义的那样:

代码语言:javascript复制
extension ParseNumbers on String {
  int parseInt() {
    return int.parse(this);
  }

  double parseDouble() {
    return double.parse(this);
  }
}

main() {
  int i = '42'.parseInt();
  print(i);
}

扩展方法是静态的

扩展方法是静态解析、静态配置的,也就是说,您无法通过动态值来调用它们。如下所示,该调用在运行时会抛出异常:

代码语言:javascript复制
  dynamic d = '2';
  d.parseInt();

→ Runtime exception: NoSuchMethodError

扩展方法和 Dart 的类型推断可以很好地协作,所以在下面这个例子中,变量 "v" 被推断为 String 类,自然 String 上的扩展方法是可用的:

代码语言:javascript复制
  var v = '1';
  v.parseInt(); // Works!

因为扩展方法是静态解析的,所以它们的速度就和调用静态方法或 helper 方法一样快,但调用语法则要友好很多。

  • 类型推断 https://dart.dev/guides/language/sound-dart#type-inference

扩展可以拥有类型变量

因为扩展方法是静态解析的,所以它们的速度就和调用静态方法或 helper 方法一样假如我们想在 List 上定义一个扩展,用来获取序号为偶数的内容列表。那么我们就会希望让这个扩展运行在任何类型的列表上,返回和输入列表相同类型的新列表。为了做到这一点,我们可以把扩展泛型化,并将它的类型参数应用到它扩展的类型和方法里:

代码语言:javascript复制
extension FancyList<T> on List<T> {
  List<T> get evenElements {
    return <T>[for (int i = 0; i < this.length; i  = 2) this[i]];
  }
}

扩展方法是扩展成员

我们把这个功能称作 "扩展方法" 是因为,如果您在其他编程语言中使用过相应的语言功能,就会对这个术语感到熟悉。不过在 Dart 中,这个功能更加宽泛: 它还支持使用新的 getter、setter 以及运算符来扩展类。在上面那个 FancyList 的例子中,evenElements 就是一个 getter。下面则是一个例子,用来展示如何为 String 添加一个用于字符串移位的运算符:

代码语言:javascript复制
extension ShiftString on String {
  String operator <<(int shift) {
    return this.substring(shift, this.length)   this.substring(0, shift);
  }
}

来自社区的优秀范例

我们已经看到 Dart 社区的很多开发者们开始试用扩展方法。下面列出我们见过的几个优秀范例。

Jeremiah Ogbomo 创建了 time 代码包,它在 num (int 和 double 的基类) 上使用扩展,从而简化了 Duration 的创建过程。

代码语言:javascript复制
// Create a Duration via a `minutes` extension on num.
Duration tenMinutes = 10.minutes;

// Create a Duration via an `hours` extension on num.
Duration oneHourThirtyMinutes = 1.5.hours;

// Create a DateTime using a ` ` operator extension on DateTime.
final DateTime afterTenMinutes = DateTime.now()   10.minutes;

Marcelo Glasberg 创建了 i18n (国际化) 代码包,它使用扩展方法来简化字符串的本地化操作:

代码语言:javascript复制
Text('Hello'.i18n) // Displays Hello in English, Hola in Spanish, etc.

Simon Leier 创建了 dartx 代码包,其中包含了多个核心 Dart 类型的扩展,如:

代码语言:javascript复制
var allButFirstAndLast = list.slice(1, -2);   // [1, 2, 3, 4] 
var notBlank = '   .'.isBlank;       // false 
var file = File('some/path/testFile.dart'); 
print(file.name);           // testFile.dart 
print(file.nameWithoutExtension);       // testFile

Brian Egan 正在更新广受欢迎的 RxDart 代码包,使用扩展方法重新定义 API,以便更好地操作流。

  • time 代码包 https://pub.dev/packages/time
  • i18n (国际化) 代码包 https://www.reddit.com/r/FlutterDev/comments/dm288s/dart_extensions_applied_to_i18n_you_have/
  • dartx 代码包 https://pub.dev/packages/dartx

更安全的字符串截取操作

Dart 的标准 String 类使用 UTF-16 编码。这是编程语言的常见选择,特别是那些需要同时支持设备本地运行和 Web 端运行的编程语言。

  • UTF-16 编码 https://en.wikipedia.org/wiki/UTF-16

UTF-16 字符串通常运作良好,编码过程对于开发者来说是透明的。然而,在操作字符串时,特别是操作那些由用户输入的字符串时,您可能会发现,某些被用户认为是字符的东西,和相应的被 UTF-16 编码系统认为是字符单元的东西,其实并不一致。下面我们来看一个例子,从用户输入的字符串中截取前三个字符:

代码语言:javascript复制
var input = ['Resume'];
input.forEach((s) => print(s.substring(0, 3)));

$ dart main.dart
Res

目前看来没有问题;我们打印出了输入列表中的字符串上的前三个字母,结果是 Res。现在我们来想想,假如用户来自世界上不同的地区,他们输入的字符中可能包含自己语言特有的符号,比如韩语,他们甚至还会创造性地用表情符号组合来表达出 "简历" 的含义:

代码语言:javascript复制
// New longer input list:
var input = ['Resume', 'Résumé', '이력서', '??', 'Currículo'];

$ dart main.dart 
Res
Ré
이력서
?�
Cur

那问题来了。有些字符串处理正常,但是 Résumé 和 ?? 这些 "特殊" 字符串呢?先来看 Résumé,为什么我们的结果字符串里只有两个字符?再看看 ??,这个奇怪的问号又是怎么回事?这里的问题涉及到 Unicode 中的一些不为人知的秘密。Résumé 中的字符 é 其实会占据两个编码位: 一个 e,还有一个 "重音组合符" 。而纸卷图标 ?,确实只占据一个编码位,但这个编码却是用 U d83d 和 U dcc3 代理对 (surrogate pair) 来编码的。是不是被搞迷糊了?

  • Unicode 中的一些不为人知的秘密 https://eev.ee/blog/2015/09/12/dark-corners-of-unicode/
  • 重音组合符 https://unicode.org/cldr/utility/character.jsp?a=0301
  • 纸卷图标 emoji https://emojipedia.org/emoji/

    0 人点赞