有一种结合了 map 和 filter 的 RX 方法吗?

21

我对RxJS比较陌生。我知道我可以使用.filter.map操作符来获取我需要的结果。但是,是否有一种方法将这两个操作结合成一个函数呢?


3
回顾过去,几乎没有理由尝试将筛选和映射作为一个单一的操作使用。如果有人是“RxJS新手”,看到我的问题,请不要采用这些答案。只需先筛选,然后再进行映射即可。 - Bryan Rayner
3个回答

32

有的。

FlatMap。

假设你有一个数字的 Observable (1、2、3、4、5,...),并且你想筛选出偶数并将它们映射为 x*10。

var tenTimesEvenNumbers = numbers.flatMap(function (x) {
  if (x % 2 === 0) {
    return Rx.Observable.just(x * 10);
  } else {
    return Rx.Observable.empty();
  }
});

感谢您的回答 - 我从flatMap的文档中永远不会意识到您可以这样做。但我还是选择@paulpdaniels的答案,因为它更加函数化。 - Bryan Rayner
你的意思是在哪方面更加功能强大? - André Staltz
2
我的意思是,在这个例子中,过滤操作必须在if语句的括号内完成。假设您有一个常见的过滤函数,想要将其应用于多次操作,您仍然可以这样做,但需要编写多个if()语句。此外,每次都需要调用它。更好的解决方案是,您可以将过滤和映射函数作为单独的参数传递。在我看来,一种能够实现这种编码风格的解决方案更具“功能性”。 - Bryan Rayner
1
数组也实现了Observable,因此您可以将其写为.flatMap(x => x % 2 == 0 ? [x * 10] : []) - phaux
2
flatMap 现在被称为 mergeMap: https://github.com/ReactiveX/RxJS/issues/333 - Mihai Răducanu
显示剩余4条评论

7
截至rxjs v6.6.7版本,解决方案如下:
    // Initialise observable with some numbers
    const numbers = of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

    // Pipe the observable using mergeMap
    const tenTimesEvenNumbers = numbers.pipe(
      mergeMap((x: number) => {
        // If the number is even, return an observable containing the number multiplied by ten
        // Otherwise return an empty observable
        return x % 2 === 0 ? of(x * 10) : EMPTY;
      })
    );

    // Subscribe to the observable and print the values
    tenTimesEvenNumbers.subscribe((value: number) =>
      console.log('Value:', value)
    );

上面的代码将会输出:
Value: 20
Value: 40
Value: 60
Value: 80
Value: 100

这里也有一个可工作的stackblitz 示例


0
你可以通过使用高阶函数来实现这一点。
例如,这里有一个非 RxJS 的函数来完成生成所需列表的繁重工作。它接受两个函数,过滤函数和映射函数,并返回一个函数,该函数接受你想要处理的列表。
function filterMap<A, B>(filterFn: (a: A) => boolean, mapFn: (a: A) => B): (as: A[]) => B[] {
   return (as) => as.reduce((acc, a) => {
     if (filterFn(a)) acc.push(mapFn(a));
     return acc;
   }, []);
}

你可以在使用纯JavaScript时忽略类型。
filterMap可以像这样使用:
const isEven = (a) => a % 2 === 0;
const doubleValue = (a) => a = 2 * a;
const doubleEvens = filterMap(isEven, doubleValue);
const myResult = doubleEvens([1, 2, 3, 4, 5);

现在你可以直接在rxjs的map函数中使用filterMap,也可以定义一个新的pipe函数。以下是一个使用示例:
of([1, 2, 3, 4, 5]).pipe(
  map(doubleEvens)
).subscribe(console.log);

有几个优点

  • 您可以重复使用filterMap来定义所需的任何过滤器和映射的组合
  • 操作可以被赋予有意义的名称
  • 列表只被迭代一次
  • 不会创建额外的可观察对象

@judos对只映射子集并保留不匹配值提出了评论。解决方案:1)只需使用map 2)定义filterApplyMap类似于filterMap,但添加else子句并执行acc.push(a),最终返回类型将是(A|B)[],与#1相同,这不太美观 3)定义filterPartitionMap,它将一对列表作为累加器,并将适当的值推送到适当的列表上。返回类型将是[A[], B[]]。我将B[]放在右边以保持“true”值在右边的一致性。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接