我有一个按名称分组的事件流。
每个生成的observable都会以events$
道具的形式传递给Folder
组件,该组件会在componentDidMount()
中订阅它。
我遇到的问题是Folder
的观察者错过了第一个事件。
要重现,请单击添加事件。您会看到Folder
呈现undefined
,并且不会将其更新为已发出事件中的数据。 Playground
const events$ = new Rx.Subject();
class Folder extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.props.events$.subscribe(e => {
this.setState({
name: e.name,
permissions: e.permissions
});
});
}
componentWillUnmount() {
this.props.events$.unsubscribe();
}
render() {
const { name, permissions } = this.state;
return (
<div>
{`[${permissions}] ${name}`}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super();
this.eventsByName$ = props.events$.groupBy(e => e.name);
this.state = {};
}
componentDidMount() {
this.eventsByName$.subscribe(folderEvents$ => {
const folder = folderEvents$.key;
this.setState({
[folder]: folderEvents$
});
});
}
onClick = () => {
this.props.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
};
render() {
const folders = Object.keys(this.state);
return (
<div>
<button onClick={this.onClick}>
Add event
</button>
<div>
{
folders.map(folder => (
<Folder events$={this.state[folder]} key={folder} />
))
}
</div>
</div>
);
}
}
ReactDOM.render(
<App events$={events$} />,
document.getElementById('app')
);
&#13;
<head>
<script src="https://unpkg.com/rxjs@5.2.0/bundles/Rx.js"></script>
<script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
</head>
<body>
<div id="app"></div>
</body>
&#13;
为了解决丢失的事件,我使用了ReplaySubject(1)
:Playground
const events$ = new Rx.Subject();
class Folder extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.props.events$.subscribe(e => {
this.setState({
name: e.name,
permissions: e.permissions
});
});
}
componentWillUnmount() {
this.props.events$.unsubscribe();
}
render() {
const { name, permissions } = this.state;
return (
<div>
{`[${permissions}] ${name}`}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super();
this.eventsByName$ = props.events$.groupBy(e => e.name);
this.state = {};
}
componentDidMount() {
this.eventsByName$.subscribe(folderEvents$ => {
const folder = folderEvents$.key;
const subject$ = new Rx.ReplaySubject(1);
folderEvents$.subscribe(e => subject$.next(e));
this.setState({
[folder]: subject$
});
});
}
onClick = () => {
this.props.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
};
render() {
const folders = Object.keys(this.state);
return (
<div>
<button onClick={this.onClick}>
Add event
</button>
<div>
{
folders.map(folder => (
<Folder events$={this.state[folder]} key={folder} />
))
}
</div>
</div>
);
}
}
ReactDOM.render(
<App events$={events$} />,
document.getElementById('app')
);
&#13;
<head>
<script src="https://unpkg.com/rxjs@5.2.0/bundles/Rx.js"></script>
<script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
</head>
<body>
<div id="app"></div>
</body>
&#13;
现在,当点击添加事件时,Folder
的观察者会看到该事件并正确呈现其数据。
对我来说这感觉有些笨拙。
是否有更好的方法来组织代码以避免丢失事件问题?
This question可能会有所帮助。
答案 0 :(得分:2)
这确实是一个时间问题。普通主题不会缓存,缓冲或以其他方式保留通过它们发出的任何内容,所以发生的是:
onClick
调用events$.next(event)
,它会通知任何正在侦听的订阅者{1}}正在侦听,因为它会调用eventsByName$
并在之前订阅groupBy
1}} componentDidMount
订阅者调用eventsByName$
,这是批处理的,因为这里的调用堆栈是从my answer to your previous question setState
callstack返回,并刷新onClick
缓冲区setState
订阅者在此之前都已收到通知,而event$
组件还不是其中之一,因为它还没有甚至被创造了。<Folder>
组件,并传递了<Folder>
event$
已安装,导致订阅<Folder>
已提供,但错过了抓住之前发出的event$
事件的机会。这就是使用ADD_FOLDER
工作的原因,它会重播您错过的最后一个事件。
总的来说,我建议远离这种事件模式。由于这是一个用于演示问题的人为例子,我无法确定您尝试使用这些样式解决的问题,但一般来说它与Redux有相似之处。如果您的应用需要此模式,则可以考虑使用Redux或使用ReplaySubject
仅使用RxJS采用相同的模式 - which is here的示例。很难说你是否需要这个,因为你给出的例子很容易就可以在没有RxJS的情况下完成,并且只需正常的React onClick就可以更清楚 - &gt; setState - &gt; rerender - &gt;将状态作为道具传递(在那里没有任何Rx)
我强烈建议不要重新实现这个轮子--Redix有一个充满活力的中间件生态系统和优秀的开发工具,你可以自己输掉。此外,您仍然可以将RxJS与Redux一起使用。我为此维护了一个库,redux-observable。
答案 1 :(得分:1)
我无法确切地告诉你究竟是什么导致了这种情况,虽然这似乎是一个时间问题,因为Subject
只能在第一个click
事件后正确初始化。
但是,对于一个更好的&#34;模式:我建议使用类似Redux的模式。这意味着,创建一个连接到您的事件流的HOC。已经有一个示例实现:https://github.com/MichalZalecki/connect-rxjs-to-react
您的实施与关联实施之间的区别在于它使用context
而不是state
。也许在这种情况下使用context
会更安全,因为context
在React应用程序的生命周期内永远不会被销毁。