我們知道 Rx 是 push 模式翻斟。比如 1 秒產(chǎn)生一個(gè)事件的流,即使我們不去使用它苞尝,它也會(huì)產(chǎn)生事件畸肆。如何人為的停止這個(gè)流是今天我們要解決的問題。
先把場(chǎng)景搭建好宙址,從頁面中得到按鈕的 click 事件流:
import React, { useRef, useEffect } from "react";
import { fromEvent } from "rxjs";
export default function App() {
const btnRef = useRef(null);
useEffect(() => {
const stopBtnClick$ = fromEvent(btnRef.current, "click");
const subscription = stopBtnClick$.subscribe(() => {
console.log("單擊事件");
});
return () => {
subscription.unsubscribe();
};
});
return (
<div className="App">
<button ref={btnRef}>停止按鈕</button>
</div>
);
}
然后我們使用 interval 操作符創(chuàng)建一個(gè)間隔為 1 秒發(fā)生的事件流:
const perSecond$ = interval(1000);
const subscription = perSecond$.subscribe((value) => {
console.log(value);
});
我們知道 subscribe 函數(shù)會(huì)返回一個(gè) subscription 對(duì)象轴脐,這個(gè)對(duì)象上有個(gè) unsubscribe 函數(shù),調(diào)用這個(gè)函數(shù)將會(huì)停止事件流。這也是我們?yōu)槭裁匆?useEffect 的返回值中調(diào)用它豁辉,防止內(nèi)存泄露令野。既然如此,停止 perSecond$ 就簡單了:
const stopBtnClick$ = fromEvent(btnRef.current, "click");
const perSecond$ = interval(1000);
const perSecondSubscription = perSecond$.subscribe(value => {
console.log(value);
});
stopBtnClick$.subscribe(_ => {
perSecondSubscription.unsubscribe();
});
這樣做確實(shí)可以解決我們的問題徽级,然而這是完全錯(cuò)誤的做法气破。
之前的文章說過,Rx 編程模型是組合編程理念的最佳實(shí)踐餐抢。讓我們來考慮一下如何組合 stopBtnClick$ 和 perSecond$ 來解決我們的問題:
takeUntil:這個(gè)操作符接收另一個(gè)流作為參數(shù)现使, 等于它監(jiān)控了兩個(gè)流。它做的事就是從原始的流接收事件旷痕,但不是一直接收碳锈,當(dāng)參數(shù)流中開始產(chǎn)生事件時(shí),它就停止接收原始流的事件了欺抗,也就是把原始流停掉了售碳。
有了這個(gè)操作符,就可以組合使用之前的兩個(gè)流了:
const stopBtnClick$ = fromEvent(btnRef.current, "click");
const perSecond$ = interval(1000);
const intervalCanBeStopped$ = perSecond$.pipe(takeUntil(stopBtnClick$));
const subscription = intervalCanBeStopped$.subscribe(v => console.log(v));
我們?cè)俳o時(shí)間間隔流加一個(gè)開始按鈕绞呈,完整代碼如下:
import React, { useRef, useEffect } from "react";
import { fromEvent, interval } from "rxjs";
import { takeUntil, switchMapTo } from "rxjs/operators";
export default function App() {
const stopBtnRef = useRef(null);
const startBtnRef = useRef(null);
useEffect(() => {
const stopBtnClick$ = fromEvent(stopBtnRef.current, "click");
const startBtnClick$ = fromEvent(startBtnRef.current, "click");
const perSecond$ = interval(1000);
const intervalCanBeStopped$ = perSecond$
.pipe(takeUntil(stopBtnClick$));
const subscription = startBtnClick$.pipe(
switchMapTo(intervalCanBeStopped$)
).subscribe(v => console.log(v));
return () => {
subscription.unsubscribe();
};
});
return (
<div className="App">
<button ref={startBtnRef}>開始按鈕</button>
<button ref={stopBtnRef}>停止按鈕</button>
</div>
);
}
switchMapTo:這個(gè)操作符接收一個(gè)事件流參數(shù)贸人,丟棄原始的事件流,使用參數(shù)提供的事件流繼續(xù)運(yùn)行佃声。
我們沒有對(duì)之前的程序邏輯做任何修改艺智,只是添加了一個(gè)開始按鈕的事件流,再和之前的事件流組合起來完成了開始輸出圾亏,定時(shí)輸出十拣,停止輸出的功能。有沒有點(diǎn)兒搭積木的意思志鹃?
有問題請(qǐng)?zhí)砑游⑿殴娞?hào)“讀一讀我”夭问。