Dart 2.7发布:更安全,更好用的Dart

原文(节选):Announcing Dart 2.7: A safer, more expressive Dart
翻译:老黑牛
声明:侵权联删

Dart 2.7 添加了对扩展方法的支持,同时新增了一个处理特殊字符的包。在null安全方面我们也做了更新,并在DartPad中带来了全新的null安全体验。在生态系统方面,pub.dev添加了一个新的Like特性:你可以给你喜欢的packages反馈。今天,Dart2.7 SDK可以从dart.dev下载,而且Dart2.7 SDK也被内嵌入了Flutter 1.12 release版本中。
dart 2.7 SDK

扩展方法

Dart 2.7添加了一个期待已久的,功能强大的新语言特性:extension methods.这使您能够向任何类型添加新功能——甚至是您无法控制的类型——并具有常规方法调用的简洁性和自动完成的体验。
让我们来看一个小例子----给String添加转化 ints 和 doubles 的支持。作为app开发人员,我们不能改变 String,因为它是声明在 dart:core库里的,但是我们可以使用扩展方法来扩展它!一旦我们定义了这个拓展,我们可以在String类型上调用我们的新方法parseInt,就像String类型自己定义的那样。

extension ParseNumbers on String {
  int parseInt() {
    return int.parse(this);
  }
  double parseDouble() {
    return double.parse(this);
  }
}
main() {
  int i = '42'.parseInt();
  print(i);
}

扩展方法是静态的

扩展方法被静态的解析和分发,这就意味着你不能在dynamic类型的变量上面使用扩展方法。下面这个调用在运行时会抛出异常:

dynamic d = '2';
d.parseInt();
→ Runtime exception: NoSuchMethodError

扩展方法可以很好地与Dart的类型推断一起使用,因此在下面的变量v可以推断出类型为String,而且String的扩展是它也是能使用的:

var v = '1';
v.parseInt(); // Works!

扩展可以有类型变量

想象一下我们想给List声明一个扩展,这个扩展方法可以获取每一个索引对应的元素。我们想让这个扩展方法可以在任何类型的list上都能工作,想让它返回一个新的并且和输入的list相同类型的list。我们可以通过使这个拓展范型化,并将其类型参数应用给它扩展的类型和扩展方法来实现这一点:

extension FancyList<T> on List<T> {
  List<T> get evenElements {
    return <T>[for (int i = 0; i < this.length; i += 2) this[i]];
  }
}

扩展方法实际上是扩展属性

我们之所以称这个特性为扩展方法,是因为如果您在其他编程语言中使用过相应的语言特性,那么这是一个熟悉的术语。但是在Dart中,该特性更加通用:它还支持gettersetter运算符的扩展。在上面的FancyList示例中,evenElements就是是一个getter的扩展。下面是一个为了移位字符串而添加运算符的例子:

extension ShiftString on String {
  String operator <<(int shift) {
    return this.substring(shift, this.length) + this.substring(0, shift);
  }
}

来自社区的优秀例子

我们已经看到过一些来自Dart社区的开发者使用扩展方法做了些实验。这有一些目前我们看到的很酷的使用。
Jeremiah Ogbomo创建了 time package,可以在numintsdoubles的基类)上面使用这些扩展来简化 Duration 对象的创建:

// 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 (internationalization) package,可以用来简化字符串的本地化操作:

Text('Hello'.i18n) // Displays Hello in English, Hola in Spanish, etc.

Simon Leier 创建了 dartx package,它包含了一些核心Dart类型的扩展。一些例子:

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 package 的相关API,重新定义它们为了可以使用streams。

安全的截取字符串处理

Dart的标准字符串类使用UTF-16编码。这在编程语言中是一种常见的选择,尤其是那些既支持在设备上本地运行又支持在web上运行的语言。
UTF-16字符串通常工作得很好,而且编码对开发人员是透明的。然而,在操作字符串时,特别是在操作用户输入的字符串时,您可能会体验到用户所感知到的字符与UTF-16中编码为代码单元的字符之间的差异。让我们来看一个小例子,提取用户输入的字符串的前三个字符:

var input = ['Resume'];
input.forEach((s) => print(s.substring(0, 3)));
$ dart main.dart
Res

到目前为止没有问题;我们在输入列表中打印了字符串的前三个字符,结果是Res.现在让我们考虑来自不同地区的用户,他们可能会输入包含口音、韩文(韩语)甚至是代表“resume”概念的表情符号组合的字符串:

