模式需要至少 3.0 的语言版本。
模式是 Dart 语言中的一个句法类别,就像语句和表达式一样。 模式表示一组值的形状,它可以与实际值匹配 值。
本页介绍:
- 模式的作用是什么。
- 在 Dart 代码中允许模式的位置。
- 模式的常见用例是什么。
要了解不同类型的模式,请访问模式类型页面。
模式的作用
通常,模式可以匹配值、解构值或两者兼而有之, 取决于图案的上下文和形状。
首先,模式匹配允许您检查给定值是否:
- 具有一定的形状。
- 是某个常数。
- 等于其他东西。
- 具有某种类型。
然后,模式解构为您提供了一种方便的声明式语法,用于 将该值分解为其组成部分。同样的图案也可以让你 将变量绑定到流程中的部分或全部部分。
匹配
模式始终针对值进行测试,以确定该值是否具有以下形式 你期望。换言之,您正在检查该值是否与模式匹配。
什么构成匹配取决于你使用的模式类型。 例如,如果常量模式的值等于模式的 不断:
switch (number) {
// Constant pattern matches if 1 == number.
case 1:
print('one');
}
许多模式都使用子模式,有时分别称为外部模式和内部模式。模式在其子模式上递归匹配。 例如,任何集合类型模式的各个字段都可以是变量模式或常量模式:
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 元素。
解构
当对象和模式匹配时,该模式可以访问对象的数据 并将其分批提取。换言之,模式对对象进行了解构:
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);
您可以将任何类型的模式嵌套在解构模式中。 例如,此案例模式匹配并解构两个元素 列表,其第一个元素为 或 :'a''b'
switch (list) {
case ['a' || 'b', var c]:
print(c);
}
可以出现的位置模式
您可以在 Dart 语言的多个位置使用模式:
- 局部变量声明和赋值
- for 和 for-in 循环
- if-case 和 switch-case
- 集合文本中的控制流
本节介绍与模式匹配和解构的常见用例。
变量声明
你可以在 Dart 允许局部变量的任何地方使用模式变量声明 声明。 该模式与声明右侧的值匹配。 匹配后,它会解构该值并将其绑定到新的局部变量:
// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);
模式变量声明必须以 either 或 开头,后跟 通过模式。varfinal
变量赋值
变量赋值模式位于赋值的左侧。 首先,它解构匹配的对象。然后,它将值分配给现有变量,而不是绑定新变量。
使用变量赋值模式交换两个变量的值,而不 宣布第三个临时的:
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".
切换语句和表达式
每个 case 子句都包含一个模式。这适用于 switch 语句和表达式,以及 if-case 语句。 您可以在案例中使用任何类型的模式。
案例模式是可以反驳的。 它们允许控制流:
- 匹配并解构正在打开的对象。
- 如果对象不匹配,请继续执行。
模式在案例中解构的值将成为局部变量。 其范围仅在该案件的正文范围内。
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:
}
逻辑 or 模式对于让多个事例共享 switch 表达式或语句中的正文:
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false
};
Switch 语句可以让多个事例共享一个主体,而无需使用逻辑或模式,但它们是 对于允许多个案例共享一个防护装置仍然特别有用:
switch (shape) {
case Square(size: var s) || Circle(size: var s) when s > 0:
print('Non-empty symmetric shape');
}
保护条款将任意条件作为案例的一部分进行评估,而不 如果条件为 false,则退出交换机 (就像在案例正文中使用语句会导致)。if
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 循环中使用对象解构来解构 调用返回的 MapEntry 对象:
Map<String, int> hist = {
'a': 23,
'b': 100,
};
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
对象模式检查具有命名类型 , 然后递归到命名字段子模式和 . 它在每次迭代中调用 getter 和 getter, 并将结果分别绑定到局部变量 和 。hist.entriesMapEntrykeyvaluekeyvalueMapEntrykeycount
将 getter 调用的结果绑定到同名变量是很常见的 用例,因此对象模式也可以从变量子模式中推断 getter 名称。这允许您简化变量模式 从冗余的东西到只是:key: key:key
for (var MapEntry(:key, value: count) in hist.entries) {
print('$key occurred $count times');
}
模式的用例
上一节描述了模式如何适应其他 Dart 代码结构。 您看到了一些有趣的用例作为示例,例如交换两个变量的值,或在映射中解构键值对。本节将介绍更多用例,并回答:
- 何时以及为何可能希望使用模式。
- 他们解决了什么样的问题。
- 他们最适合哪些成语。
解构多个返回
记录允许从单个函数聚合和返回多个值 叫。模式增加了对记录字段进行结构化的功能 直接进入局部变量,与函数调用内联。
而不是为每个记录字段单独声明新的局部变量, 喜欢这个:
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
您可以将函数返回的记录字段解构为本地 使用变量声明或同化模式的变量,以及记录模式作为其子模式的变量:
var (name, age) = userInfo(json);
解构类实例
对象模式与命名对象类型匹配,允许 您可以使用对象的类已经公开的 getter 来解构他们的数据。
若要解构类的实例,请使用命名类型, 后跟属性 括在括号中的解构:
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
代数数据类型
对象解构和切换情况有利于写入 代数数据类型样式的代码。 在以下情况下使用此方法:
- 您有一个相关类型的系列。
- 您有一个操作,该操作需要每种类型的特定行为。
- 您希望将该行为分组到一个地方,而不是将其分散到所有位置 不同的类型定义。
而不是将操作实现为每种类型的实例方法, 将操作的变体保留在切换子类型的单个函数中:
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
映射和列表模式非常适合解构 JSON数据:
var json = {
'user': ['Lily', 13]
};
var {'user': [name, age]} = json;
如果您知道 JSON 数据具有您期望的结构, 前面的例子是现实的。 但数据通常来自外部来源,例如通过网络。 您需要先验证它以确认其结构。
如果没有模式,验证是冗长的:
if (json is Map<String, Object?> &&
json.length == 1 &&
json.containsKey('user')) {
var user = json['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.');
}
}
单个案例模式可以实现相同的验证。 单个案例作为 if-case 语句效果最好。 模式提供声明性更强、更不冗长 验证JSON的方法:
if (json case {'user': [String name, int age]}) {
print('User $name is $age years old.');
}
此案例模式同时验证:
- json是一个映射,因为它必须首先与外部映射模式匹配才能继续。而且,由于它是一张地图,它还确认它不是 null。json
- json包含一个键。user
- 键与两个值的列表配对。user
- 列表值的类型为 和 。Stringint
- 保存这些值的新局部变量是 和 。Stringint