【文章內容使用 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 2 3 4 5 6 7 List <String > getArtists() { return [ 'Picasso' , 'Warhol' , 'Monet' , ]; }
以下是一個映射字面量:
1 2 3 4 5 6 7 Map <String , String > getArtistsByPainting { return { 'The Old Guitarist' : 'Picasso' , 'Orange Prince' : 'Warhol' , 'The Water Lily Pond' : 'Monet' , }; }
以下是一個集合字面量,在 Dart 2.3 中加入:
1 2 3 4 5 6 7 Set <String > getArtistsSet() { return { 'Picasso' , 'Warhol' , 'Monet' , }; }
如果您想知道為什麼映射和集合可以使用相同的 {}
語法,那是因為 Dart 使用 類型推斷 來區分。類型系統根據參數 a 和 b 的類型確定類型。它通常可以根據內容確定這一點——例如,{1}
顯然是一個 Set
,而 {1: 2}
顯然是一個 Map
。
注意: 使用 {}
預設構造一個映射。要建立一個集合,您可以使用泛型類型註釋:<String>{}
。使用兩個泛型類型參數則建立一個映射:<String, String>{}
。
元素的類型 集合字面量中的每個項目通常是一個值或表達式,但也可以是以下新功能之一:collection if 、collection for 或 spread 。所有這些都被稱為 元素 。
每個元素解包零個或多個項目,並將它們放入周圍的集合中。例如,一個字串字面量(例如「oatmeal」)會產生一個項目,但 collection for 會解包 0 個或多個項目。這些功能也可以以有趣的方式組合,我們將在下面探討。
Spreads spread 接收一個集合(例如,一個列表),並將其內容放入周圍的集合中:
1 2 3 4 5 6 List <String > combineLists(List <String > a, List <String > b) { return [ ...a, ...b, ]; }
前面的程式碼相當於:
1 2 3 4 5 6 List <String > combineLists(List <String > a, List <String > b) { var list = []; list.addAll(a); list.addAll(b); return list; }
您也可以在映射和集合字面量中使用 spreads:
1 2 3 4 5 6 Map <String , String > combineMaps(Map <String , String > a, Map <String , String > b) { return { ...a, ...b, }; }
1 2 3 4 5 6 Set <String > combineSets(Set <String > a, Set <String > b) { return { ...a, ...b, }; }
在映射和集合中,當發生衝突時,b 的內容會覆蓋 a 中的內容。例如,調用 combineMaps({'foo': 'bar'}, {'foo': 'baz'})
會產生一個包含 {'foo': 'baz'}
的映射。
Null-aware spreads (…?) null-aware spread 僅當運算符後的表達式為非 null 時才將內容加入到集合中:
1 2 3 4 5 6 List <String > combineIfExists(List <String > a, List <String > b) { return [ ...?a, ...?b, ]; }
1 2 3 4 void main() { var result = combineIfExists(['foo' ], null ); print (result); }
Collection if 使用 if
、else
和 else if
關鍵字根據條件將某些內容加入到集合中。以下是一個使用 collection if 的範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Article { String title; DateTime date; Article(this .title, this .date); String toString() { return [ if (title != null ) title, date.toString(), ].join(', ' ); } }
可以在末尾加入 else
關鍵字:
1 2 3 4 5 String toString() { return [ if (title != null ) title else '(no title)' , ].join(', ' ); }
請注意逗號的位置。逗號不能在 title
之後,因為 else
是同一個元素的一部分。將 if
和 else
保持在一起,在逗號之前,可以將它們與集合中的下一個元素區分開來。
加入 else if
也可以:
1 2 3 4 5 6 7 8 9 10 11 String toString() { return [ if (title != null ) title else '(no title)' , if (date == null ) '(no date)' else if (date.year == DateTime .now().year) 'this year' else '${date.year} ' , ].join(', ' ); }
Collection for 最後,使用 for
關鍵字將序列插入到集合中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Article { String title; DateTime date; List <String > tags; Article(this .title, this .date, this .tags); String toString() { return [ title, date.toString(), for (var tag in tags) 'tag: $tag ' ].join(', ' ); } }
在此範例中,for
表達式為 tags
列表中的每個項目加入一個字串。就像 Dart 中的普通 for
迴圈一樣,tags
表達式可以是任何 Iterable
。
Flutter 程式碼中的集合 如果您正在使用 Dart,很有可能您正在使用它來構建 Flutter 應用程式 。由於這裡描述的功能是在設計時考慮到 Flutter 的 ,讓我們來看看一些 Flutter 程式碼。
重構 build() 方法 在 Flutter 中,通常在 build()
方法中構建 Widget 列表:
1 2 3 4 5 6 7 8 9 10 @override Widget build(BuildContext context) { var articleWidgets = articles .map<Widget>((article) => ArticleWidget(article: article)) .toList(); return ListView( children: articleWidgets, ); }
可以使用 spread 重寫此程式碼:
1 2 3 4 5 6 7 8 Widget build(BuildContext context) { return ListView( children: [ ...articles.map((article) => ArticleWidget(article:article)) ], ); }
或者使用 collection for :
1 2 3 4 5 6 7 8 Widget build(BuildContext context) { return ListView( children: [ for (var article in articles) ArticleWidget(article: article) ], ); }
第一個程式碼片段使用 map()
將 Article
類別轉換為 ArticleWidget
物件的集合,然後應用 spread 運算符將它們展開到周圍的列表中。在第二個範例中,collection for 運算符讓您可以更簡潔地表達這一點。
更大的 build() 方法 以下是一個更複雜的範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Widget build(BuildContext context) { var headerStyle = Theme.of(context).textTheme.headline4; return Column( children: [ if (article.title != null ) Text(article.title, style: headerStyle), if (article.date != null ) Text(article.date.toString()), Text('Tags:' ), for (var tag in article.tags) Text(tag), ], ); }
放置 Widget 到 Column
中的邏輯就在讀者可能期望的位置,並且節省了大量程式碼。在這些功能出現之前,實現相同行為最常用的方法是建立一個變數,並使用調用 add()
的普通 if
陳述式。
組合這些功能 這些功能可以以有趣的方式組合,如本節中的範例所示。以下是一些需要注意的事項:
從語法上講,collection if 、collection for 或 spread 是一個 單個元素 ——即使它最終建立了多個物件。
任何表達式都可以放在 collection if 或 collection for 的主體中。
任何 元素 都可以放在 collection if 或 collection for 的主體中。
結合使用 if 和 for 以下是一些使用 collection for 內部的 collection if 建立列表的程式碼。在這裡,如果每個文章的日期晚於特定日期,則將其加入到列表中:
1 2 3 4 5 6 7 8 List <Article> recentArticles(List <Article> allArticles) { var ninetyDaysAgo = DateTime .now().subtract(Duration (days: 90 )); return [ for (var article in allArticles) if (article.date.isAfter(ninetyDaysAgo)) article ]; }
如果您更喜歡 spreads,則返回值可以寫成 ...allArticles.where((article) => article.date.isAfter(ninetyDaysAgo))
。
將 collection if 和 spreads 結合使用 collection if 接收單個元素,但如果您想包含多個元素,則可以使用 spread :
1 2 3 4 5 6 7 8 9 10 Widget build(BuildContext context) { return Column( children: [ if (article.date != null ) ...[ Icon(Icons.calendar_today), Text('${article.date} ' ), ], ], ); }
將集合功能與 async-await 結合使用 您也可以將非同步調用與集合字面量結合使用。例如,一個常見的模式是使用 Future.wait()
觸發一組非同步調用:
1 2 3 4 5 6 7 8 9 Future<Article> fetchArticle(String id); Future<List <Article>> fetchArticles() async { return Future.wait([ fetchArticle('1' ), fetchArticle('2' ), fetchArticle('3' ), ]); }
可以使用 collection for 改進該程式碼:
1 2 3 4 5 6 Future<List <Article>> fetchArticles(List <String > ids) async { return Future.wait([ for (var id in ids) fetchArticle(id), ]); }
也可以在集合字面量中放入 await
,儘管它會依次等待每個 Future
:
1 2 3 4 5 6 7 Future<List <Article>> fetchArticles(List <String > ids) async { return [ for (var id in ids) await fetchArticle(id), ]; }
前面的程式碼會依次等待,因為它相當於以下程式碼:
1 2 3 4 5 6 7 8 Future<List <Article>> fetchArticles() async { return <Article>[ await fetchArticle('1' ), await fetchArticle('2' ), await fetchArticle('3' ), ]; }
您也可以使用 await for
展開 Stream
:
1 2 3 4 5 6 7 8 9 10 11 Stream<String > get idStream => Stream.fromIterable(['1' ,'2' ,'3' ]); Future<List <String >> gatherIds(Stream<String > ids) async { return [ await for (var id in ids) id ]; } void main() async { print (await gatherIds(idStream)); }
這是 collection if 、collection for 和 spreads 如何與語言的其他部分一起使用的另一個範例。如果您使用過 await for
陳述式,您可能會猜到其行為:它偵聽 Stream
中的新值,並將主體放入周圍的列表中。
進一步探索 希望這些技巧能幫助您編寫更乾淨的 Dart 程式碼。除了這裡提到的之外,還有更多使用這些功能的方法。如果您發現了一個好的技巧,請與社群分享 或在 Twitter 上提及 @dart_lang 。如需更多詳細資訊,請查看使 Dart 成為更好的 UI 語言 或 GitHub 上的初始語言提案 。
探索 Dart 中的集合 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。