Flutter学习:Dart展开操作符 和 Control Flow Collections

2023年 7月 13日 63.7k 0

展开操作符(spread operators)

展开操作符 ... 能够把 list、set、map 字面量里的元素插入到一个集合中。一个对象是否可用于展开操作符取决于是否继承了Iterable,Map集合例外,对 map 进行展开操作 实际上是 调用了 Mapentries.iterator()

在实际开发中,我们可能需要创建新的集合,集合的元素通常依赖另一个已经存在集合,然后再次基础上再添加写新的元素,如:

1var args = testArgs.toList()
2  ..add('--packages=${PackageMap.globalPackagesPath}')
3  ..add('-rexpanded')
4  ..addAll(filePaths);

上面的代码案例,来自官网。其中 toList() 函数会创建一个新的集合,集合里包含了 testArgs 所有元素 然后通过 级联操作符 实现链式调用来简化代码,最后通过 addAll 添加另一个集合的所有元素

但是这种写法依然非常笨重,下面来看下如何通过 展开操作符简化代码:

1var args = [
2  ...testArgs, // 通过展开操作符将testArgs集合解包然后将里面的元素逐个放进args里
3  '--packages=${PackageMap.globalPackagesPath}',
4  '-rexpanded',
5  ...filePaths // 通过展开操作符将filePaths集合解包然后将里面的元素逐个放进args里
6];

Flutter 中是使用 声明式UI(declarative style), Android/iOS 是使用 命令式UI(imperative style)

什么是 声明式UI?在 Flutter 中,如果 Widget 发生变化通过 setState 函数触发,然后 Flutter 会构建新的 Widget 实例和 Widget 子树 而 命令式UI 是拿到原来的 Widget 实例,然后调用该实例的方法来触发变化,而不是创建新的 Widget

他们之间的代码风格区别如下:

 1// 声明式UI代码风格
 2return ViewB(
 3  color: red,
 4  child: ViewC(...),
 5)
 6
 7// 命令式UI代码风格
 8b.setColor(red)
 9b.clearChildren()
10ViewC c3 = new ViewC(...)
11b.add(c3)

展开操作符 在 Flutter 这种声明式 UI 中应用非常广泛,例如我们构建一个 ListView:

 1Widget build(BuildContext context) {
 2  return CupertinoPageScaffold(
 3    child: ListView(
 4      children: [ //children 就是一个List
 5        Tab2Header(),
 6        // buildTab2Conversation()返回值也是一个List
 7        ...buildTab2Conversation(), 
 8      ],
 9    ),
10  );
11}

上面的例子是在 List 中和使用展开操作符,在 Map 集合中也是一样的,例如将 queryParams 和 formParams 合并产生一个新的 Map 集合

1var map = {
2    ...options.queryParameters,
3    ...options.data
4};

在 Set 中的使用展开操作符也一样的,如:

1var items = [2, 3, 4];
2var set = { 1, 2, ...items };

可空的展开操作符(Null-aware spread)

上面的我们讲到的展开操作符,如果被展开的对象是 null,那么会抛出运行时异常

1var oops = null;
2//NoSuchMethodError: The getter 'iterator' was called on null
3var list = [...oops];

如果被展开的对象可能为 null,需要在展开操作符后面加上 ? 号 (...?):

1var oops = null;
2var list = [...?oops];

展开操作符语义分析

List 中使用展开操作符分析

 1var list  = [elem_1 ... elem_n]
 21. 首先会创建新的集合,用于保存集合字面量里的元素
 32. 遍历字面量里的所有元素
 4    1)把元素表达式(展开操作符)赋值给value遍历
 5    2)如果遍历的元素是展开操作符
 6        a)如果使用的是可空的展开操作符,并且 value 是 null,直接 continue
 7        b)将 value.iterator 赋值给 iterator 变量
 8        c)然后遍历 iterator,将里面的元素追加到上面新建的集合里
 9    3)如果遍历的元素不是展开操作符,直接将元素添加到集合中
