跳到主要內容

模式

模式是 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');
}

要忽略匹配值的部分,您可以使用萬用字元模式作為佔位符。對於列表模式,您可以使用其餘元素

解構

#

當一個物件和模式匹配時,模式可以訪問物件的資料並將其部分提取出來。換句話說,模式*解構*物件。

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 模式*是可駁回的。它們允許控制流

  • 匹配並解構被切換的物件。
  • 如果物件不匹配,則繼續執行。

模式在 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 的一部分評估任意條件,如果條件為假,則不會退出 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 有助於編寫代數資料型別風格的程式碼。在以下情況使用此方法:

  • 您有一系列相關型別。
  • 您有一個操作,需要針對每種型別進行特定行為。
  • 您希望將該行為集中在一處,而不是分散到所有不同的型別定義中。

無需為每種型別實現操作作為例項方法,而是將操作的變體保留在一個函式中,該函式切換子型別:

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