Rxjs 为什么mapTo只更改一次?

Rxjs 为什么mapTo只更改一次?,rxjs,rxjs-observables,rxjs-pipeable-operators,Rxjs,Rxjs Observables,Rxjs Pipeable Operators,我正在制作秒表,当我想第二次重置时钟时,它不会改变。 第一次单击时,它会设置h:0、m:0、s:0。但当再次单击时,它不会设置h:0、m:0、s:0,秒表将继续 const events$ = merge( fromEvent(startBtn, 'click').pipe(mapTo({count: true})), click$.pipe(mapTo({count: false})), fromEvent(resetBtn, 'click').pipe(mapTo(

我正在制作秒表,当我想第二次重置时钟时,它不会改变。 第一次单击时,它会设置h:0、m:0、s:0。但当再次单击时,它不会设置h:0、m:0、s:0,秒表将继续

const events$ = merge(
    fromEvent(startBtn, 'click').pipe(mapTo({count: true})),
    click$.pipe(mapTo({count: false})), 
    fromEvent(resetBtn, 'click').pipe(mapTo({time: {h: 0, m: 0, s: 0}})) // there is reseting
    )
    
const stopWatch$ = events$.pipe(
    startWith({count: false, time: {h: 0, m: 0, s: 0}}), 
    scan((state, curr) => (Object.assign(Object.assign({}, state), curr)), {}), 
    switchMap((state) => state.count
    ? interval(1000)
        .pipe(
            tap(_ => {
                if (state.time.s > 59) {
                    state.time.s = 0
                    state.time.m++
                }
                if (state.time.s > 59) {
                    state.time.s = 0
                    state.time.h++
                }
                const {h, m, s} = state.time
                secondsField.innerHTML = s + 1
                minuitesField.innerHTML = m
                hours.innerHTML = h
                state.time.s++
            }),
        )
    : EMPTY)
stopWatch$.subscribe()
问题 您正在使用可变状态并将其更新为observable发出的事件的副作用(tap就是这样做的)

一般来说,产生副作用间接改变产生副作用的流是个坏主意。因此,创建日志或显示值不太可能引起问题,但改变对象然后将其注入流中很难维护/缩放

一种修复方法: 创建一个新对象

//fromEvent(resetBtn,'click').pipe(映射到({time:{h:0,m:0,s:0}))
fromEvent(resetBtn,'click').pipe(映射({time:{h:0,m:0,s:0})))
尽管应该是一个创可贴解决方案,但这应该奏效。 预加工解决方案 这是我刚才做的秒表。下面是它的工作原理。通过给秒表一个
control$
observable来创建秒表(在本例中,我使用了一个名为
controller
的主题)

控制$
发出
“启动”
时,秒表启动;当它发出
“停止”
时,秒表停止;当它发出
“重置”
时,秒表将计数器设置回零。当
control$
出错、完成或发出
“END”
时,秒表会出错或完成

函数createStopwatch(控件$:可观察,间隔=1000):可观察{
返回延迟(()=>{
let toggle:boolean=false;
让计数:数字=0;
常量代码=()=>{
返回计时器(0,间隔)。管道(
映射(x=>count++)
)
}
返回控制$.pipe(
catchError(=>of(“END”)),
s=>concat(s,of(“END”)),
过滤器(控制=>
控件==“开始”||
控件==“停止”||
控件==“重置”||
控件==“结束”
),
开关映射(控件=>{
如果(控件==“开始”&&&!切换){
切换=真;
返回ticker();
}else if(控制==“停止”&切换){
切换=假;
返回空;
}否则如果(控制==“重置”){
计数=0;
如果(切换){
返回ticker();
}
}
返回空;
})
);
});
}
//适合您的代码:)
const controller=新主题();
const seconds$=创建秒表(控制器);
fromEvent(startBtn,'click').pipe(映射到(“开始”)).subscribe(控制器);
fromEvent(resetBtn,'click').pipe(映射到(“重置”)).subscribe(控制器);
秒$.subscribe(秒=>{
secondsField.innerHTML=秒%60;
minutesfield.innerHTML=数学地板(秒/60)%60;
hours.innerHTML=Math.floor(秒/3600);
});
作为奖励,您可能可以看到如何在不重置计时器的情况下制作一个按钮,使
停止
此计时器

没有主题 这里有一种更惯用的反应方式。它通过直接合并DOM事件(中间没有主题)为秒表生成一个
控件$

这确实会剥夺您编写类似于
controller.next(“重置”)
将您自己的值随意注入流中
控制器。完成()当应用程序使用秒表完成时(尽管您可能会通过其他事件自动完成)

。。。
//适合您的代码:)
创建秒表(合并)(
fromEvent(开始,单击)。管道(映射到(“开始”),
fromEvent(resetBtn,'click')。管道(映射到(“重置”))
)).订阅(秒=>{
secondsField.innerHTML=秒%60;
minutesfield.innerHTML=数学地板(秒/60)%60;
hours.innerHTML=Math.floor(秒/3600);
});

我怀疑发生这种情况,因为每次调用
mapTo()
时都使用相同的对象实例。尝试使用
map(()=>({time:{h:0,m:0,s:0}}))
instad.@martin它很有效,谢谢你,非常感谢:)