【问题标题】:What RxJs operators to use for a persistent Spinner with an initial delay?哪些 RxJs 运算符用于具有初始延迟的持久 Spinner?
【发布时间】:2018-03-07 15:47:46
【问题描述】:

尝试同时学习和使用 RxJS,但事实证明这很糟糕!

这是我的要求 - 我有一个 Spinner 组件,从它的 isLoading 属性设置为 true 开始:

  • 如果在initialDelay 之前将isLoading 设置为false,则它永远不会显示
  • 如果在initialDelay 之后但在minSpinTime 之前将isLoading 设置为false,它会持续minSpinTime 然后消失
  • 如果isLoadinginitialDelay之后和minSpinTime之后设置为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 专家。请帮忙!我想我可能需要结合 2 个或更多 observables 才能达到预期的效果。我知道我至少需要其中一个 observables 才能在第一个 observables 之后启动,所以建议使用 switchMap?

编辑 1:这是一个半工作的代码框:https://codesandbox.io/s/72k9qko211,但它不满足第三规范的“同步消失”要求

编辑 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 种情况(切换 isLoading)。希望沟通清楚

【问题讨论】:

    标签: rxjs


    【解决方案1】:

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

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

    zip 运算符用于确保在两个 deactivateLoaderSubject 都被调用并且两个最小延迟都过去时发生停用。

    SwitchLatest 将使外部流保持活动状态,而内部流只发出一次 max 。

    这个解决方案唯一没有考虑到的是,如果你想立即从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);
    };
    

    【讨论】:

    • 嘿,非常感谢您这样做!我在它周围放了一个反应包装器来直观地测试它,它是:@ 987654322@ 我认为仍然失败的第三种情况,它应该与@ 987654329@ 同步消失。在我的测试中,即使在 initialDelay + minSpinTime 过去之后,它似乎也在等待消失。
    • 我没有花足够的时间测试它是否有效。如果没有,我将考虑编写一个更可测试的解决方案,例如您自己的组件沙箱。
    • 我用我喜欢的结构更新了代码框,但它与这里在更多文件和 React 视图中发布的逻辑相同。
    • 这是我所拥有的最好的 stackoverflow/twitter 体验。关闭解决!谢谢
    猜你喜欢
    • 1970-01-01
    • 2021-01-02
    • 1970-01-01
    • 2011-12-25
    • 1970-01-01
    • 1970-01-01
    • 2018-02-08
    • 1970-01-01
    • 2011-11-16
    相关资源
    最近更新 更多