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

试图学习和使用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);
};
点赞