Dart 语法原来这么好玩儿


说到到某个语言的语法可能大家会觉得很枯燥、乏味,而日常开发中我们往往更加注重的是业务逻辑和页面开发,语法的使用大多也停留在满足基本的需求。其实 Dart 语法有很多有意思的地方的,仔细探究一下你会发现,它的简洁清晰、灵活多样的语法会让人爱不释手。在本文中,我们将探索 Dart 语法的各种奇妙之处吧。

unwrap 操作

Flutter 中,unwrap 操作常常用于处理可能为空的数据,以便过滤掉空值并只保留非空值。其使用场景也相当广泛,例如 为 FutureStreams 添加 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 函数,此时这里返回的 xFileoptional 类型,要使用之前需要判断是否为 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
    ...
  */
}

通过 extensionFutureStreams 添加 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 == 来定义相等性比较。这种语义式的也更加符合直觉、清晰易懂。

下面来看看 MapMerge 功能调用代码例子:

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 语言的兴趣,还有更多干货内容在我的公众号:Flutter技术实践,感谢您的阅读,记得关注点赞哈。

  • 26
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值