有人可以帮助我如何将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} />
}
//
答案 0 :(得分:3)
要抽象出下面的一些复杂性,请使用重构mapPropsStream或componentFromStream。 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处理程序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)。不是未来的选择,但它易于使用,所以如果只是为了您自己的需要,为什么不呢。其他选项是根据您的使用情况使用三种方法之一中的主题:shouldComponentUpdate
,componentWillUpdate
,componentDidUpdate
。参看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组件:
它使用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;
}