Dart 语法原来这么好玩儿
说到到某个语言的语法可能大家会觉得很枯燥、乏味,而日常开发中我们往往更加注重的是业务逻辑和页面开发,语法的使用大多也停留在满足基本的需求。其实 Dart
语法有很多有意思的地方的,仔细探究一下你会发现,它的简洁清晰、灵活多样的语法会让人爱不释手。在本文中,我们将探索 Dart 语法的各种奇妙之处吧。
unwrap
操作
在 Flutter
中,unwrap
操作常常用于处理可能为空的数据,以便过滤掉空值并只保留非空值。其使用场景也相当广泛,例如 为 Future
和 Streams
添加 unwrap
来处理掉非空数据,或者从网络请求或其他异步操作中获取数据,并在数据流中处理结果等等,如下面这段代码:
extension Unwrap<T> on Future<T?> {
Future<T> unwrap() => then(
(value) => value != null
? Future<T>.value(value)
: Future.any([]),
);
}
unwrap
函数将可能为空的 Future
解包,如果 Future
返回的值不为 null
,则将值包装在一个新的 Future
中返回,否则返回一个空的 Future
。调用示例:
class ImagePickerHelper {
static final ImagePicker _imagePicker = ImagePicker();
static Future<File> pickImageFromGallery() => _imagePicker
.pickImage(source: ImageSource.gallery)
.unwrap()
.then((xFile) => xFile.path)
.then((filePath) => File(filePath));
}
这里用到图片选择器插件 image_picker
,只有当返回的 xFile
不为空时才进行后续操作。如果不调用 unwrap
函数,此时这里返回的 xFile
为 optional
类型,要使用之前需要判断是否为 null
。日常开发中这种情况还不少,给 Future
添加 Unwrap
函数之后这样非空判断集中在这一个函数里面处理。
unwrap
不仅在 Future
中使用,还可以为 Streams
添加 unwrap
操作,代码如下:
extension Unwrap<T> on Stream<T?> {
Stream<T> unwrap() => where((event) => event != null).cast();
}
unwrap
方法,通过 where
过滤掉了 null
的事件,并使用 cast()
方法将结果转换为 Stream<T>
类型,将可空的事件转换为非空的事件流,下面是调用代码:
void main() {
Stream<int?>.periodic(
const Duration(seconds: 1),
(value) => value % 2 == 0 ? value : null,
).unwrap().listen((evenValue) {
print(evenValue);
});
/* 输出结果
0
2
4
6
...
*/
}
通过 extension
给 Future
和 Streams
添加 unwrap
函数后让我们的代码看起来清晰简洁多了,有没有?
数组的展开、合并和过滤
下面代码为任意类型的可迭代对象(Iterable
)添加名为 Flatten
的扩展。在这个扩展中,函数 flatten
使用了递归算法将多层嵌套的 Iterable
里面的所有元素扁平化为单层 Iterable
。
extension Flatten<T extends Object> on Iterable<T> {
Iterable<T> flatten() {
Iterable<T> _flatten(Iterable<T> list) sync* {
for (final value in list) {
if (value is List<T>) {
yield* _flatten(value);
} else {
yield value;
}
}
}
return _flatten(this);
}
}
注意了上面代码中使用了 yield
关键字,在 Flutter
中,yield
关键字用于生成迭代器,通常与sync*
或 async*
一起使用。它允许您在处理某些数据时逐步生成数据,而不是在内存中一次性处理所有数据。对于处理大量数据或执行长时间运行的操作非常有用,因为它可以节省内存并提高性能。
这个和 ES6
中使用 function*
语法和 yield
关键字来生成值一个东西,也是逐个生成值,而不需要一次性生成所有值。以下是 JS
写法:
function* generateNumbers(n) {
for (let i = 0; i < n; i++) {
yield i;
}
}
const numbers = generateNumbers(5);
for (const number of numbers) {
console.log(number);
}
我们来看看 Dart
中的 flatten()
函数的调用:
Future<void> main() async {
final flat = [
[[1, 2, 3], 4, 5],
[6, [7, [8, 9]], 10],
11,12
].flatten();
print(flat); // (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
}
嵌套的集合可能在数据处理、转换或展示中经常遇到,而将这些嵌套的集合扁平化可以简化数据处理过程,使代码更加简洁和易于理解。另外一点,递归展多维数组在面试中经常会出现,说不定哪天就用上了哈。
如果将两个数组合并成一个数组该怎么操作呢?其实和 Map
的合并相似,也是用到了自定义操作符 operator
,来看看怎么实现的。
extension InlineAdd<T> on Iterable<T> {
Iterable<T> operator +(T other) => followedBy([other]);
Iterable<T> operator &(Iterable<T> other) => followedBy(other);
}
void main() {
const Iterable<int> values = [10, 20, 30];
print((values & [40, 50]));
// 输出结果:(10, 20, 30, 40, 50)
}
添加了两个操作符:+
和 &
。将一个元素或者另一个可迭代对象添加到当前的可迭代对象中,然后返回一个新的可迭代对象,让可迭代对象 terable
有了合并数组的功能。
当数组中有一个为 null
的对象时,该如何过滤掉这个 null
对象呢,很简单可以这样做:
extension CompactMap<T> on Iterable<T?> {
Iterable<T> compactMap<E>([
E? Function(T?)? transform,
]) =>
map(transform ?? (e) => e).where((e) => e != null).cast();
}
void main() {
const list = ['Hello', null, 'World'];
print(list); // [Hello, null, World]
print(list.compactMap()); // [Hello, World]
print(list.compactMap((e) => e?.toUpperCase())); // [HELLO, WORLD]
}
Map
的过滤和合并
下面代码是 Map
类型的 extension
,为 Map
类型添加了查找过滤的函数。
extension DetailedWhere<K, V> on Map<K, V> {
Map<K, V> where(bool Function(K key, V value) f) => Map<K, V>.fromEntries(
entries.where((entry) => f(entry.key, entry.value)),
);
Map<K, V> whereKey(bool Function(K key) f) =>
{...where((key, value) => f(key))};
Map<K, V> whereValue(bool Function(V value) f) =>
{...where((key, value) => f(value))};
}
where
: 接受一个函数作为参数,该函数接受Map
的键和值作为参数,并返回一个布尔值。whereKey
: 接受一个只接受键作为参数的函数。whereValue
: 这个方法接受一个只接受值作为参数的函数。
下面是调用:
void main(){
const Map<String, int> people = {'John': 20, 'Mary': 21, 'Peter': 22};
print(people.where((key, value) => key.length > 4 && value > 20)); // {Peter: 22}
print(people.whereKey((key) => key.length < 5)); // {John: 20, Mary: 21}
print(people.whereValue((value) => value.isEven)); // {John: 20, Peter: 22}
}
其中 where
方法先使用 entries
获取 Map
的键值对列表,然后使用 entries.where
方法对列表中的每个键值对进行过滤,最后使用 fromEntries
方法将过滤后的键值对列表转换回 Map
,最后返回的新的 Map
中只包含满足条件的键值对,达到对 Map
中键值过滤的效果,也让代码更加简洁和易读。
Map
过滤还有另外一种写法
extension Filter<K, V> on Map<K, V> {
Iterable<MapEntry<K, V>> filter(
bool Function(MapEntry<K, V> entry) f,
) sync* {
for (final entry in entries) {
if (f(entry)) {
yield entry;
}
}
}
}
void main(){
const Map<String, int> people = {
'foo': 20,
'bar': 31,
'baz': 25,
'qux': 32,
};
final peopleOver30 = people.filter((e) => e.value > 30);
print(peopleOver30); // 输出结果:(MapEntry(bar: 31), MapEntry(qux: 32))
}
Map
其它一些更有趣的 extension
,如 Merge
功能,将两个 Map
合并成一个,代码如下:
extension Merge<K, V> on Map<K, V> {
Map<K, V> operator |(Map<K, V> other) => {...this}..addEntries(
other.entries,
);
}
上面的代码用到了 operator
关键字,在 Dart
中,operator
关键字用于定义自定义操作符或者重载现有的操作符。通过 operator
关键字,我们可以为自定义类定义各种操作符的行为,使得我们的类可以像内置类型一样使用操作符。
如 operator +
来定义两个对象相加的行为,operator []
来实现索引操作,operator ==
来定义相等性比较。这种语义式的也更加符合直觉、清晰易懂。
下面来看看 Map
的 Merge
功能调用代码例子:
const userInfo = {
'name': 'StellarRemi',
'age': 28,
};
const address = {
'address': 'shanghai',
'post_code': '200000',
};
void main() {
final allInfo = userInfo | address;
print(allInfo);
// 输出结果:{name: StellarRemi, age: 28, address: shanghai, post_code: 200000}
}
调用的时候也很简单直接 userInfo | address;
,这种操作在处理数据更新或合并配置等情况下特别有用。使用的时候需要注意的是,如果两个 Map
中有重复的键,那么上述操作会保留最后一个 Map
中的值。
小结
怎么样,上面的这些 Dart
的语法是不是很有意思,有没有函数式编程那味儿,后面还会单独一篇来分享 Dart
语言面向对象的设计。好了,今天就到这里,也希望通过本文的分享,能够激发大家对 Dart
语言的兴趣,感谢您的阅读,记得关注点赞哈。
来源:juejin.cn/post/7361096760449466406