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

时间:2015-11-16 00:30:16

标签: javascript reactjs rxjs

有人可以帮助我如何将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(),每次渲染组件后都会调用它。但是,结果仍然保持不变。

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

更新:以下是我使用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} />
}
// 

5 个答案:

答案 0 :(得分:3)

更新

要抽象出下面的一些复杂性,请使用重构mapPropsStreamcomponentFromStream。 E.g。

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处理程序
  • stream应取消订阅componentWillUnmount hook

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

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

答案 1 :(得分:2)

一种选择可能是使用Rx.Observable.ofObjectChanges&gt;比照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专家,所以我猜他们可能是其他选择。

答案 2 :(得分:1)

虽然主题可行,但我认为best practice是为了避免在可以使用observable时使用主题。在这种情况下,您可以使用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 ....

在我看来,还有其他时候主题是最佳选择,就像自定义React组件在事件发生时执行函数一样。

答案 3 :(得分:0)

我强烈建议您使用RxJS阅读关于流式道具的博客文章到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);

答案 4 :(得分:0)

您可以使用钩子来做到这一点。

这是代码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;
}