跳到主要内容

模式

模式是 Dart 语言中的一个语法类别,类似于语句和表达式。模式表示一组值的形状,它可以与实际值进行匹配。

本页介绍

  • 模式的作用。
  • 模式在 Dart 代码中允许出现的位置。
  • 模式的常见用例是什么。

要了解不同种类的模式,请访问模式类型页面。

模式的作用

#

一般来说,根据上下文和模式的形状,模式可以匹配一个值,解构一个值,或两者兼有。

首先,模式匹配允许你检查给定值是否

  • 具有某种形状。
  • 是某个常量。
  • 等于其他某个东西。
  • 具有某种类型。

然后,模式解构为你提供了一种方便的声明式语法,可以将该值分解为其组成部分。在解构过程中,同一个模式还可以让你将变量绑定到部分或全部这些部分。

匹配

#

模式始终针对一个值进行测试,以确定该值是否具有你期望的形式。换句话说,你正在检查该值是否匹配该模式。

什么构成匹配取决于你使用的模式类型。例如,如果值等于模式的常量,则常量模式匹配

dart
switch (number) {
  // Constant pattern matches if 1 == number.
  case 1:
    print('one');
}

许多模式使用子模式,有时分别称为外部模式和内部模式。模式在其子模式上递归匹配。例如,任何集合类型模式的各个字段都可以是变量模式常量模式

dart
const a = 'a';
const b = 'b';
switch (obj) {
  // List pattern [a, b] matches obj first if obj is a list with two fields,
  // then if its fields match the constant subpatterns 'a' and 'b'.
  case [a, b]:
    print('$a, $b');
}

要忽略匹配值的某些部分,你可以使用通配符模式作为占位符。在列表模式的情况下,你可以使用rest 元素

解构

#

当对象和模式匹配时,模式可以访问对象的数据并将其分部分提取出来。换句话说,模式解构对象

dart
var numList = [1, 2, 3];
// List pattern [a, b, c] destructures the three elements from numList...
var [a, b, c] = numList;
// ...and assigns them to new variables.
print(a + b + c);

你可以在解构模式中嵌套任何类型的模式。例如,这个 case 模式匹配并解构一个包含两个元素的列表,其第一个元素是 'a''b'

dart
switch (list) {
  case ['a' || 'b', var c]:
    print(c);
}

模式可以出现的位置

#

你可以在 Dart 语言的多个地方使用模式

本节介绍使用模式进行匹配和解构的常见用例。

变量声明

#

你可以在 Dart 允许局部变量声明的任何地方使用模式变量声明。该模式与声明右侧的值进行匹配。一旦匹配,它将解构该值并将其绑定到新的局部变量。

dart
// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);

模式变量声明必须以 varfinal 开头,后跟一个模式。

变量赋值

#

变量赋值模式位于赋值的左侧。首先,它解构匹配的对象。然后,它将值分配给现有变量,而不是绑定新的变量。

使用变量赋值模式来交换两个变量的值,而无需声明第三个临时变量

dart
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".

Switch 语句和表达式

#

每个 case 子句都包含一个模式。这适用于switch 语句表达式,以及if-case 语句。你可以在 case 中使用任何类型的模式

Case 模式可驳斥的。它们允许控制流执行以下操作之一:

  • 匹配和解构正在 switch 的对象。
  • 如果对象不匹配,则继续执行。

模式在 case 中解构的值会变成局部变量。它们的作用域仅限于该 case 的主体内。

dart
switch (obj) {
  // Matches if 1 == obj.
  case 1:
    print('one');

  // Matches if the value of obj is between the
  // constant values of 'first' and 'last'.
  case >= first && <= last:
    print('in range');

  // Matches if obj is a record with two fields,
  // then assigns the fields to 'a' and 'b'.
  case (var a, var b):
    print('a = $a, b = $b');

  default:
}

逻辑或模式对于在 switch 表达式或语句中让多个 case 共享一个主体非常有用

dart
var isPrimary = switch (color) {
  Color.red || Color.yellow || Color.blue => true,
  _ => false,
};

Switch 语句可以有多个 case 共享一个主体而无需使用逻辑或模式,但它们对于允许多个 case 共享一个守卫仍然非常有用

dart
switch (shape) {
  case Square(size: var s) || Circle(size: var s) when s > 0:
    print('Non-empty symmetric shape');
}

