什么RxJs运算符用于具有初始延迟的持久Spinner?

时间:2018-03-06 17:16:20

标签: rxjs

尝试同时学习和使用RxJS,证明它非常粗糙!

以下是我的要求 - 我有一个Spinner组件,从isLoading道具设置为true开始:

  • 如果isLoadingfalse之前设置为initialDelay,则永远不会显示
  • 如果isLoadingfalse之后但在initialDelay之前设置为minSpinTime,则会minSpinTime继续isLoading然后消失
  • 如果falseinitialDelay之后和minSpinTime之后设置为isLoading,则会显示然后与Subject
  • 同步消失

为了实现这一点,我有一个Subject根据我的UI传递一个布尔值。但是我想将一些运算符应用于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 })); ,但我完全迷失了要使用的运算符。这就是我现在所拥有的(订阅代码使用React但这里不相关):

xstate

我觉得我需要咨询更有经验的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的RxJS实现的,我试图将其转换为使用a - 但是你可以看到它符合规范中的所有3个案例(toggle isLoading)。希望沟通清晰

1 个答案:

答案 0 :(得分:1)

我使用了两个不同的Subject,因为触发加载/未加载与发出加载状态的延迟流之间存在关系。

基本上,我做了两个延迟了一些量的流,如果在延迟之前触发了另一个主题,就会中止。

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);
};