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。处理您的变异状态
在这里,我们只想在完成时进行处理,所以我们对它进行过滤
内部状态样本
仅供参考:用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$))