守卫子句评估作为 case 一部分的任意条件,如果条件为 false,则不会退出 switch(就像在 case 主体中使用 if 语句会导致的那样)。

dart
switch (pair) {
  case (int a, int b):
    if (a > b) print('First element greater');
  // If false, prints nothing and exits the switch.
  case (int a, int b) when a > b:
    // If false, prints nothing but proceeds to next case.
    print('First element greater');
  case (int a, int b):
    print('First element not greater');
}

For 和 for-in 循环

#

你可以在for 和 for-in 循环中使用模式来迭代和解构集合中的值。

此示例在 for-in 循环中使用对象解构来解构 <Map>.entries 调用返回的 MapEntry 对象

dart
Map<String, int> hist = {'a': 23, 'b': 100};

for (var MapEntry(key: key, value: count) in hist.entries) {
  print('$key occurred $count times');
}

对象模式检查 hist.entries 是否具有命名类型 MapEntry,然后递归到命名字段子模式 keyvalue 中。它在每次迭代中调用 MapEntry 上的 key getter 和 value getter,并将结果分别绑定到局部变量 keycount

将 getter 调用的结果绑定到同名变量是一种常见的用例,因此对象模式也可以从变量子模式中推断出 getter 名称。这允许你将变量模式从像 key: key 这样的冗余形式简化为仅 :key

dart
for (var MapEntry(:key, value: count) in hist.entries) {
  print('$key occurred $count times');
}

模式的用例

#

上一节介绍了模式如何融入其他 Dart 代码结构。你看到了一些有趣的用例示例,例如交换两个变量的值,或解构 Map 中的键值对。本节介绍更多用例,回答了

  • 你可能想要使用模式的时间和原因
  • 它们解决什么类型的问题。
  • 它们最适合哪种习惯用法。

解构多个返回值

#

记录允许从单个函数调用中聚合和返回多个值。模式增加了将记录的字段直接解构为局部变量的能力,与函数调用内联。

与其像这样为每个记录字段单独声明新的局部变量

dart
var info = userInfo(json);
var name = info.$1;
var age = info.$2;

你可以使用变量声明赋值模式以及作为其子模式的记录模式,将函数返回的记录字段解构为局部变量

dart
var (name, age) = userInfo(json);

要使用模式解构具有命名字段的记录

dart
final (:name, :age) =
    getData(); // For example, return (name: 'doug', age: 25);

解构类实例

#

对象模式与命名对象类型匹配,允许你使用对象类已公开的 getter 来解构它们的数据。

要解构类的实例,请使用命名类型,后跟要解构的属性,并用括号括起来

dart
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');

代数数据类型

#

对象解构和 switch case 有利于以代数数据类型风格编写代码。在以下情况下使用此方法:

  • 你有一系列相关的类型。
  • 你有一个操作,需要为每种类型提供特定的行为。
  • 你希望将该行为集中在一个地方,而不是将其分散到所有不同的类型定义中。

与其为每种类型将操作实现为实例方法,不如将操作的变体保存在一个函数中,该函数在子类型上进行 switch

dart
sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.length);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => math.pi * r * r,
};

验证传入的 JSON

#

Maplist 模式非常适合解构反序列化数据中的键值对,例如从 JSON 解析的数据

dart
var data = {
  'user': ['Lily', 13],
};
var {'user': [name, age]} = data;

如果你知道 JSON 数据具有你期望的结构,则前面的示例是真实的。但数据通常来自外部来源,例如通过网络。你需要先验证它以确认其结构。

如果没有模式,验证会很冗长

dart
if (data is Map<String, Object?> &&
    data.length == 1 &&
    data.containsKey('user')) {
  var user = data['user'];
  if (user is List<Object> &&
      user.length == 2 &&
      user[0] is String &&
      user[1] is int) {
    var name = user[0] as String;
    var age = user[1] as int;
    print('User $name is $age years old.');
  }
}

单个 case 模式可以实现相同的验证。单个 case 最适合作为 if-case 语句。模式提供了一种更声明式且简洁得多的 JSON 验证方法

dart
if (data case {'user': [String name, int age]}) {
  print('User $name is $age years old.');
}

此 case 模式同时验证了

  • json 是一个 Map,因为它必须首先匹配外部 map 模式才能继续。
    • 而且,由于它是一个 Map,它也确认 json 不是 null。
  • json 包含键 user
  • user 与包含两个值的列表配对。
  • 列表值的类型是 Stringint
  • 用于保存值的新局部变量是 nameage