103. 就这样,集合字面量的元素全部在上面新建的集合里了

像其他的集合如 Set、Map 也是类似的机制

控制流集合(Control Flow Collections)

控制流集合 就是在构建集合字面量的时候可以使用 if、for 控制流语句,简称 collection-if/for

上面我们提到 Flutter 是声明式 UI,例如:

1Widget build(BuildContext context) {
2  return Row(
3    children: [ // 集合字面量
4      IconButton(icon: Icon(Icons.menu)),
5      Expanded(child: title),
6      IconButton(icon: Icon(Icons.search)),
7    ],
8  );
9}

如果我们想在上面的代码的基础上加一些判断,例如只在 Android 平台展示搜索按钮(Icons.search),我们可能会这么写:

 1Widget build(BuildContext context) {
 2  // 构建button集合
 3  var buttons = [
 4      IconButton(icon: Icon(Icons.menu)),
 5      Expanded(child: title),
 6  ];
 7  // 如果是 android 平台,添加搜索按钮
 8  if (isAndroid) {
 9    buttons.add(IconButton(icon: Icon(Icons.search)));
10  }
11
12  return Row(
13    children: buttons,
14  );
15}

有了 Control Flow Collection,我们可以这样类简化代码:

 1Widget build(BuildContext context) {
 2  return Row(
 3    children: [
 4      IconButton(icon: Icon(Icons.menu)),
 5      Expanded(child: title),
 6      // 如果是 android 平台,添加搜索按钮
 7      if (isAndroid) IconButton(icon: Icon(Icons.search)),
 8    ]
 9  );
10}

除了使用 if,还可以使用 else,例如在 Android 平台展示搜索按钮, 其他平台展示关于按钮 :

 1Widget build(BuildContext context) {
 2  return Row(
 3    children: [
 4      IconButton(icon: Icon(Icons.menu)),
 5      Expanded(child: title),
 6      if (isAndroid)
 7        IconButton(icon: Icon(Icons.search))
 8      else
 9        IconButton(icon: Icon(Icons.about)),
10    ]
11  );
12}

再比如我们需要根据一个集合然后生成另一个集合,可能会这样写:

 1var command = [
 2  engineDartPath,
 3  frontendServer,
 4];
 5for (var root in fileSystemRoots) {
 6  command.add('--filesystem-root=$root');
 7}
 8for (var entryPointsJson in entryPointsJsonFiles) {
 9  if (fileExists("$entryPointsJson.json")) {
10    command.add(entryPointsJson);
11  }
12}
13command.add(mainPath);

有了 Control Flow Collection,我们可以这样写,可读性更强:

1var command = [
2  engineDartPath,
3  frontendServer,
4  for (var root in fileSystemRoots) '--filesystem-root=$root',
5  for (var entryPointsJson in entryPointsJsonFiles)
6    if (fileExists("$entryPointsJson.json")) entryPointsJson,
7  mainPath
8];

还可以在构建集合字面量的时候 for 循环计算:

1var integers = [for (var i = 1; i  '${demo.routeName}',
4  value: (demo) => demo.buildRoute,
5);

collection-for 可以让我们的代码简化成如下所示:

1return {
2  for (var demo in kAllGalleryDemos)
3    '${demo.routeName}': demo.buildRoute,
4};

collection if-for 还可以组合,例如 for 循环嵌套,for-if 嵌套:

1//for-for
2[for (var x in hor) for (var y in vert) Point(x, y)]
3
4//for-if
5[for (var i in integers) if (i.isEven) i * i]

总结

Dart 的展开操作符 (Null-aware spread、Non-null-ware spread) 和 Control flow collections(collection if/for)

让我们编写的代码的时候代码的可读性更高

特别是编写 Flutter UI 的时候更符合 declarative style 代码风格

从中可以看出 Dart 代码的表现力还是很强的

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论