跳至主要內容

模式

模式是 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:
}

邏輯 OR 模式對於在 switch 表達式或語句中讓多個 case 共用一個主體很有用

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

Switch 語句可以讓多個 case 共用一個主體,而無需使用邏輯 OR 模式,但它們仍然對於允許多個 case 共用guard 具有獨特的用處

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

Guard 子句評估作為 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 迴圈中使用物件解構來解構 MapEntry 物件,<Map>.entries 呼叫會傳回這些物件

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);

若要使用模式解構具有具名字段的記錄

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 是一個地圖,因為它必須首先匹配外部地圖模式才能繼續。
    • 而且,由於它是一個地圖,它也確認 json 不是 null。
  • json 包含鍵 user
  • user 與兩個值的列表配對。
  • 列表值的類型為 Stringint
  • 用於保存值的新區域變數為 nameage