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

How to bind React state to RxJS observable stream?

有人可以帮我如何将 React State 绑定到 RxJS Observable 吗?我做了某事

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

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

但是,在这种情况下,source 只会更新一次,即使在更新 this.state.val 并重新呈现组件之后也是如此。

// 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 生命周期中只调用了一次。所以我将 source 移动到 componentDidUpdate() ,每次渲染组件后都会调用它。然而,结果还是一样。

所以问题是如何在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} />
}
// 

一种选择是使用 Rx.Observable.ofObjectChanges > cf。 https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/ofobjectchanges.md.

然而 :

  • 它使用的 Object.observe 不是标准功能,因此必须在某些浏览器中进行 polyfill,并且实际上已从 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 专家,所以我想他们可能是其他选择。

更新

要抽象出下面的一些复杂性,请使用重组的 mapPropsStream or 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);

原版Post

对于 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()
  • 绑定 observable next 方法允许它直接传递给 mouseMove 处理程序
  • 应在 componentWillUnmount 挂钩中取消订阅流

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

想知道是否有人有关于如何更好地封装此逻辑以简化绑定和取消订阅等内容的想法。

虽然 subject 会起作用,但我认为 best practice 是为了在可以使用 observable 时避免使用 subject。在这种情况下,您可以使用 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'));

这里是 codepen.....

在我看来,有时 Subject 是最佳选择,例如自定义 React 组件在事件发生时执行函数。

我强烈建议阅读此博客 post,了解使用 RxJS 将 props 流式传输到 React 组件:

https://medium.com/@fahad19/using-rxjs-with-react-js-part-2-streaming-props-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);

你可以使用钩子来完成。

这是代码sample

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