內容

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

您可以在解構模式中嵌套 任何類型的模式。例如,此案例模式比對並解構一個二元素清單,其第一個元素為 '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:
}

邏輯或模式 可用於讓多個 case 共用 switch 運算式或陳述式中的主體

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 程式碼建構。您看到一些有趣的用例做為範例,例如 交換 兩個變數的值,或 解構地圖中的鍵值對。本節說明更多用例,回答

  • 何時以及為何您可能想使用模式。
  • 它們解決哪些類型的問題。
  • 它們最適合哪些慣用語。

解構多個回傳值

#

記錄允許從單一函式呼叫中彙總和 傳回多個值。模式新增了將記錄欄位直接解構到區域變數中的能力,與函式呼叫內聯。

與為每個記錄欄位個別宣告新的區域變數不同,如下所示

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

您可以使用 變數宣告指派模式,以及 記錄模式 做為其子模式,將函式傳回的記錄欄位解構到區域變數中

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

解構類別實例

#

物件模式 與命名的物件類型相符,讓您可以使用物件類別已公開的 getter 解構其資料。

若要解構類別的執行個體,請使用命名類型,後接括在括號中的要解構的屬性

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

代數資料類型

#

物件解構和 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 json = {
  'user': ['Lily', 13]
};
var {'user': [name, age]} = json;

如果您知道 JSON 資料具有您預期的結構,則前一個範例是實際的。但資料通常來自外部來源,例如網路。您需要先驗證它以確認其結構。

沒有模式,驗證會很囉嗦

dart
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.');
  }
}

單一的 case 模式 可以達成相同的驗證。單一 case 最適合作為 if-case 陳述式。模式提供更具宣告性且更簡潔的 JSON 驗證方法

dart
if (json 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