Dart - collections

文章目录[x]
  1. 1:Collections
  2. 2:Collections字面构造
  3. 3:Collections中的类型
  4. 3.1:扩展
  5. 3.2:空感知的扩展(...?)
  6. 3.3:Collection if
  7. 3.4:Collection for
  8. 4:Flutter代码中的集合
  9. 4.1:重构build()方法
  10. 4.2:一个更大的build方法
  11. 5:组合这些功能
  12. 5.1:同时使用if和for
  13. 5.2:同时使用collection if和spread
  14. 5.3:在async-await中使用Collection的功能

如果你曾经调用过add()addAll()map()toList()来构建过List或者Map,则可能发现Dart2.3版中添加的功能:collection ifcollect forspread。 。

在本文中,我们将研究集合,并探索这些功能,并查看一些的示例。 通过掌握这些功能,可以使你的代码更简洁,更易于阅读。

Collections

首先,我们需要了解什么是集合。 集合是包含其他一系列对象的对象。 例如:

  • List:具有一定长度的对象的有序集合(也称为数组)
  • Set:无序的唯一对象集合
  • Map:键值对的无序集合
  • Queue:可以在两端添加/删除对象的有序集合
  • SplayTreeMap:基于自平衡二叉树的键-值对的有序集合

这些类型在dart:collection包中可用。 有关更多Collections的类型,可以在pub.dev上查看package:collection

这些集合类型中的每一个都实现Iterable,它提供了常见的方法,例如在集合中的每个对象上运行一个函数、获取第一个对象、确定集合的长度等等。

Collections字面构造

Dart支持用于构造三种类型的集合的语法:List[]Map:{} Set{}

下面是List的字面构造:

List<String> getArtists() {
 return [
 'Picasso',
 'Warhol',
 'Monet',
 ];
}

下面是Map的字面构造:

Map<String, String> getArtistsByPainting {
 return {
 'The Old Guitarist': 'Picasso',
 'Orange Prince': 'Warhol',
 'The Water Lily Pond': 'Monet',
 };
}

下面是Dart 2.3版本中添加的Set构造:

Set<String> getArtistsSet() {
 return {
 'Picasso',
 'Warhol',
 'Monet',
 };
}

如果想知道为什么MapSet可以使用相同的{}语法,那是因为Dart使用类型推断来区分的。 类型系统根据参数ab的类型确定类型。 通常,它可以根据内容确定该位置,例如,{1}显然是Set,而{1:2}显然是Map

注意:默认情况下,使用{}会构造一个Map。 要创建Set,可以使用通用类型注释:<String>{}。 使用两个通用类型参数创建一个Map<String,String>{}

Collections中的类型

集合字面构造中的每个通常是一个值或一个表达式,但也可以是以下功能之一:collection if、cikkection for、spread

扩展

扩展一个集合,并将其内容放入当前的集合中:

List<String> combineLists(List<String> a, List<String> b) {
 return [
 ...a,
 ...b,
 ];
}

上面的代码与如下代码等效:

List<String> combineLists(List<String> a, List<String> b) {
 var list = [];
 list.addAll(a);
 list.addAll(b);
 return list;
}

也可以在SetMap中使用改功能:

Map<String, String> combineMaps(Map<String, String> a, Map<String, String> b) {
 return {
 ...a,
 ...b,
 };
}
Set<String> combineSets(Set<String> a, Set<String> b) {
 return {
 ...a,
 ...b,
 };
}

MapSet中,发生冲突时b的内容都会覆盖a的内容。 例如,调用combineMaps({'foo':'bar'},{'foo':'baz'})会生成包含{'foo':'baz'}Map

空感知的扩展(...?)

仅当运算符后的表达式为非null时,可感知null的扩展才将内容添加到集合中:

List<String> combineIfExists(List<String> a, List<String> b) {
 return [
 ...?a,
 ...?b,
 ];
}
void main() {
 var result = combineIfExists(['foo'], null);
 print(result); // [foo]
}

Collection if

使用ifelseelse if关键字根据条件向集合中添加内容。 如下示例:

class Article {
 String title;
 DateTime date;
 Article(this.title, this.date);
 String toString() {
 return [
 if (title != null) title,
 date.toString(),
 ].join(', ');
 }
}

也可以在最后添加else关键字:

String toString() {
 return [
 if (title != null) title else '(no title)',
 ].join(', ');
}

当然也可以添加else if

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关键字将一个序列插入到集合中:

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代码中的集合

重构build()方法

Flutter中,通常会在build()方法中构建小部件列表:

@override
Widget build(BuildContext context) {
 var articleWidgets = articles
 .map<Widget>((article) => ArticleWidget(article: article))
 .toList();
 return ListView(
 children: articleWidgets,
 );
}

这可以用扩展重写:

Widget build(BuildContext context) {
 return ListView(
 children: [
 ...articles.map((article) => ArticleWidget(article:article))
 ],
 );
}

或者用collection for重写:

Widget build(BuildContext context) {
 return ListView(
 children: [
 for (var article in articles)
 ArticleWidget(article: article)
 ],
 );
}

第一例子的代码段使用map()Article类转换为ArticleWidget对象的集合,然后应用spread运算符将其扩展到当前的列表中。 在第二个示例中,用于操作符的集合使可以更简洁地表达这一点。

一个更大的build方法

如下是一个更复杂的示例:

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),
 ],
 );
}

你可能期望将小部件正确地放置在Column中,并节省大量代码。 在使用这些功能之前,实现相同行为的最常见方法是创建一个变量并使用if语句并调用add()

组合这些功能

如文中的示例所示,可以以非常有趣的方式组合这些功能。 这里有几件事要牢记:

  • 从语法上来说,即使最终创建多个对象,collection if、collection for、spread所得到是单个元素。
  • collection if、collection for的任何表达式都可以放入集合的主体中。
  • collection if、collection for的任何元素都可以进入集合的主体中。

同时使用if和for

下面的代码中,如果每条文章在特定日期之后都添加到列表中:

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
 ];
}

如果更喜欢使用扩展,则可以将返回值写为:

...allArticles.where((article) => article.date.isAfter(ninetyDaysAgo))

同时使用collection if和spread

Widget build(BuildContext context) {
 return Column(
 children: [
 if (article.date != null) ...[
 Icon(Icons.calendar_today),
 Text('${article.date}'),
 ],
 ],
 );
}

在async-await中使用Collection的功能

还可以将异步调用与集合这些功能结合起来。 例如,一种常见的模式是使用Future.wait()触发一组异步调用:

Future<Article> fetchArticle(String id);
Future<List<Article>> fetchArticles() async {
 return Future.wait([
 fetchArticle('1'),
 fetchArticle('2'),
 fetchArticle('3'),
 ]);
}

可以使用Collection for来改进该代码:

Future<List<Article>> fetchArticles(List<String> ids) async {
 return Future.wait([
 for (var id in ids)
 fetchArticle(id),
 ]);
}

也可以直接使用await:

Future<List<Article>> fetchArticles(List<String> ids) async {
 return [
 // 一次取一个
 for (var id in ids)
 await fetchArticle(id),
 ];
}

前面的代码按顺序等待完成,因为它等效于以下代码:

Future<List<Article>> fetchArticles() async {
 return <Article>[
 await fetchArticle('1'),
 await fetchArticle('2'),
 await fetchArticle('3'),
 ];
}

还可以使用await for转换Stream

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)); // [1, 2, 3]
}

     它会在流中侦听新值,并将主体放入当前的集合中。

 

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00