为什么在Redux中需要用于异步流的中间件?

根据文档,“没有中间件,Redux store只支持同步数据流”。我不明白为什么会这样。为什么容器组件不能调用异步API,然后dispatch操作

例如,想象一个简单的UI:一个字段和一个按钮。当用户按下按钮时,该字段将填充来自远程服务器的数据

导入*作为“React”的React;
从“Redux”导入*作为Redux;
从'react redux'导入{Provider,connect};
const ActionTypes={
已启动\u更新:“已启动\u更新”,
更新:“已更新”
};
类异步API{
静态getFieldValue(){
持续承诺=新承诺((解决)=&gt{
设置超时(()=&gt{
解析(Math.floor(Math.random()*100));
}, 1000);
});
回报承诺;
}
}
类应用程序扩展了React.Component{
render(){
返回(
<div>
<输入值={this.props.field}/>
<button disabled={this.props.isWaiting}onClick={this.props.update}>Fetch</button>
{this.props.isWaiting&<div>Waiting…</div>}
</div>
);
}
}
App.propTypes={
调度:React.PropTypes.func,
字段:React.PropTypes.any,
isWaiting:React.PropTypes.bool
};
const reducer=(state={field:'No data',isWaiting:false},action)=>{
开关(动作类型){
案例操作类型。已启动\u更新:
返回{…状态,isWaiting:true};
案例操作类型。已更新:
返回{…状态,isWaiting:false,字段:action.payload};
违约:
返回状态;
}
};
const store=Redux.createStore(reducer);
const ConnectedApp=connect(
(州)=&gt{
返回{…状态};
},
(调度)=&gt{
返回{
更新:()=&gt{
派遣({
类型:ActionTypes.STARTED\u更新
});
AsyncApi.getFieldValue()
。然后(结果=>分派({
类型:ActionTypes.UPDATED,
有效载荷:结果
}));
}
};
})(App);
导出默认类扩展React.Component{
render(){
return<Provider store={store}><ConnectedApp/></Provider>;
}
}

渲染导出的组件时,我可以单击该按钮,输入将正确更新

注意connect调用中的update功能。它调度一个动作,告诉应用程序它正在更新,然后执行异步调用。调用完成后,提供的值作为另一个操作的有效负载进行调度

这种方法有什么问题?为什么我要使用Redux Thunk或Redux Promise,正如文档所示

编辑:我在Redux repo中搜索线索,发现动作创作者在过去被要求是纯功能的。例如,有一个用户试图为异步数据流提供更好的解释:

action creator本身仍然是一个纯函数,但它返回的thunk函数不需要是,它可以执行异步调用

动作创造者不再被要求是纯粹的。所以,thunk/promise中间件在过去是绝对需要的,但现在似乎不再是这样了

这种方法有什么问题?为什么我要使用Redux Thunk或Redux Promise,正如文档所示

这种方法没有错。这在大型应用程序中很不方便,因为不同的组件将执行相同的操作,您可能希望取消某些操作,或者将某些局部状态(如自动递增ID)保持在靠近操作创建者的位置,因此,从维护的角度来看,将动作创建者提取到单独的函数中更容易

您可以阅读我对“如何在超时情况下发送Redux操作”的回答,以了解更详细的演练。

像Redux Thunk或Redux Promise这样的中间件只为发送Thunk或Promise提供了“语法糖”,但您不必使用它

因此,如果没有任何中间件,您的操作创建者可能看起来像

//操作创建者
函数loadData(dispatch,userId){//需要分派,所以它是第一个参数
回传(`http://data.com/${userId}`)
.then(res=>res.json())
.那么(
数据=>分派({type:'LOAD_data_SUCCESS',data}),
err=>分派({type:'LOAD\u DATA\u FAILURE',err})
);
}
//组成部分
组件willmount(){
loadData(this.props.dispatch,this.props.userId);//不要忘记传递dispatch
}

但使用Thunk中间件,您可以这样编写:

