Dart 3 新特性 switch

更新时间:2023-11-04 12:48

Dart 3被描述为迄今为止最大的Dart版本。

这个版本引入了一些重要的特性,例如:

  • 模式和记录
  • 增强的switch和if-case语句
  • 解构
  • 封闭类和其他类修饰符

这些特性在Flutter Forward首次宣布,并我很高兴现在可以在Flutter 3.10和Dart 3.0正式发布后使用它们。

实际上,Dart 3.0允许我们编写更具表现力和优雅的代码,但如果想充分利用它,会有一些学习曲线。

由于需要涵盖的内容很多,我决定写一系列新文章,详细介绍如何使用每个特性,以及您在现实世界中可能遇到的示例和实际用例。

至于本文,我想为您展示一些Dart 3.0提供的特点。

换句话说,这篇文章是前菜。接下来的将是主菜(和甜点 🍰)。

准备好了吗?让我们开始吧!

在您的Flutter项目中启用Dart 3

要使用这些新特性,您需要在您的pubspec.yaml文件中启用Dart 3.0:


environment:
    sdk: '>=3.0.0 <4.0.0'

在创建一个新的Flutter 3.10项目时,默认启用Dart 3.0。

有了这个设置,让我们来了解一下新的语言特性。🔥

Dart 3中的Records简介

Records非常灵活,可以在许多不同的场景中使用。

举个例子,想象一下,你正在进行网络请求并获得一些JSON数据,它看起来像这样:


final json = {'name': 'Andrea', 'age': 38, 'height': 184};

如果您直接使用 json 变量,Dart 会将其推断为 Map,从而失去类型安全性。

为了改进这一点,您可以声明一个 Person 类,并在一个工厂构造函数内实现反序列化逻辑:


class Person {
    const Person({required this.name, required this.age, required this.height});
    final String name;
    final int age;
    final int height;
    factory Person.fromJson(Map<String, dynamic> json) {
      // * error handling code missing for simplicity
      return Person(
        name: json['name'],
        age: json['age'],
        data-height: json['height'],
      );
    }
  }

(String, int, int) getPerson(Map<String, dynamic> json) {
    return (
      json['name'],
      json['age'],
      json['height'],
    );
  }

final person = getPerson(json);
  print(person.$1); // 'Andrea'
  print(person.$2); // 38
  print(person.$3); // 184

或者,您可以像这样解构返回的值:


final (name, age, height) = getPerson(json);
  print(name); // 'Andrea'
  print(age); // 38
  print(height); // 184

但还有更多。👇

位置参数与命名参数

在上面的示例中,记录类型仅使用了位置参数(在其他编程语言中,这被称为元组)。

但 Dart 记录也可以使用命名参数。

这意味着如果我们希望,我们可以更改函数的返回类型:


// same example as above, now using named arguments
  ({String name, int age, int height}) getPerson(Map<String, dynamic> json) {
    return (
      name: json['name'],
      age: json['age'],
      data-height: json['height'],
    );
  }

因此,我们可以像这样获取和打印记录的数值:


final person = getPerson(json);
  print(person.name); // 'Andrea'
  print(person.age); // 38
  print(person.height); // 184

// * we'll cover the : syntax in a follow up article
  final (:name, :age, :height) = getPerson(json);
  print(name); // 'Andrea'
  print(age); // 38
  print(height); // 184

总体来说,可以将记录视为类的轻量级替代品。

MapList相比,记录更加类型安全,因为它们允许您指定值的数量以及它们的类型。

它们支持命名参数和位置参数(或二者的组合),就像常规函数参数一样。

正如我们所见,您可以使用解构来进一步简化您的代码。

但如果您想要正确使用记录并避免语法错误,需要遵循一些规则。因此,我将在即将发布的文章中更详细地介绍它们。

引介:Switch 表达式和模式

假设我们正在编写一个二维游戏,玩家可以向上、向下、向左和向右移动。

这可以很容易地表示为一个枚举:


enum Move { up, down, left, right }

现在,假设我们想根据当前的移动来计算特定的偏移量(作为一对x-y坐标)。在Dart 3之前,我们可能会像这样实现所需的代码:


enum Move {
    up,
    down,
    left,
    right;
    Offset get offset {
      switch (this) {
        case up:
          return const Offset(0.0, 1.0);
        case down:
          return const Offset(0.0, -1.0);
        case left:
          return const Offset(-1.0, 0.0);
        case right:
          return const Offset(1.0, 0.0);
      }
    }
  }

enum Move {
    up,
    down,
    left,
    right;
    Offset get offset => switch (this) {
          up => const Offset(0.0, 1.0),
          down => const Offset(0.0, -1.0),
          left => const Offset(-1.0, 0.0),
          right => const Offset(1.0, 0.0),
        };
  }

只想在X轴上进行移动吗?那么可以在switch语句内使用逻辑运算符来利用模式匹配:


double get xAxisMovement => switch (this) {
          up || down => 0.0, // logical OR operator with pattern matching
          left => -1.0,
          right => 1.0,
        };

Dart 3的switch表达式功能更加强大。

因此,我将在另一篇文章中详细介绍它们。👍

介绍封闭类

封闭类有助于您检查穷尽性,以便您可以处理所有可能的情况。

在处理代码中的异常时,这特别重要。

例如,您可以使用封闭类来定义后端可能返回的所有可能的身份验证异常:


sealed class AuthException implements Exception {}
  class EmailAlreadyInUseException extends AuthException {
    EmailAlreadyInUseException(this.email);
    final String email;
  }
  class WeakPasswordException extends AuthException {}
  class WrongPasswordException extends AuthException {}
  class UserNotFoundException extends AuthException {}

这让你可以使用 switch 表达式处理每种可能的异常类型:


String describe(AuthException exception) {
    return switch (exception) {
      EmailAlreadyInUseException(email: final email) =>
        'Email already in use: $email',
      WeakPasswordException() => 'Password is too weak',
      WrongPasswordException() => 'Wrong password',
      UserNotFoundException() => 'User not found',
    };
  }

以上的代码之所以有效,是因为AuthException类被声明为sealed

如果没有这个声明,我们将会得到一个non_exhaustive_switch_expression错误:

如果基类没有被声明为sealed,使用switch表达式时将会产生non_exhaustive_switch_expression错误。但如果我们将基类标记为sealed,编译器就会知道我们已经处理了所有可能的情况。

声明一个sealed类有两个重要的含义:

  • 该类变为抽象类(无法创建具体实例)
  • 所有的子类必须在同一个库(文件)中定义。

关于sealed类还有更多内容,敬请期待更深入的文章。😊

Dart 3中的类修饰符简介

在Dart 3之前,只有两个类修饰符可用:abstractmixin

但在新版本中,我们有六个:

  • abstract(抽象)
  • base(基类)
  • final(最终)
  • interface(接口)
  • sealed(密封)
  • mixin(混入)

方便的是,Dart官网有一个新页面解释了所有这些修饰符的工作原理:

我可能会在将来更详细地介绍这些修饰符,但目前来说,官方页面是一个很好的起点。

Dart 3: 下一步

通过这篇文章,我希望为您展示了在Dart 3中可以做些什么。

希望我的介绍能激发您尝试在项目中使用这些新语言特性的兴趣。

但还有很多需要探讨的内容,包括if-case语句、如何对可空值进行模式匹配以及其他高级用法。除非您深刻理解它们的工作原理,否则可能会遇到困难。