跳到主要內容

記錄

記錄是一種匿名、不可變、聚合型別。像其他 集合型別 一樣,它們允許你將多個物件打包成一個物件。與其他集合型別不同,記錄是固定大小、異構且型別化的。

記錄是實際的值;你可以將它們儲存在變數中,巢狀它們,在函式之間傳遞它們,以及將它們儲存在列表、對映和集合等資料結構中。

記錄語法

#

記錄表示式 是用逗號分隔的命名或位置欄位列表,用括號括起來

dart
var record = ('first', a: 2, b: true, 'last');

記錄型別註解 是用逗號分隔並用括號括起來的型別列表。你可以使用記錄型別註解來定義返回型別和引數型別。例如,以下 (int, int) 語句是記錄型別註解

dart
(int, int) swap((int, int) record) {
  var (a, b) = record;
  return (b, a);
}

記錄表示式和型別註解中的欄位反映了 函式中引數和實參 的工作方式。位置欄位直接放在括號內

dart
// Record type annotation in a variable declaration:
(String, int) record;

// Initialize it with a record expression:
record = ('A string', 123);

在記錄型別註解中,命名欄位位於用花括號分隔的型別-名稱對部分中,緊隨所有位置欄位之後。在記錄表示式中,名稱位於每個欄位值之前,後面跟一個冒號

dart
// Record type annotation in a variable declaration:
({int a, bool b}) record;

// Initialize it with a record expression:
record = (a: 123, b: true);

記錄型別中命名欄位的名稱是 記錄型別定義 或其 形狀 的一部分。兩個具有不同命名欄位名稱的記錄具有不同的型別

dart
({int a, int b}) recordAB = (a: 1, b: 2);
({int x, int y}) recordXY = (x: 3, y: 4);

// Compile error! These records don't have the same type.
// recordAB = recordXY;

在記錄型別註解中,你也可以命名 位置 欄位,但這些名稱純粹用於文件,不影響記錄的型別

dart
(int a, int b) recordAB = (1, 2);
(int x, int y) recordXY = (3, 4);

recordAB = recordXY; // OK.

這類似於 函式宣告或函式型別定義 中的位置引數可以有名稱,但這些名稱不影響函式的簽名。

有關更多資訊和示例,請檢視 記錄型別記錄相等性

記錄欄位

#

記錄欄位可透過內建的 getter 訪問。記錄是不可變的,因此欄位沒有 setter。

命名欄位公開同名 getter。位置欄位公開名稱為 $<position> 的 getter,跳過命名欄位

dart
var record = ('first', a: 2, b: true, 'last');

print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'

為了進一步簡化記錄欄位訪問,請檢視 模式 頁面。

記錄型別

#

沒有針對單個記錄型別的型別宣告。記錄是根據其欄位的型別進行結構化型別化的。記錄的 形狀(其欄位集、欄位型別以及它們的名稱(如果有))唯一地決定了記錄的型別。

記錄中的每個欄位都有自己的型別。同一記錄中的欄位型別可以不同。型別系統在從記錄訪問每個欄位時都瞭解其型別

dart
(num, Object) pair = (42, 'a');

var first = pair.$1; // Static type `num`, runtime type `int`.
var second = pair.$2; // Static type `Object`, runtime type `String`.

考慮兩個不相關的庫,它們建立的記錄具有相同的欄位集。型別系統理解這些記錄是相同的型別,即使這些庫彼此不耦合。

記錄相等性

#

如果兩個記錄具有相同的 形狀(欄位集)並且其相應欄位具有相同的值,則它們相等。由於命名欄位的 順序 不是記錄形狀的一部分,因此命名欄位的順序不影響相等性。

例如

dart
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);

print(point == color); // Prints 'true'.
dart
({int x, int y, int z}) point = (x: 1, y: 2, z: 3);
({int r, int g, int b}) color = (r: 1, g: 2, b: 3);

print(point == color); // Prints 'false'. Lint: Equals on unrelated types.

記錄根據其欄位的結構自動定義 hashCode== 方法。

多重返回

#

記錄允許函式返回捆綁在一起的多個值。要從返回中檢索記錄值,請使用 模式匹配 將值 解構 到區域性變數中。

dart
// Returns multiple values in a record:
(String name, int age) userInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['age'] as int);
}

final json = <String, dynamic>{'name': 'Dash', 'age': 10, 'color': 'blue'};

// Destructures using a record pattern with positional fields:
var (name, age) = userInfo(json);

/* Equivalent to:
  var info = userInfo(json);
  var name = info.$1;
  var age  = info.$2;
*/

你還可以使用記錄的 命名欄位,透過冒號 : 語法來解構記錄,你可以在 模式型別 頁面瞭解更多資訊

dart
({String name, int age}) userInfo(Map<String, dynamic> json)
// ···
// Destructures using a record pattern with named fields:
final (:name, :age) = userInfo(json);

你可以在不使用記錄的情況下從函式返回多個值,但其他方法存在缺點。例如,建立類會更加冗長,而使用 ListMap 等其他集合型別會失去型別安全。

記錄作為簡單資料結構

#

記錄只儲存資料。當你只需要資料時,它們立即可用且易於使用,無需宣告任何新類。對於一個所有元組都具有相同形狀的簡單資料列表,*記錄列表* 是最直接的表示。

例如,以下是“按鈕定義”列表

dart
final buttons = [
  (
    label: "Button I",
    icon: const Icon(Icons.upload_file),
    onPressed: () => print("Action -> Button I"),
  ),
  (
    label: "Button II",
    icon: const Icon(Icons.info),
    onPressed: () => print("Action -> Button II"),
  )
];

這段程式碼可以直接編寫,無需任何額外的宣告。

記錄與型別定義

#

你可以選擇使用 型別定義 來為記錄型別本身命名,並使用該名稱而不是完整地寫出記錄型別。這種方法允許你宣告某些欄位可以為 null (?),即使列表中當前沒有任何條目具有 null 值。

dart
typedef ButtonItem = ({String label, Icon icon, void Function()? onPressed});
final List<ButtonItem> buttons = [
  // ...
];

由於記錄型別是結構型別,因此像 ButtonItem 這樣的命名只是引入了一個別名,使其更容易引用結構型別:({String label, Icon icon, void Function()? onPressed})

讓所有程式碼都透過別名引用記錄型別,可以更輕鬆地在以後更改記錄的實現,而無需更新每個引用。

程式碼可以像處理簡單類例項一樣處理給定的按鈕定義

dart
List<Container> widget = [
  for (var button in buttons)
    Container(
      margin: const EdgeInsets.all(4.0),
      child: OutlinedButton.icon(
        onPressed: button.onPressed,
        icon: button.icon,
        label: Text(button.label),
      ),
    ),
];

你甚至可以在以後決定將記錄型別更改為類型別以新增方法

dart
class ButtonItem {
  final String label;
  final Icon icon;
  final void Function()? onPressed;
  ButtonItem({required this.label, required this.icon, this.onPressed});
  bool get hasOnpressed => onPressed != null;
}

或更改為 擴充套件型別

dart
extension type ButtonItem._(({String label, Icon icon, void Function()? onPressed}) _) {
  String get label => _.label;
  Icon get icon => _.icon;
  void Function()? get onPressed => _.onPressed;
  ButtonItem({required String label, required Icon icon, void Function()? onPressed})
      : this._((label: label, icon: icon, onPressed: onPressed));
  bool get hasOnpressed => _.onPressed != null;
}

然後使用該型別的建構函式建立按鈕定義列表

dart
final List<ButtonItem> buttons =  [
  ButtonItem(
    label: "Button I",
    icon: const Icon(Icons.upload_file),
    onPressed: () => print("Action -> Button I"),
  ),
  ButtonItem(
    label: "Button II",
    icon: const Icon(Icons.info),
    onPressed: () => print("Action -> Button II"),
  )
];

同樣,所有這些操作都不需要更改使用該列表的程式碼。

更改任何型別都要求使用它的程式碼非常小心,不要做出假設。類型別名不能為將其用作引用的程式碼提供任何保護或保證,即別名值是記錄。擴充套件型別也提供很少的保護。只有類才能提供完整的抽象和封裝。