//操作创建者
函数loadData(用户ID){
return dispatch=>fetch(`http://data.com/${userId}`//Redux Thunk处理这些
.then(res=>res.json())
.那么(
数据=>分派({type:'LOAD_data_SUCCESS',data}),
err=>分派({type:'LOAD\u DATA\u FAILURE',err})
);
}
//组成部分
组件willmount(){
this.props.dispatch(loadData(this.props.userId));//像往常一样进行调度
}

所以没有什么大的区别。关于后一种方法,我喜欢的一点是组件不关心动作创建者是否是异步的。它只调用dispatch通常,它还可以使用mapDispatchToProps用一个简短的语法绑定这样的动作创建者,等等。组件不知道动作创建者是如何实现的,您可以在不同的异步方法(Redux-Thunk、Redux-Promise、Redux-Saga)之间切换,而无需更改组件。另一方面,使用前一种显式方法,组件确切地知道特定调用是异步的,并且需要通过某种约定(例如,作为同步参数)传递dispatch

还要考虑此代码将如何更改。假设我们想要第二个数据加载函数,并将它们组合到一个动作创建者中

对于第一种方法,我们需要注意我们所称的行动创造者:

//动作创建者
函数loadSomeData(分派,用户ID){
回传(`http://data.com/${userId}`)
.then(res=>res.json())
.那么(
data=>分派({type:'LOAD_SOME_data_SUCCESS',data}),
err=>分派({type:'LOAD_SOME_DATA_FAILURE',err})
);
}
函数loadOtherData(分派,用户ID){
回传(`http://data.com/${userId}`)
.then(res=>res.json())
.那么(
data=>分派({type:'LOAD_OTHER_data_SUCCESS',data}),
err=>分派({type:'LOAD\u OTHER\u DATA\u FAILURE',err})
);
}
函数loadAllData(分派,用户ID){
回报你的承诺(
loadSomeData(dispatch,userId),//首先传递dispatch:它是异步的
loadOtherData(dispatch,userId)//首先传递dispatch:它是异步的
);
}
//组成部分
组件willmount(){
loadAllData(this.props.dispatch,this.props.userId);//首先传递dispatch
}

使用Redux Thunk,动作创建者可以分派其他动作创建者的结果,甚至不考虑它们是同步的还是异步的:

//动作创建者
函数loadSomeData(用户ID){
return dispatch=>fetch(`http://data.com/${userId}`)
.then(res=>res.json())
.那么(
data=>分派({type:'LOAD_SOME_data_SUCCESS',data}),
err=>分派({type:'LOAD_SOME_DATA_FAILURE',err})
);
}
函数loadOtherData(用户ID){
return dispatch=>fetch(`http://data.com/${userId}`)
.then(res=>res.json())
.那么(
data=>分派({type:'LOAD_OTHER_data_SUCCESS',data}),
err=>分派({type:'LOAD\u OTHER\u DATA\u FAILURE',err})
);
}
函数loadAllData(用户ID){
退货派送=>承诺。全部(
分派(loadSomeData(userId)),//正常分派即可!
dispatch(loadOtherData(userId))//正常发送即可!
);
}
//组成部分
组件willmount(){
this.props.dispatch(loadAllData(this.props.userId));//正常发送即可!
}

使用这种方法,如果您以后希望动作创建者查看当前的Redux状态,您可以只使用传递给thunks的第二个getState参数,而不修改调用代码:

函数loadSomeData(userId){
//多亏了Redux Thunk,我可以在这里使用getState(),而无需更改调用者
返回(调度,getState)=&gt{
如果(getState().data[userId].isLoaded){
返回承诺。解决();
}
取回(`http://data.com/${userId}`)
.then(res=>res.json())
.那么(
data=>分派({type:'LOAD_SOME_data_SUCCESS',data}),
err=>分派({type:'LOAD_SOME_DATA_FAILURE',err})
);
}
}

如果需要将其更改为同步,也可以在不更改任何调用代码的情况下执行此操作:

//我可以将其更改为常规操作创建者,而无需接触调用方
函数loadSomeData(用户ID){
返回{
键入:“加载某些数据成功”,
数据:localStorage.getItem('my-data')
}
}

因此,使用Redux Thunk或Redux Promise等中间件的好处在于,组件不知道动作创建者是如何实现的,也不知道它们是否关心Redux状态,它们是同步还是异步的,以及它们是否调用其他动作创建者。缺点是有点间接,但我们相信在实际应用中它是值得的

最后,Redux Thunk和friends只是Redux应用程序中异步请求的一种可能方法。另一个有趣的方法是Redux Saga,它允许您定义长时间运行的守护进程(“Saga”),这些守护进程在执行操作时进行转换或重新执行

发表评论