Flutter学习:Dart展开操作符 和 Control Flow Collections
展开操作符(spread operators)
展开操作符 ...
能够把 list、set、map
字面量里的元素插入到一个集合中。一个对象是否可用于展开操作符取决于是否继承了Iterable
,Map集合例外,对 map 进行展开操作 实际上是 调用了 Map
的 entries.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 代码的表现力还是很强的