如何仅使用RxJS操作符实现React自定义计时器清零并重启间隔
RxJS 自定义计时器重置功能修复方案
原有代码核心问题
- 重置按钮点击回调未向
actionReset$发送信号:当前写为() => actionReset$仅返回了Subject实例,未调用.next()触发事件,导致repeatWhen无法收到重置通知 interval流声明在组件外部,多次挂载组件会复用同一个流,出现状态串扰useEffect依赖仅设置了timerOn,存在闭包陷阱,订阅回调中读取的状态永远是初始挂载时的值- 未绑定重置时将
time置为0的逻辑,就算重启了间隔流也不会清零计时值 - 未定义
onStopClick方法,Stop按钮无响应
修正后代码实现
import { useState, useEffect } from 'react'; import { interval, Subject, takeUntil, switchMap, tap } from 'rxjs'; function CustomTimer () { const [time, setTime] = useState(0); const [timerOn, setTimerOn] = useState(false); // 所有Subject放到组件内部,避免多实例串扰 const actionReset$ = new Subject(); const actionStop$ = new Subject(); const unmount$ = new Subject(); useEffect(() => { if (!timerOn) { // 计时器未开启时直接返回 return () => unmount$.next(); } // 构建计时流 const subscription = actionReset$.pipe( // 每次重置信号触发时,先把时间清0,再启动新的interval流 tap(() => setTime(0)), switchMap(() => interval(1000)), // 收到停止信号或者组件卸载时终止流 takeUntil(actionStop$), takeUntil(unmount$) ).subscribe(count => { setTime(count + 1); }); // 组件挂载/计时器开启时先触发一次重置,启动首次计时 actionReset$.next(); return () => { subscription.unsubscribe(); unmount$.next(); }; }, [timerOn]); const onStopClick = () => { actionStop$.next(); setTimerOn(false); }; return ( <div className="mainBlock"> {!timerOn && ( <button onClick={() => setTimerOn(true)}>Start</button> )} {timerOn && ( <button onClick={onStopClick}>Stop</button> )} {time > 0 && ( {/* 点击重置按钮发送信号 */} <button onClick={() => actionReset$.next()}>Reset</button> )} <div>当前计时:{time}秒</div> </div> ); } export default CustomTimer;
关键改动说明
- 用
switchMap切换流:每次收到重置信号时,销毁旧的interval流,启动新的流,天然实现间隔重启 - 重置信号触发时先用
tap操作符执行置0逻辑,满足重置为0的需求 - 新增
actionStop$处理停止逻辑,和重置逻辑解耦 - 所有RxJS流放到组件内部,避免多实例冲突
- 修复了重置按钮的点击事件,正确调用
.next()发送信号
内容的提问来源于stack exchange,提问作者Александр Соколов
相关产品推荐
相关产品推荐

