简体   繁体   English

将 React useEffect 钩子与 rxjs mergeMap 运算符一起使用

[英]Using React useEffect hook with rxjs mergeMap operator

I'm trying to implement a data stream that has to use inner observables , where I use one from mergeMap , concatMap etc.我正在尝试实现一个必须使用内部 observables的数据流,其中我使用了来自mergeMapconcatMap等的一个。

eg:例如:

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

  output$$.subscribe(console.log);

This works fine when logging into console.这在登录控制台时工作正常。 But when I try to use it in React like below utilizing useEffect and useState hooks to update some text:但是当我尝试在 React 中使用它时,如下所示,使用useEffectuseState钩子来更新一些文本:

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

it starts acting weird/unpredictable (eg: sometimes the text is updated in the middle of typing, sometimes it doesn't update at all).它开始表现得奇怪/不可预测(例如:有时文本在打字过程中更新,有时根本不更新)。

Things I have noticed:我注意到的事情:

  • If the inner observable completes immediately/is a promise that resolves immediately, it works fine.如果内部 observable 立即完成/是立即解决的承诺,则它可以正常工作。
  • If we print to console instead of useEffect , it works fine.如果我们打印到控制台而不是useEffect ,它工作正常。

I believe this has to do something with the inner workings of useEffect and how it captures and notices outside changes, but cannot get it working.我相信这与useEffect的内部工作原理以及它如何捕获和注意外部变化有关,但无法使其正常工作。
Any help is much appreciated.任何帮助深表感谢。

Minimal reproduction of the case:案例的最小复制:
https://codesandbox.io/s/hooks-and-observables-1-7ygd8 https://codesandbox.io/s/hooks-and-observables-1-7ygd8

I'm not quite sure what you're trying to achieve, but I found a number of problems which hopefully the following code fixes:我不太确定您要实现的目标,但我发现了许多问题,希望以下代码可以修复:

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

I had a similar task but the goal was to pipe and debounce the input test and execute ajax call.我有一个类似的任务,但目标是管道和去抖动输入测试并执行 ajax 调用。 The simple answer that you should init RxJS subject with arrow function in the react hook 'useState' in order to init subject once per init.简单的答案是,您应该在反应钩子“useState”中使用箭头函数初始化 RxJS 主题,以便每个 init 初始化主题一次。

Then you should useEffect with empty array [] in order to create a pipe once on component init.然后你应该 useEffect 和空数组 [] 以便在组件初始化时创建一个管道。

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM