在React - Controlling multiple Ajax Calls提出的codelayer1提出的解决方案存在直接在动作创建者中访问状态的问题 - 反模式。
所以,如果我不能访问我的动作创建者内部的状态,我将会在我的组件中监听batchRequestCompleted状态。当组件prop batchRequestCompleted将变为true(表示先前的请求已完成)时,我将检查是否有任何待处理的请求。如果是,我将派遣行动处理下一个请求。所以基本上saga调用动作,反过来修改状态。一旦修改了状态,就会从组件中调度另一个处理进一步请求的操作。通过这种方式,saga永远不会访问该状态。
上面的解决方案听起来不错,但却以Route change before action creator completes中提到的问题为代价。也就是说,如果有人在清除队列之前导航到不同的路由,那么队列内的请求会发生什么。
我是否可以解决React - Controlling multiple Ajax Calls中提到的问题,而无需访问动作创建者内部的状态,也无需将组件重新放入图片中以便调度动作以清除待处理队列。
注意:我创建了一个新问题,因为React - Controlling multiple Ajax Calls中提到的问题已经解决,但有副作用,这个问题主要集中在达到解决方案,以消除这种副作用。
答案 0 :(得分:1)
我做了一些回购github.com/adz5a/so-stream-example
来说明我将如何解决你的问题。
此仓库使用两个库xstream
和recompose
。前者提供ObservableStreams及其运算符的实现,后者使用React连接它。
在所有事情之前都需要一个概念:ES Observables
。在this等文章中深入介绍了这些内容(我强烈建议您阅读和收听Ben Lesh过去关于此主题的文章/会谈)。
Observabes是一个懒惰的原语,用于随时间推移对值进行建模。在JS中,我们有另一个用于执行异步的原语:Promises。那些模型eventual value or error
因此不是懒惰而是渴望。对于React组件(或更一般的UI),我们对lazyness感兴趣,因为事情可能出错:用户可能想要中断长时间运行的进程,它可能崩溃,更改路由等。 ..
那么,我们如何解决您的问题:通过用户交互控制可以中断(获取大量行)的长时间运行流程?
首先,用户界面:
export class AnswerView extends React.Component {
static propTypes = {
// called when the user make a batch
// of request
onStart: PropTypes.func.isRequired,
// called when you want to stop the processing
// of requests ( when unmounting or at the request
// of the user )
onStop: PropTypes.func.isRequired,
// number of requests completed, 0 by default
completedRequests: PropTypes.number.isRequired,
// whether it's working right now or not
processing: PropTypes.bool.isRequired
};
render () {
// displays a form if no work is being done,
// else the number of completed requests
return (
<section>
<Link to="/other">Change Route !</Link>
<header>
Lazy Component Example
</header>
{
this.props.processing ?
<span>{"requests done " + this.props.completedRequests}<button onClick={this.props.onStop}>Stop !</button></span>:
<form onSubmit={e => {
e.preventDefault();
this.props.onStart(parseInt(e.currentTarget.elements.number.value, 10));
}}>
Nb of posts to fetch<input type="number" name="number" placeholder="0"/>
<input type="submit" value="go"/>
</form>
}
</section>
);
}
componentWillMount () {
console.log("mounting");
}
}
非常简单:一个表单,其中包含要执行的请求数量的输入(可以在表组件上复选框...)。
它的道具如下:
componentWillUnmout
。这本身并没有太大作用,所以让我们介绍recompose
。其目的是通过HOC 增强组件。我们将在此示例中使用mapPropsStream
帮助程序。
注意:在这个答案中,我可以互换使用stream / Observable,但在一般情况下不是这样。流是具有operators
的Observable,允许将发射的值转换为新的Observable。
对于React组件,我们可以使用标准api来观察它的道具:在componentWillMount中第一个,然后在componentWillReceiveProps。我们还可以发信号通知componentWillUnmount不再有道具。我们可以构建以下(marble)图表:p1--p2--..--pn--|
(管道表示流的完成)。
增强器代码发布在下面,带有注释。
需要理解的是,所有带流的内容都可以像信号一样接近:通过将所有内容建模为流,我们可以确保通过发送适当的信号,我们可以获得所需的行为。
export const enhance = mapPropsStream(prop$ => {
/*
* createEventHandler will help us generates the callbacks and their
* corresponding streams.
* Each callback invocation will dispatch a value to their corresponding
* stream.
*/
// models the requested number of requests
const { handler: onStart, stream: requestCount$ } = createEventHandler();
// models the *stop* signals
const { handler: onStop, stream: stop$ } = createEventHandler();
// models the number of completed requests
const completedRequestCount$ = requestCount$.map( n => {
// for each request, generate a dummy url list
const urls = Array.from({ length: n }, (_, i) => `https://jsonplaceholder.typicode.com/posts/${i + 1}` );
// this is the trick : we want the process to be aware of itself when
// doing the next operation. This is a circular invocation so we need to
// use a *proxy*. Note : another way is to use a *subject* but they are
// not present in __xstream__, plz look at RxJS for a *subject* overview
// and implementation.
const requestProxy$ = xs.create();
const count$ = requestProxy$
// a *reduce* operation to follow where we are
// it acts like a cursor.
.fold(( n ) => n + 5, 0 )
// this will log the current value
.debug("nb");
const request$ = count$.map( n => Promise.all(urls.slice(n, n + 5).map(u => fetch(u))) )
.map(xs.fromPromise)
.flatten()
.endWhen(xs.merge(
// this stream completes when the stop$ emits
// it also completes when the count is above the urls array length
// and when the prop$ has emitted its last value ( when unmounting )
stop$,
count$.filter(n => n >= urls.length),
prop$.last()
));
// this effectively activates the proxy
requestProxy$.imitate(request$);
return count$;
} )
.flatten();
// models the processing props,
// will emit 2 values : false immediately,
// true when the process starts.
const processing$ = requestCount$.take(1)
.mapTo(true)
.startWith(false);
// combines each streams to generate the props
return xs.combine(
// original props
prop$,
// completed requests, 0 at start
completedRequestCount$.startWith(0),
// boolean indicating if processing is en route
processing$
)
.map(([ props, completedRequests, processing ]) => {
return {
...props,
completedRequests,
processing,
onStart,
onStop
};
})
// allows us to catch any error generated in the streams
// very much equivalent to the new ErrorBoundaries in React
.replaceError( e => {
// logs and return an empty stream which will never emit,
// effectively blocking the component
console.error(e);
return xs.empty();
} );
});
export const Answer = enhance(AnswerView);
我希望这个答案不会(太)令人费解,随时可以提出任何问题。
作为旁注,经过一些研究后你可能会注意到processing
布尔值并没有真正用在逻辑中,而只是帮助UI知道发生了什么:这是一个比将一些状态附加到组件的this
更清洁。