Javascript 如何有条件地分支rxjs流?

Javascript 如何有条件地分支rxjs流?,javascript,rxjs,observable,reactive-programming,Javascript,Rxjs,Observable,Reactive Programming,我试图模拟“画笔”功能,就像任何图像编辑器中的功能一样 我有以下信息流: $pointerDown:指针按下 $pointerUp:指针向上按下 $position:笔刷的位置 $escape:按escape键 我想做什么 当用户拖动鼠标时,执行临时计算。如果鼠标已启动,则提交这些更改。如果按下退出键,则不要提交这些更改 我目前正在处理的是第一个案件: $pointerDown.pipe( r.switchMap(() => $position.pipe(

我试图模拟“画笔”功能,就像任何图像编辑器中的功能一样

我有以下信息流:

  • $pointerDown
    :指针按下
  • $pointerUp
    :指针向上按下
  • $position
    :笔刷的位置
  • $escape
    :按escape键
我想做什么 当用户拖动鼠标时,执行临时计算。如果鼠标已启动,则提交这些更改。如果按下退出键,则不要提交这些更改

我目前正在处理的是第一个案件:

$pointerDown.pipe(
   r.switchMap(() =>
       $position.pipe(
           r.throttleTime(150),
           r.map(getNodesUnderBrush),
           r.tap(prepareChanges),
           r.takeUntil($pointerUp),
           r.finalize(commitBrushStroke))
)).subscribe()
如何以两种不同的方式结束流?rxjs的惯用用法是什么


谢谢

如果我理解正确,当用户按下退出键时,整个流应该被取消订阅,这样,当用户再次开始向下拖动鼠标指针时,流再次开始发射

如果是这种情况,您可能希望使用
$escape
merge
尝试
switchMap
,换句话说,类似这样的操作

const drag$ = $pointerDown.pipe(
   r.switchMap(() =>
       $position.pipe(
           r.throttleTime(150),
           r.map(getNodesUnderBrush),
           r.tap(prepareChanges),
           r.takeUntil($pointerUp),
           r.finalize(commitBrushStroke))
))
const brush$ = $escape.pipe(
   startWith({}),  // emit any value at start just to allow the stream to start
   switchMap(() => drag$)
);
const stopBrush$ = $escape.pipe(tap(() => // do stuff to cancel));
merge(brush$, stopBrush$).subscribe();
整个想法是,只要
$escape
发出,先前订阅的
drag$
就会被取消订阅,并启动一个新的订阅。同时,可以执行任何逻辑来取消需要取消的内容


我不能测试这个东西,所以我希望我没有忘记什么。

关于你的问题,我可以看出你需要随着时间的推移有某种状态。在这里,您的
状态
是可观察到的
指针下移/移动/拖动
,需要
累计
清除
,最后发出。当我看到这样的
状态
场景时,我总是喜欢使用操作符:

Pre

为了简单的例子,我没有使用预定义的观察值。如果您在将特定指针用例调整到这个非常类似的用例时遇到问题,我可以尝试更新它,使其更接近您的问题

1。什么可以代表我的状态

在这里,我使用enum[status]来稍后对之前发生的事件作出反应,并使用累积[acc]来计算时间点

2。编写改变状态的函数

您的需求可以分为:累积[pointerdown$+position$],完成[pointerup$],退出[escape$]

3。将您的功能映射到您的可观察对象

merge(
  move$.pipe(map(accumulate)),
  finish$.pipe(map(finish)),
  escape$.pipe(map(escape))
)
4。使用扫描中随时间变化的状态所在的功能

scan((acc: State, fn: (state: State) => State) => fn(acc), DEFAULT_STATE)
5。处理您的变异状态

在这里,我们只想在完成时进行处理,所以我们对它进行过滤

内部状态样本

  • move$.next('1')=状态:{status:move,acc:['1']}
  • escape$.next()=状态:{status:escape,acc:[]}
  • move$.next('2')=状态:{status:move,acc:['2']}
  • finish$.next()=状态:{status:finish,acc:['2']}
  • 运行


    仅供参考:用rxjs解决状态问题的心态非常难。但是一旦你理解了这个想法背后的流程,你就可以在几乎所有的全州场景中使用它。您将避免副作用,坚持rxjs工作流,您可以轻松优化/调试代码。

    非常有趣的问题

    以下是我的方法:

    const down$=fromEvent(div,'mousedown');
    const up$=fromEvent(div,'mouseup');
    const calc$=of(‘计算’);
    常量esc$=fromEvent(文档“keyup”)
    .烟斗(
    过滤器((e:KeyboardEvent)=>e.code==='Escape')
    );
    down$//流的源-mousedown事件
    .烟斗(
    switchMapTo(计算$.pipe(//进行一些计算
    切换映射到(
    merge(//`merge()`-要在`up$`或`esc$`发出时停止计算吗
    up$.pipe(映射到({shouldCommit:true})),
    esc$.pipe(映射到({shouldCommit:false})),
    ).pipe(first())/`first()`-这两个选项中的任何一个都可以停止计算;
    )
    ))
    )
    .subscribe(console.log)
    

    .

    我认为使用
    toArray()
    takeUntil()
    可以非常简单地做到这一点。我假设,当你说你想要“提交”更改时,你想要收集所有更改并同时处理它们。否则,同样的方法也适用于
    buffer()

    $pointerDown.pipe(
       switchMap(() => $position.pipe(
         throttleTime(150),
         map(getNodesUnderBrush),
         tap(prepareChanges), // ?
         takeUntil($pointerUp),
         toArray(),
         takeUntil($escape),
      )
    ).subscribe(changes => {
      ...
      commitBrushStroke(changes);
    })
    

    因此,整个技巧是在
    toArray
    之前还是之后完成内链。当您在
    toArray()
    之前完成它时,
    toArray()
    将发出一个数组,其中包含迄今为止收集到的所有更改。如果您在
    toArray()
    之后完成,则链将被释放,
    toArray()
    将放弃所有内容并取消订阅。

    下面是一个与您的原始解决方案非常相似的其他可能解决方案:

        const start$ = fromEvent(document, "mousedown").pipe(
          tap((event: MouseEvent) => this.start = `x: ${event.clientX}, y: ${event.clientY}`)
        );
        const drag$ = fromEvent(document, "mousemove").pipe(
          tap((event: MouseEvent) => (this.move = `x: ${event.clientX}, y: ${event.clientY}`)      )
        );
        const stop$ = fromEvent(document, "mouseup").pipe(
          tap((event: MouseEvent) => (this.stop = `x: ${event.clientX}, y: ${event.clientY}`))
        );
        const cancel$ = fromEvent(document, "keydown").pipe(
          filter((e: KeyboardEvent) => e.code === "Escape"),
          tap(() => this.stop = 'CANCELLED')
        );
    
        const source$ = start$
          .pipe(
            mergeMap(() =>
              drag$.pipe(
                takeUntil(merge(stop$, cancel$))
              )
            )
          )
          .subscribe();
    
    流可以通过两种方式结束,即合并两种条件:

    takeUntil(merge(stop$, cancel$))
    

    以下是Stackblitz:

    谢谢您的回答@Picci。如何为转义密钥添加自定义处理?因为我正在调用的
    prepareChanges
    向我的数据集中添加了一些临时字段,如果用户按下
    escape
    键取消,我需要清理这些字段。另外,我认为取消订阅仍然会导致
    r.finalize(commitbrushroke)
    运行,因为它将在
    complete
    error
    unsubscribe
    上运行。我希望找到某种类似这样的操作员:
    直到这两个通知器中的一个发出某种信息,然后继续给定的流
    我已经用一些可能适用于您的案例的东西更新了我的回答。只需在
    finalize
    上做一个注释。根据我的理解,
    finalize
    在源代码可见的完成或错误时被触发,而不是在取消订阅时。如果您希望在取消订阅时触发逻辑,则需要创建
    $pointerDown.pipe(
       switchMap(() => $position.pipe(
         throttleTime(150),
         map(getNodesUnderBrush),
         tap(prepareChanges), // ?
         takeUntil($pointerUp),
         toArray(),
         takeUntil($escape),
      )
    ).subscribe(changes => {
      ...
      commitBrushStroke(changes);
    })
    
        const start$ = fromEvent(document, "mousedown").pipe(
          tap((event: MouseEvent) => this.start = `x: ${event.clientX}, y: ${event.clientY}`)
        );
        const drag$ = fromEvent(document, "mousemove").pipe(
          tap((event: MouseEvent) => (this.move = `x: ${event.clientX}, y: ${event.clientY}`)      )
        );
        const stop$ = fromEvent(document, "mouseup").pipe(
          tap((event: MouseEvent) => (this.stop = `x: ${event.clientX}, y: ${event.clientY}`))
        );
        const cancel$ = fromEvent(document, "keydown").pipe(
          filter((e: KeyboardEvent) => e.code === "Escape"),
          tap(() => this.stop = 'CANCELLED')
        );
    
        const source$ = start$
          .pipe(
            mergeMap(() =>
              drag$.pipe(
                takeUntil(merge(stop$, cancel$))
              )
            )
          )
          .subscribe();
    
    takeUntil(merge(stop$, cancel$))