根据文档,“没有中间件,Redux store只支持同步数据流”。我不明白为什么会这样。为什么容器组件不能调用异步API,然后dispatch
操作
例如,想象一个简单的UI:一个字段和一个按钮。当用户按下按钮时,该字段将填充来自远程服务器的数据
导入*作为“React”的React;
从“Redux”导入*作为Redux;
从'react redux'导入{Provider,connect};
const ActionTypes={
已启动\u更新:“已启动\u更新”,
更新:“已更新”
};
类异步API{
静态getFieldValue(){
持续承诺=新承诺((解决)=>{
设置超时(()=>{
解析(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(
(州)=>{
返回{…状态};
},
(调度)=>{
返回{
更新:()=>{
派遣({
类型: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)=>{
如果(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”),这些守护进程在执行操作时进行转换或重新执行