使用带有rxjs mergeMap运算符的React useEffect钩子

时间:2019-08-23 18:02:50

标签: javascript reactjs rxjs react-hooks rxjs6

我正在尝试实现必须使用内部可观察对象的数据流,其中我使用了mergeMapconcatMap等中的一个。

例如:

const output$$ = input$$.pipe(
    mergeMap(str => of(str).pipe(delay(10))),
    share()
  );

  output$$.subscribe(console.log);

这在登录控制台时工作正常。 但是,当我尝试像下面那样利用useEffectuseState钩子在React中使用它来更新一些文本时:

function App() {
  const input$ = new Subject<string>();
  const input$$ = input$.pipe(share());
  const output$$ = input$$.pipe(
    mergeMap(str => of(str).pipe(delay(10))),
    share()
  );

  output$$.subscribe(console.log);
  // This works

  const [input, setInput] = useState("");
  const [output, setOutput] = useState("");

  useEffect(() => {
    const subscription = input$$.subscribe(setInput);

    return () => {
      subscription.unsubscribe();
    };
  }, [input$$]);

  useEffect(() => {
    const subscription = output$$.subscribe(setOutput);
    // This doesn't

    return () => {
      subscription.unsubscribe();
    };
  }, [output$$]);

  return (
    <div className="App">
      <input
        onChange={event => input$.next(event.target.value)}
        value={input}
      />
      <p>{output}</p>
    </div>
  );
}

它开始表现出奇怪/不可预测的作用(例如:有时文本会在键入过程中更新,有时甚至根本不会更新)。

我注意到的事情:

  • 如果内部观察对象立即完成/是对 立即解决,效果很好。
  • 如果我们打印到控制台而不是useEffect,则可以正常工作。

我认为这与useEffect的内部运作以及它如何捕获和注意到外部更改有关,但无法使其正常工作。
任何帮助深表感谢。

案件的最低限度复制:
https://codesandbox.io/s/hooks-and-observables-1-7ygd8

2 个答案:

答案 0 :(得分:2)

我不太确定您要实现什么目标,但是我发现了许多问题,希望可以修复以下代码:

function App() {
    // Create these observables only once.
    const [input$] = useState(() => new Subject<string>());
    const [input$$] = useState(() => input$.pipe(share()));
    const [output$$] = useState(() => input$$.pipe(
        mergeMap(str => of(str).pipe(delay(10))),
        share()
    ));

    const [input, setInput] = useState("");
    const [output, setOutput] = useState("");

    // Create the subscription to input$$ on component mount, not on every render.
    useEffect(() => {
        const subscription = input$$.subscribe(setInput);

        return () => {
            subscription.unsubscribe();
        };
    }, []);

    // Create the subscription to output$$ on component mount, not on every render.
    useEffect(() => {
        const subscription = output$$.subscribe(setOutput);

        return () => {
            subscription.unsubscribe();
        };
    }, []);

    return (
        <div className="App">
            <input
                onChange={event => input$.next(event.target.value)}
                value={input}
            />
            <p>{output}</p>
        </div>
    );
}

答案 1 :(得分:0)

我有一个类似的任务,但目标是通过管道传递和消除输入测试的抖动并执行ajax调用。 一个简单的答案是,您应该在react钩子“ useState”中使用箭头功能初始化RxJS主题,以便每次初始化一次即可初始化主题。

然后,您应将Effect与空数组[]配合使用,以便在组件初始化时创建一个管道。

import React, { useEffect, useState } from "react";
import { ajax } from "rxjs/ajax";
import { debounceTime, delay, takeUntil } from "rxjs/operators";
import { Subject } from "rxjs/internal/Subject";

const App = () => {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filterChangedSubject] = useState(() => {
    // Arrow function is used to init Singleton Subject. (in a scope of a current component)
    return new Subject<string>();
  });

  useEffect(() => {
    // Effect that will be initialized once on a react component init. 
    // Define your pipe here.
    const subscription = filterChangedSubject
      .pipe(debounceTime(200))
      .subscribe((filter) => {
        if (!filter) {
          setLoading(false);
          setItems([]);
          return;
        }
        ajax(`https://swapi.dev/api/people?search=${filter}`)
          .pipe(
            // current running ajax is canceled on filter change.
            takeUntil(filterChangedSubject)
          )
          .subscribe(
            (results) => {
              // Set items will cause render:
              setItems(results.response.results);
            },
            () => {
              setLoading(false);
            },
            () => {
              setLoading(false);
            }
          );
      });

    return () => {
      // On Component destroy. notify takeUntil to unsubscribe from current running ajax request
      filterChangedSubject.next("");
      // unsubscribe filter change listener
      subscription.unsubscribe();
    };
  }, []);

  const onFilterChange = (e) => {
    // Notify subject about the filter change
    filterChangedSubject.next(e.target.value);
  };
  return (
    <div>
      Cards
      {loading && <div>Loading...</div>}
      <input onChange={onFilterChange}></input>
      {items && items.map((item, index) => <div key={index}>{item.name}</div>)}
    </div>
  );
};

export default App;