【问题标题】:What is the difference between Observable.lift and Observable.pipe in rxjs?rxjs 中的 Observable.lift 和 Observable.pipe 有什么区别?
【发布时间】:2018-02-22 11:47:41
【问题描述】:

The docs 定义Observable.lift(operator: Operator) 为:

创建一个新的 Observable,以这个 Observable 为源,并且 传递的运算符定义为新的 observable 的运算符。

Observable.pipe(operations: ...*) 为:

用于将功能运算符拼接成一个链。返回 调用所有操作符的可观察结果 按顺序传入。

很明显.pipe 可以接受多个运算符,而.lift 不能。但是pipe 也可以接受单个运算符,所以这不是唯一的区别。仅从文档来看,我不清楚它们的用途以及它们存在的原因。 谁能解释一下这些功能的用途,以及何时应该使用它们?


到目前为止的观察

以下代码(打字稿):

let myObservable = Observable.of(1, 2, 3);
let timesByTwoPiped = myObservable.pipe(map(n => n * 2));
let timesByTwoLift = myObservable.lift(new TimesByTwoOperator());

timesByTwoPiped.subscribe(a => console.log('pipe:' + a));
timesByTwoLift.subscribe(a => console.log('lift:' + a));

TimesByTwoOperator:

class TimesByTwoOperator implements Operator<number, number> {
  call(subscriber: Subscriber<number>, source: Observable<number>): void | Function | AnonymousSubscription {
    source.subscribe(n => {
      subscriber.next(n * 2);
    });
  }
}

似乎同时使用.lift.pipe 可以达到相同的结果。这个实验表明我认为提升和管道都可以用来实现同样的事情是正确的,尽管在这种情况下管道版本更简洁。

由于传递给.liftOperator 类型被授予对源可观察对象和订阅的完全访问权限,显然可以用它实现强大的功能;例如保持状态。但我知道.pipe 也可以实现同样的功能,例如buffer operator

我仍然不清楚为什么它们都存在以及各自的设计目的。

【问题讨论】:

    标签: javascript angular rxjs


    【解决方案1】:

    我在这里找到了关于这个主题的很好的深入讨论以及删除 Observable.lift 以支持 Observable.pipe 的潜在想法: https://github.com/ReactiveX/rxjs/issues/2911

    TL;DR

    现在让我们比较一下“纯”提升和管道签名:

    // I'm intentionally ignoring pipe's multiple operator function args,
    // since we could redefine lift to also take multiple operator functions
    type Operator = <T, R>(sink: Observer<R>) => Observer<T>
    type lift = <T, R>(src: Observable<T>, op: Operator) => Observable<R>;
    type pipe = <T, R>(op: Operator) => (src: Observable<T>) => Observable<R>
    
    • 管道的操作符函数将 Observable 映射到 Observable
    • lift 的 operator 函数将 Observer 映射到 Observer

    这只是表达两者之一的另一种方式:

    • 构建从源到接收器的 Observable 链
    • 或构建从接收器到源的观察者链

    【讨论】:

      【解决方案2】:

      谁能解释一下这些功能的用途,以及何时应该使用它们?

      lift() 创建一个新的 observable 对象,但 pipe() 没有。 pipe() 遵循函数式编程范式,lift() 是面向对象的。

      它们都接受 函数 作为输入参数,但 pipe() 的优点是不会创建额外的 observable。

      当您使用lift() 时,单个 运算符被附加到一个新的 observable,并且当这个新的 observable 被订阅时,附加的运算符 拦截 订阅前的流。

      这与 pipe() 的工作方式不同,因为运算符返回相同的 observable 不会对原始 observable 产生任何更改。

      pipe() 是在lift() 之后引入的,我认为这是链接运算符的首选方式。

      【讨论】:

      • 我很确定 lift 也会创建一个新的 observable...observable.lift(x =&gt; x) !== observable // true
      • 据我所知,这并不准确。 Pipe 也创建了新的 observables,每个管道操作符一个。 const o$ = from([1, 2, 3]).pipe( map(n =&gt; n * 2), map(n =&gt; n * 2) ); console.log(o$ !== o$.source &amp;&amp; o$.source !== o$.source.source); // Returns true
      • @MathieuRenda 在您的示例中,map 运算符创建了一个新的 observable,这与 pipe() 无关。你想要做的是这个o$ === o$.pipe(),它返回true。 lift() 操作符总是会创建一个新的 observable,而 pipe() 不会。
      【解决方案3】:

      ADT(代数数据类型 - OOP 类的替代品)

      Lifting 通常与 Monad 一起使用。 RXJS 库本身 - 代表 Monad(和其他一堆)ADT。

      “lift”来自 FP 和 ADT。 以下是“电梯”一词的描述性解释: https://wiki.haskell.org/Lifting

      TLDR:

      const sum = a => b => a + b;
      
      console.log(sum(2)(3)); // 5
      
      // and now we need to reuse our "sum" within a Monad level.
      // it means we need to "lift" our "sum" function to the Monad level:
      
      // assumption: our Monad - is just an Array
      
      
      const monad0 = [2];
      const monad1 = [3];
      
      // lift1 === fmap
      // lift1:: Monad M: (a -> b) -> M a -> M b
      const lift1 = f => Ma => [f(Ma[0])];
      
      // lift2:: Monad M: (a -> b) -> M a -> M b -> M c
      const lift2 = f => Ma => Mb => [f(Ma[0])(Mb[0])];
      
      // lift3 etc...
      
      console.log(lift2(sum)(monad0)(monad1)); // [5]
      

      【讨论】:

        猜你喜欢
        • 2017-05-30
        • 2017-03-22
        • 2022-06-26
        • 1970-01-01
        • 2018-05-12
        • 2020-08-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-25
        相关资源
        最近更新 更多