试图学习和使用RxJS同时证明是非常粗糙的!
这是我的要求 – 我有一个Spinner组件,从isLoading prop设置为true开始:
>如果在initialDelay之前将isLoading设置为false,则它永远不会显示
>如果在initialDelay之后但在minSpinTime之前将isLoading设置为false,则它会持续minSpinTime然后消失
>如果在initialDelay之后和minSpinTime之后将isLoading设置为false,它将显示然后与isLoading同步消失
为了实现这一点,我有一个Subject,它根据我的UI传递一个布尔值.但是我想将一些运算符应用于Subject,但我完全迷失了要使用的运算符.这就是我现在所拥有的(订阅代码使用React,但这与此无关):
subject
.audit(
(val: boolean) =>
val ? Rx.Observable.interval(initialDelay) : Rx.Observable.interval(0)
)
// doesnt work, false is merely delayed, i want it synchronous
.audit(
(val: boolean) =>
val ? Rx.Observable.interval(0) : Rx.Observable.interval(minSpinTime)
)
// this also doesnt work, false goes thru too fast
// .switchMap(
// (val: boolean) =>
// val
// ? Rx.Observable.of(true)
// : Rx.Observable.of(false).throttleTime(minSpinTime) //auditTime also doesnt work here
// )
.subscribe((val: boolean) => this.setState({ show: val }));
我觉得我需要咨询更有经验的RxJS人.请帮忙!我想我可能需要结合两个或更多的可观察量来达到预期的效果.我知道我需要至少有一个这样的observable在第一个之后启动,所以建议使用switchMap?
编辑1:这是一个半工作的代码盒:https://codesandbox.io/s/72k9qko211但它不符合第3规范的“同步消失”要求
编辑2:我实际上有一个完整的组件示例:http://tsiq-ui-components.s3-website-us-east-1.amazonaws.com/?knob-isLoading=true&knob-initialDelay=3000&knob-minSpinTime=3000&selectedKind=Components%2FIcons&selectedStory=3000ms%20SmartSpinner&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybooks%2Fstorybook-addon-knobs但它是用@ DavidKPiano的xstate实现的,我试图将其转换为使用RxJS – 但你可以看到它符合规范中的所有3个案例(toggle isLoading ).希望沟通清楚
最佳答案 我使用了两个不同的主题,因为触发加载/未加载和发出加载状态的延迟流之间存在关系.
基本上,我做了两个延迟一些数量的流,如果在延迟之前触发了另一个主题,则中止.
zip运算符用于确保在调用deactivateLoaderSubject时都发生停用,并且都已经过了最小延迟.
SwitchLatest将保持外部流的活动,而内部流只发出一次最大值.
这个解决方案唯一没有考虑的是,如果你想要立即从deactivateLoaderStream发出
initialDelay.我无法弄清楚为什么会出现这个问题,所以我选择了最简单的解决方案.
代码盒在这里:https://codesandbox.io/s/mq380ol5nj
在发布此答案时,代码如下所示:
import React from "react";
import { render } from "react-dom";
import Rx from "rxjs";
const initialDelay = 1000;
const minSpinTime = 1000;
const activateLoaderSubject = new Rx.Subject();
const deactivateLoaderSubject = new Rx.Subject();
const activateLoaderStream = activateLoaderSubject.switchMap(() =>
Rx.Observable.of(null)
.delay(initialDelay)
.takeUntil(deactivateLoaderSubject)
);
const deactivateLoaderStream = activateLoaderSubject.switchMap(() =>
Rx.Observable.zip(
Rx.Observable.of(null)
.delay(initialDelay + minSpinTime)
.takeUntil(activateLoaderSubject),
deactivateLoaderSubject
).take(1)
);
const initialState = { loading: null };
const activateLoaderReducerStream = activateLoaderStream.map(() => state => ({
...state,
loading: true
}));
const deactivateLoaderReducerStream = deactivateLoaderStream.map(
() => state => ({ ...state, loading: false })
);
const stateStream = Rx.Observable.merge(
activateLoaderReducerStream,
deactivateLoaderReducerStream
)
.startWith(initialState)
.scan((state, reducer) => reducer(state));
stateStream.forEach(state => {
render(
<div>{JSON.stringify(state, null, 2)}</div>,
document.getElementById("root")
);
});
// Removes loader directly when deactivate is triggered
const finnishVeryLate = () => {
activateLoaderSubject.next();
setTimeout(() => {
deactivateLoaderSubject.next();
}, initialDelay + minSpinTime + 1000);
};
// Shows loader for minSpinTime
const finnishLate = () => {
activateLoaderSubject.next();
setTimeout(() => {
deactivateLoaderSubject.next();
}, initialDelay + 1);
};
// Doesn't show loader
const finnishEarly = () => {
activateLoaderSubject.next();
setTimeout(() => {
deactivateLoaderSubject.next();
}, initialDelay - 1);
};