使用Redux-thunk中间件实现ajax数据请求
- 我们之前把异步请求放在TodoList组件的生命周期函数中,当我们放在组件中的逻辑过于复杂的时候就可以使用Redux-thunk将异步请求或者其他比较复杂的逻辑移到action中进行统一管理。
- 在这里我们先使用中间件,关于“什么是中间件”放到下面进行解释。
流程总结
- 安装Redux-thunk中间件
- 在store文件夹下的
index.html
中创建store时,在参数2中使用Redux-thunk(使用方法参考github中的官方文档) - 原本 创建store时 作为 参数2 的 开发者工具
REDUX_DEVTOOLS_EXTENSION
我们可以根据github中的官方文档 换个地方放置。 - 原本写在
TodoList.js
的生命周期函数中的AJAX请求放到actionCreator.js
的一个函数getTodoList
返回的匿名函数中,在匿名函数中进行AJAX请求,并通过调用actionCreator.js
的函数initListAction
返回一个action对象,该匿名函数可以接收到dispatch()
,可以将其产生的action对象传给store。(原本actionCreator.js
中的函数必须返回的action是一个对象才能被dispatch(action)
传递到store中,但通过thunk中间件,action可以是一个函数,当dispatch
发现action是函数时就会自动执行该函数)
安装Redux-thunk中间件
在github中搜索Redux-thunk
可以获取他的安装和使用方法。
1 | yarn add redux-thunk |
在创建store的文件中使用Redux-thunk
**store文件夹下的index.html
**:
- 引入
applyMiddleware
,它使我们可以使用Redux-thunk中间件 - 从
'redux-thunk'
库中引入thunk
- 在创建store的**
createStore()
中添加第二个参数applyMiddleware(thunk)
来引入thunk中间件**。 - 原本我们的在参数2的位置放了
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
,其实**REDUX_DEVTOOLS_EXTENSION
也是一个中间件**,现在需要将它换一个地方。- 参考github中的官方文档:
- 参考github中的官方文档:
compose
需要从redux
中引入。
最终代码:
1 | import { createStore, applyMiddleware, compose } from "redux"; |
注意:中间件是redux的,在创建store的时候使用。不是react的!
通过action返回一个函数(进行AJAX请求)
在actionCreators.js
中,本来action返回的是一个对象,使用了中间件Redux-thunk后,action可以返回一个函数,在这个函数中可以进行异步操作。
中间件Redux-thunk的作用:
本来store.dispatch(action);
中action
必须是一个 对象 ,它才能被传递给store。
使用中间件Redux-thunk后,action
可以是一个函数,当调用store.dispatch(action);
时,它识别到action
是一个函数,便会自动执行该函数。
且dispatch()
会被传递到函数中,在该函数中也可以使用dispatch()
来给store传递数据,在该函数中使用dispatch(action)
时,它**识别到action
是一个对象,便会自动传递给store
**。
中间件Redux-thunk的好处:
把复杂的逻辑(异步操作)放到action中有利于后期自动化测试,比起放在生命周期函数中,放在action中比较方便自动化测试。
TodoList.js:
1 | // 生命周期函数 |
actionCreator.js:
1 | export const getTodoList = ()=>{ |
什么是Redux的中间件
- 中间件指的是action和store的中间。只有redux才有action和store,所以只有redux才有中间件!(不是react,区分清楚)
- 总的来说,redux中间件就是对
dispatch()
做了个升级。- 比如:上面介绍的redux-thunk中间件,本来原始的
dispatch()
只能接收对象类型的action,接收以后直接传递给store。但经过redux-thunk中间件升级后,**dispatch()
可以接收 函数类型的 action**,他会执行该函数。(在函数中就可以进行AJAX请求了)
- 比如:上面介绍的redux-thunk中间件,本来原始的
Redux-saga中间件使用入门
- 之前我们使用redux-thunk解决了异步代码的问题,通过redux-thunk将异步操作放到action函数中,方便了自动化测试和代码的拆分管理。
- Redux-saga中间件 也是用于解决异步代码的问题的,完全可以使用Redux-saga中间件 代替 redux-thunk中间件。
- 目前主流的react项目中有关异步的问题基本都是使用这两个中间件去完成的。
流程总结
- 安装Redux-saga中间件
- 在store文件夹下新建sagas.js文件,在index.js中配置Redux-saga中间件
- 引入’redux-saga’的createSagaMiddleware()用于帮助创造中间件sagaMiddleware
- 和thunk中间件传入方式一样,通过applyMiddleware()将中间件sagaMiddleware放入enhancer中,再将enhancer传入createStore()作为参数2
- 引入存储异步操作的文件sagas.js,并通过sagaMiddleware.run()让文件sagas.js运行起来
- 在sagas.js文件的mySaga()中通过takeEvery()捕获组件中通过dispatch传递的action,捕获到参数1的type类型的action时就执行参数2的回调函数(在该回调函数中随便打印一串字符,运行项目,测试是否可以被捕获成功)
- sagas.js文件的takeEvery()参数2的回调函数中发起AJAX请求并将获取到的数据通过put()传递给store。注意这里使用yield替代Promise,使用try…catch代替then()/catch(),使用saga自带的put()代替store.dispatch。(sagas.js就在store文件夹中,他没有dispatch方法)
删除redux-thunk中间件的一系列操作
在这之前我们使用了redux-thunk中间件,现在我们需要将代码还原到没有使用redux-thunk时的状态。
删除actionCreator.js
中的getTodoList
函数,将异步操作放回TodoList.js
组件的生命周期函数中。记得在store文件夹的index.js
中去除redux-thunk的引用。
安装Redux-saga中间件
在github中搜索Redux-saga,安装:
1 | yarn add redux-saga |
配置Redux-saga中间件
新建文件用于存放异步操作
(参考官方案例中的sagas.js)
在redux-saga中,我们会将异步操作放到一个文件中进行管理。
所以我们需要**在store文件夹下新建sagas.js
**:
1 | function* mySaga() { |
在这里我们先不管函数内容,先配置好Redux-saga中间件。
创建store时引入Redux-saga
(参考官方案例中的main.js)
我们需要在创建store的文件中引入Redux-saga,所以在store文件夹的index.js中:
1 | import { createStore, applyMiddleware, compose } from "redux"; |
配置成功
重新运行后不报错说明配置成功:
使用Redux-saga中间件的小案例
首先我们需要在组件TodoList.js
的生命周期函数中移除AJAX请求的操作,改成一个从actionCreator.js
中返回的对象类型的action(这里我们没有使用thunk中间件了,action必须是对象)
1.actionCreator.js
中的getInitList()不需要返回value,只需要返回一个action对象:
1 | export const getInitList = () => ({ |
2.组件TodoList.js
中调用getInitList()将返回的action对象发给store:
1 | // 生命周期函数 |
3.(参考官方案例中的sagas.js)在store文件夹下的sagas.js
中设置,当获取到type值为GET_INIT_LIST
的action时就执行sagas.js
中的getInitList()
(因为中间件所以sagas.js
中的mySaga()
也能接收到dispatch()
传给store的action
)
1 | import { takeEvery } from 'redux-saga/effects' |
流程总结:
因为在创建store
时我们使用了redux-saga
中间件,所以组件使用dispatch()
将action
传递给store
时,不仅reducer.js
可以接收到这个action,sagas.js
中的mySaga()
也能接收到这个action
,而我们在mySaga()
中通过takeEvery()
声明:“当获取到type值为GET_INIT_LIST
的action时就执行sagas.js
中的getInitList()
”,所以最后会打印abc
使用Redux-saga中间件完成AJAX请求
sagas.js
中的mySaga()
也能接收到dispatch()
传递的action
,并且通过takeEvery()
设置 当获取到type值为 参数1 的action时就执行 参数2 的函数。
所以我们可以将AJAX请求放在takeEvery()
的参数2的回调函数中,AJAX请求获得的数据通过saga的put()传给store。
sagas.js
:
1 | import { takeEvery, put } from 'redux-saga/effects' |
需要注意的是:
- 在saga中发送AJAX请求,使用yield代替promise,yield会等待axios请求成功就赋值给res。
- 没有了Promise的then()和catch(),在这里我们把错误判断放到
try...catch
中 - 在saga文件中并没有store,所以我们需要通过官方文档例子中的put方法将action传给store(替代
store.dispatch()
)
对比redux-thunk和redux-saga
- redux-thunk只是允许action为一个函数,我们可以将异步操作放在该函数中。
- redux-saga是将异步操作直接分离到sagas.js文件中,当action被dispatch传递时,该sagas.js文件中的一个函数也可拦截action,并通过type值确定要执行sagas.js文件中的某个函数,在这个函数中就可进行异步操作。
- redux-saga分离的更加彻底也更加复杂,更适合大型项目,接下来的简书项目会比较偏向使用redux-thunk。
redux-thunk
优点:
- 学习成本低
- 把复杂的逻辑(异步操作)放到action中有利于后期自动化测试,比起放在生命周期函数中,放在action中比较方便自动化测试。
缺点:
- 一个异步请求的action代码过于复杂,且异步操作太分散,相对比saga就显得简单多了。
- action形式不统一,thunk允许action为一个函数,而原本的action是一个对象。如果不一样的异步操作,就要写多个了。
redux-saga
优点
- saga将异步操作直接分离到sagas.js文件中,集中处理了所有的异步操作,异步接口部分一目了然(有提供自己的方法)
- action是普通对象,这跟redux同步的action一模一样(
{type:XXX}
) - 通过Effect,方便异步接口的测试
- 通过worker和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
- 不再使用Promise进行异步操作,所以异步操作的流程是可以控制的,可以随时取消相应的异步操作。
缺点:学习成本高。