如何将React状态绑定到RxJS可观察流

How to bind React state to RxJS observable stream?

本文关键字:RxJS 观察 绑定 React 状态      更新时间:2023-09-26

有人能帮我如何将React State绑定到RxJS Observable吗?我做了类似的事情

componentDidMount() {
  let source = Rx.Observable.of(this.state.val)
}

理想的结果是,每当this.state.val更新(通过this.setState(...))时,source也会更新,所以我可以将source与其他RxJS可观测流相结合。

然而,在这种情况下,即使在this.state.val被更新并且组件被重新渲染之后,source也只被更新一次。

// Ideal result:
this.state.val = 1
source.subscribe(val => console.log(x)) //=> 1
this.state.val = 2
source.subscribe(val => console.log(val)) //=> 2
// Real result:
this.state.val = 1
source.subscribe(val => console.log(x)) //=> 1
this.state.val = 2
source.subscribe(val => console.log(val)) //=> 1 ???WTH

这可能是因为componentDidMount()在React生命周期中只调用过一次。所以我把CCD_ 8移到CCD_。然而,结果仍然是一样的。

所以问题是如何在this.state.val更新时使source更新

更新:这是我用来解决问题的解决方案,使用Rx.Subject

// Component file
constructor() {
  super(props)
  this.source = new Rx.Subject()
_onChangeHandler(e) {
 this.source.onNext(e.target.value)
}
componentDidMount() {
  this.source.subscribe(x => console.log(x)) // x is updated
}
render() {
  <input type='text' onChange={this._onChangeHandler} />
}
// 

更新

要抽象出以下一些复杂性,请使用recompose的mapPropsStream或componentFromStream。例如

const WithMouseMove = mapPropsStream((props$) => {
  const { handler: mouseMove, stream: mouseMove$ } = createEventHandler();
  const mousePosition$ = mouseMove$
    .startWith({ x: 0, y: 0 })
    .throttleTime(200)
    .map(e => ({ x: e.clientX, y: e.clientY }));
  return props$
    .map(props => ({ ...props, mouseMove }))
    .combineLatest(mousePosition$, (props, mousePosition) => ({ ...props, ...mousePosition }));
});
const DumbComponent = ({ x, y, mouseMove }) => (
  <div
    onMouseMove={mouseMove}
  >
    <span>{x}, {y}</span>
  </div>
);
const DumbComponentWithMouseMove = WithMouseMove(DumbComponent);

原始帖子

对于OP更新后的答案的一个稍微更新的答案,使用rxjs5,我提出了以下内容:

class SomeComponent extends React.Component {
  constructor(props) {
    super(props);
    this.mouseMove$ = new Rx.Subject();
    this.mouseMove$.next = this.mouseMove$.next.bind(this.mouseMove$);
    this.mouseMove$
      .throttleTime(1000)
      .subscribe(idx => {
        console.log('throttled mouse move');
      });
  }
  componentWillUnmount() {
    this.mouseMove$.unsubscribe();
  }
  render() {
    return (
      <div
       onMouseMove={this.mouseMove$.next}
      />
    );
  }
}

一些值得注意的补充:

  • onNext()现在是next()
  • 绑定可观察的next方法可以将其直接传递给mouseMove处理程序
  • 应在componentWillUnmount挂钩中取消订阅流

此外,在组件constructor钩子中初始化的主题流可以作为属性传递给1+个子组件,这些子组件都可以使用任何可观察的next/error/complete方法推送到流。下面是我放在一起的一个jsbin示例,演示了多个组件之间共享的多个事件流。

很好奇是否有人对如何更好地封装这种逻辑以简化绑定和取消订阅等内容有想法。

一个选项可以是使用Rx.Observable.ofObjectChanges>参见https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/ofobjectchanges.md

但是:

  • 它使用Object.observe,这不是一个标准功能,因此在一些浏览器中必须进行聚合填充,并且实际上正在从ecmascript中删除(参见。http://www.infoq.com/news/2015/11/object-observe-withdrawn)。这不是未来的选择,但它很容易使用,所以如果它只是为了你自己的需要,为什么不呢

另一种选择是根据您的用例,以三种方法之一使用主题:shouldComponentUpdatecomponentWillUpdatecomponentDidUpdate。查阅https://facebook.github.io/react/docs/component-specs.html用于执行每个功能时。在其中一种方法中,您将检查this.state.val是否已更改,如果更改了,则会在主题上发出其新值。

我不是reactjs专家,所以我想它们可能是其他选择。

尽管主题会起作用,但我认为最好的做法是在可以使用可观察对象时避免使用主题。在这种情况下,您可以使用Observable.fromEvent:

class MouseOverComponent extends React.Component {
  componentDidMount() {
    this.mouseMove$ = Rx.Observable
      .fromEvent(this.mouseDiv, "mousemove")
      .throttleTime(1000)
      .subscribe(() => console.log("throttled mouse move"));
  }
  componentWillUnmount() {
    this.mouseMove$.unsubscribe();
  }
  render() {
    return (
      <div ref={(ref) => this.mouseDiv = ref}>
          Move the mouse...
      </div>
    );
  }
}

ReactDOM.render(<MouseOverComponent />, document.getElementById('app'));

这是代码笔上的。。。。

在我看来,在其他时候,Subject是最好的选择,比如当事件发生时,自定义React组件执行函数。

我强烈建议阅读这篇关于使用RxJS:将道具流式传输到React组件的博客文章

https://medium.com/@fahad19/使用-rxjs-with-react-js-part-2-streaming-pros-to-component-c7792bc1f40f

它使用FrintJS,并应用observe高阶组件以流形式返回道具:

import React from 'react';
import { Observable } from 'rxjs';
import { observe } from 'frint-react';
function MyComponent(props) {
  return <p>Interval: {props.interval}</p>;
}
export default observe(function () {
  // return an Observable emitting a props-compatible object here
  return Observable.interval(1000)
    .map(x => ({ interval: x }));
})(MyComponent);

您可以使用钩子来完成。

这是的代码示例

import { Observable, Subscription } from 'rxjs';
import { useState, useEffect } from 'react';
export default function useObservable<T = number | undefined>(
    observable: Observable<T | undefined>,
    initialState?: T): T | undefined {
    const [state, setState] = useState<T | undefined>(initialState);
    useEffect(() => {
        const subscription: Subscription = observable.subscribe(
            (next: T | undefined) => {
                setState(next);
            },
            error => console.log(error),
            () => setState(undefined));
        return () => subscription.unsubscribe();
    }, [observable])
    return state;
}