【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】
探索 Dart 中的集合

如果您曾經調用 add()
、addAll()
、map()
或 toList()
來建立列表或映射,您可能需要查看 collection if、collection for 和 spreads。去年,Dart 在 2.3 版中加入了這些功能。
在本文中,我們將研究集合,探索這些新功能,並查看一些有趣的範例。透過掌握這些功能,您可以使您的程式碼更簡潔、更易於閱讀。
集合
首先,我們需要了解什麼是集合。集合 是一個包含其他物件的物件。例如:
- List:具有長度的有序物件集合(也稱為 陣列)
- Set:唯一物件的無序集合
- Map:鍵值對的無序集合
- Queue:可以在兩端添加/移除物件的有序集合
- SplayTreeMap:基於自平衡二元樹的鍵值對的有序集合
這些類型在 dart:collection
套件中可用。如需更多集合類型,請在 pub.dev 上查看 package:collection。
這些集合類型都實作了 Iterable,它提供了一些常用行為,例如在集合中的每個物件上運行函數、獲取第一個物件、確定集合的長度等等。
集合字面量
Dart 支援用於構造三種類型集合的語法:列表字面量 ([]
)、映射字面量 ({}
) 和集合字面量(也是 {}
)。
以下是一個列表字面量:
1 | List<String> getArtists() { |
以下是一個映射字面量:
1 | Map<String, String> getArtistsByPainting { |
以下是一個集合字面量,在 Dart 2.3 中加入:
1 | Set<String> getArtistsSet() { |
如果您想知道為什麼映射和集合可以使用相同的 {}
語法,那是因為 Dart 使用 類型推斷 來區分。類型系統根據參數 a 和 b 的類型確定類型。它通常可以根據內容確定這一點——例如,{1}
顯然是一個 Set
,而 {1: 2}
顯然是一個 Map
。
注意: 使用
{}
預設構造一個映射。要建立一個集合,您可以使用泛型類型註釋:<String>{}
。使用兩個泛型類型參數則建立一個映射:<String, String>{}
。
元素的類型
集合字面量中的每個項目通常是一個值或表達式,但也可以是以下新功能之一:collection if、collection for 或 spread。所有這些都被稱為 元素。
每個元素解包零個或多個項目,並將它們放入周圍的集合中。例如,一個字串字面量(例如「oatmeal」)會產生一個項目,但 collection for 會解包 0 個或多個項目。這些功能也可以以有趣的方式組合,我們將在下面探討。
Spreads
spread 接收一個集合(例如,一個列表),並將其內容放入周圍的集合中:
1 | List<String> combineLists(List<String> a, List<String> b) { |
前面的程式碼相當於:
1 | List<String> combineLists(List<String> a, List<String> b) { |
您也可以在映射和集合字面量中使用 spreads:
1 | Map<String, String> combineMaps(Map<String, String> a, Map<String, String> b) { |
1 | Set<String> combineSets(Set<String> a, Set<String> b) { |
在映射和集合中,當發生衝突時,b 的內容會覆蓋 a 中的內容。例如,調用 combineMaps({'foo': 'bar'}, {'foo': 'baz'})
會產生一個包含 {'foo': 'baz'}
的映射。
Null-aware spreads (…?)
null-aware spread 僅當運算符後的表達式為非 null 時才將內容加入到集合中:
1 | List<String> combineIfExists(List<String> a, List<String> b) { |
1 | void main() { |
Collection if
使用 if
、else
和 else if
關鍵字根據條件將某些內容加入到集合中。以下是一個使用 collection if 的範例:
1 | class Article { |
可以在末尾加入 else
關鍵字:
1 | String toString() { |
請注意逗號的位置。逗號不能在 title
之後,因為 else
是同一個元素的一部分。將 if
和 else
保持在一起,在逗號之前,可以將它們與集合中的下一個元素區分開來。
加入 else if
也可以:
1 | String toString() { |
Collection for
最後,使用 for
關鍵字將序列插入到集合中:
1 | class Article { |
在此範例中,for
表達式為 tags
列表中的每個項目加入一個字串。就像 Dart 中的普通 for
迴圈一樣,tags
表達式可以是任何 Iterable
。
Flutter 程式碼中的集合
如果您正在使用 Dart,很有可能您正在使用它來構建 Flutter 應用程式。由於這裡描述的功能是在設計時考慮到 Flutter 的,讓我們來看看一些 Flutter 程式碼。
重構 build() 方法
在 Flutter 中,通常在 build()
方法中構建 Widget 列表:
1 |
|
可以使用 spread 重寫此程式碼:
1 | Widget build(BuildContext context) { |
或者使用 collection for:
1 | Widget build(BuildContext context) { |
第一個程式碼片段使用 map()
將 Article
類別轉換為 ArticleWidget
物件的集合,然後應用 spread 運算符將它們展開到周圍的列表中。在第二個範例中,collection for 運算符讓您可以更簡潔地表達這一點。
更大的 build() 方法
以下是一個更複雜的範例:
1 | Widget build(BuildContext context) { |
放置 Widget 到 Column
中的邏輯就在讀者可能期望的位置,並且節省了大量程式碼。在這些功能出現之前,實現相同行為最常用的方法是建立一個變數,並使用調用 add()
的普通 if
陳述式。
組合這些功能
這些功能可以以有趣的方式組合,如本節中的範例所示。以下是一些需要注意的事項:
- 從語法上講,collection if、collection for 或 spread 是一個 單個元素——即使它最終建立了多個物件。
- 任何表達式都可以放在 collection if 或 collection for 的主體中。
- 任何 元素 都可以放在 collection if 或 collection for 的主體中。
結合使用 if 和 for
以下是一些使用 collection for 內部的 collection if 建立列表的程式碼。在這裡,如果每個文章的日期晚於特定日期,則將其加入到列表中:
1 | List<Article> recentArticles(List<Article> allArticles) { |
如果您更喜歡 spreads,則返回值可以寫成 ...allArticles.where((article) => article.date.isAfter(ninetyDaysAgo))
。
將 collection if 和 spreads 結合使用
collection if 接收單個元素,但如果您想包含多個元素,則可以使用 spread:
1 | Widget build(BuildContext context) { |
將集合功能與 async-await 結合使用
您也可以將非同步調用與集合字面量結合使用。例如,一個常見的模式是使用 Future.wait()
觸發一組非同步調用:
1 | Future<Article> fetchArticle(String id); |
可以使用 collection for 改進該程式碼:
1 | Future<List<Article>> fetchArticles(List<String> ids) async { |
也可以在集合字面量中放入 await
,儘管它會依次等待每個 Future
:
1 | Future<List<Article>> fetchArticles(List<String> ids) async { |
前面的程式碼會依次等待,因為它相當於以下程式碼:
1 | Future<List<Article>> fetchArticles() async { |
您也可以使用 await for
展開 Stream
:
1 | Stream<String> get idStream => Stream.fromIterable(['1','2','3']); |
這是 collection if、collection for 和 spreads 如何與語言的其他部分一起使用的另一個範例。如果您使用過 await for
陳述式,您可能會猜到其行為:它偵聽 Stream
中的新值,並將主體放入周圍的列表中。
進一步探索
希望這些技巧能幫助您編寫更乾淨的 Dart 程式碼。除了這裡提到的之外,還有更多使用這些功能的方法。如果您發現了一個好的技巧,請與社群分享或在 Twitter 上提及 @dart_lang。如需更多詳細資訊,請查看使 Dart 成為更好的 UI 語言或 GitHub 上的初始語言提案。
探索 Dart 中的集合 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。