// New longer input list:
var input = ['Resume', 'Résumé', '이력서', '💼📃', 'Currículo'];
$ dart main.dart
Res
Ré
이력서
💼�
Cur

嗯,一些是正常的,但是对于Résumé💼📃而言发生了什么呢?对于Résumé而言,为什么我们得到了两个字符?对于💼📃,这个奇怪的问号又是怎么回事?这里的问题在于dark corners of Unicode.Résumé里的重音 确实占两位: e组合的重音. 至于 📃, 一个卷纸表情,是一个刚好用U+d83d U+dcc3的代理对进行编码的单一代码点。困惑吗?
正如我们所说的,通常不需要担心字符和代码点。如果您所做的只是接收、分发和传递整个字符串,那么内部编码是透明的。但是,如果需要遍历字符串的字符或操作字符串的内容,就会遇到麻烦。好消息是,Dart 2.7引入了一个新的包—— characters 用于处理这些情况。这个包支持将字符串视为用户感知字符的序列,也称为Unicode字符簇。有了字符包,我们可以在截短代码上做一点改变就能修复上面的问题:

// Before:
input.forEach((s) => print(s.substring(0, 3)));
// After, using the characters package:
input.forEach((s) => print(s.characters.take(3)));

首先,我们从s中的字符串创建一个新的Characters实例(使用方便的.characters扩展方法)。然后我们使用灵巧的take()方法来提取初始的三个字符。
这个新包的技术预览版可以在pub.dev上找到。我们很想听听你对这个包的看法。如果您发现任何问题,请提出来

空安全预览

几个月前,我们宣布了在Dart中支持空安全的意图,增加了对安全访问对象引用而不触发空引用异常的支持。今天,我们将为您提供一种预览空安全静态分析的方法。让我们来看看一个激励人的小例子:

void main() {
  Person('Larry', birthday: DateTime(1973, 03, 26)).describe();
  Person('Sergey').describe();
}
class Person {
  String firstName;
  DateTime birthday;
  Person(this.firstName, {this.birthday});
  void describe() {
    print(firstName);
    int birthyear = birthday?.year;
    print('Born ${DateTime.now().year - birthyear} years ago');
  }
}

如果我们运行这段代码,当描述第二个人的时候会因为空指针异常而崩溃,因为第二个人没设置生日信息。我们犯了一个编码错误:当我们通过将构造函数中的birthday属性设置为可选并且使用birthday?.year来测试空的生日属性的时候,是能预见到某些人可能有未知的生日,我们忘记了去处理birthday为null的场景。
让我们把这些代码粘贴到我们新的null safety playground,,它是一个具备空安全静态分析预览技术的特殊DartPad构建。甚至不用运行代码,我们可以看到三个issues:
DartPad with null safety showing three analysis errors related to nulls
通过修复这些分析错误,我们可以开始利用null安全性。尝试在null safety playground进行以下编辑(最终得到这个安全代码):

  1. 声明生日属性或许为空,将 DateTime birthday 改成 DateTime? birthday
  2. 声明birthyear属性或许为空,当birthday为空时,将 int birthyear 改为 int? birthyear
  3. 使用空探测包装最后的输出调用:if (birthyear != null) {…}

我们希望这个例子能很好地说明我们想要的null安全体验。如前所述,这个playground只是null安全的一部分的早期技术预览,因为它正在建设中。我们正在努力在Dart SDK中完成第一个null安全beta版。这是我们正在做的beta版:

  1. 完成可空引用和不可空引用的完整实现

  2. 将null安全性集成到Dart的类型推断和智能提升中(例如,允许在赋值或空值检查后安全访问可为空的变量)

  3. 移植Dart核心库来声明哪些类型可以为空,哪些类型不能为空

  4. 添加一个迁移工具,它可以自动完成移植Dart应用程序和包的大部分升级任务

一旦这项工作完成,我们将在beta SDK中提供它,您在您的app和包中使用这个特性。我们也计划保持空安全的playground的更新,当新特点被实现完成后。
同时我们确信许多开发者将想使用null safety一旦他可以获得的时候,您可以在任何方便的时候进行迁移,并在准备好时选择使用该特性。尚未加入该特性的库和包将能够依赖于已加入的库,反之亦然。
在接下来的几个月里,我们将更多地讨论null安全性,包括关于如何准备转换的更详细的建议。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值