- 1:Collections
- 2:Collections字面构造
- 3:Collections中的类型
- 3.1:扩展
- 3.2:空感知的扩展(...?)
- 3.3:Collection if
- 3.4:Collection for
- 4:Flutter代码中的集合
- 4.1:重构build()方法
- 4.2:一个更大的build方法
- 5:组合这些功能
- 5.1:同时使用if和for
- 5.2:同时使用collection if和spread
- 5.3:在async-await中使用Collection的功能
如果你曾经调用过add(),addAll(),map()或toList()来构建过List或者Map,则可能发现Dart在2.3版中添加的功能:collection if,collect for和spread。 。
在本文中,我们将研究集合,并探索这些功能,并查看一些的示例。 通过掌握这些功能,可以使你的代码更简洁,更易于阅读。
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',
};
}
如果想知道为什么Map和Set可以使用相同的{}语法,那是因为Dart使用类型推断来区分的。 类型系统根据参数a和b的类型确定类型。 通常,它可以根据内容确定该位置,例如,{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;
}
也可以在Set和Map中使用改功能:
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,
};
}
在Map和Set中,发生冲突时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
使用if,else和else 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]
}
它会在流中侦听新值,并将主体放入当前的集合中。