Redux进阶(2)Redux-thunk与Redux-saga中间件

使用Redux-thunk中间件实现ajax数据请求

  • 我们之前把异步请求放在TodoList组件的生命周期函数中,当我们放在组件中的逻辑过于复杂的时候就可以使用Redux-thunk将异步请求或者其他比较复杂的逻辑移到action中进行统一管理
  • 在这里我们先使用中间件,关于“什么是中间件”放到下面进行解释。

流程总结

  1. 安装Redux-thunk中间件
  2. 在store文件夹下的index.html创建store时,在参数2中使用Redux-thunk(使用方法参考github中的官方文档
  3. 原本 创建store时 作为 参数2 的 开发者工具REDUX_DEVTOOLS_EXTENSION 我们可以根据github中的官方文档 换个地方放置。
  4. 原本写在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**:

  1. 引入applyMiddleware,它使我们可以使用Redux-thunk中间件
  2. 'redux-thunk'库中引入thunk
  3. 在创建store的**createStore()中添加第二个参数applyMiddleware(thunk)来引入thunk中间件**。
  4. 原本我们的在参数2的位置放了window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),其实**REDUX_DEVTOOLS_EXTENSION也是一个中间件**,现在需要将它换一个地方。
    1. 参考github中的官方文档:
      官方文档
  5. compose需要从redux中引入。

最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createStore, applyMiddleware, compose } from "redux";
import thunk from 'redux-thunk';
import reducer from "./reducer";

// redux-devtools-extension中间件
const composeEnhancers =
//如果REDUX_DEVTOOLS_EXTENSION_COMPOSE存在就调用它,否则就调用compose函数
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;//compose要从redux中引入

// thunk中间件
const enhancer = composeEnhancers(
applyMiddleware(thunk),
);

// 创建store,将reducers作为第一个参数传给store
const store = createStore(reducer, enhancer);

export default store;

注意:中间件是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
2
3
4
5
6
7
// 生命周期函数
componentDidMount() {
// 将异步AJAX请求放到action中
const action = getTodoList();
// 因为thunk中间件,action可以是一个函数
store.dispatch(action);
}

actionCreator.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const getTodoList = ()=>{
return(dispatch)=>{
// 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据
axios.get("/list.json").then((res) => {
const data = res.data.list;
// 通过initListAction()将data转为action对象
const action = initListAction(data);
// 将action对象传给store
dispatch(action);
}).catch(() => {
alert("失败");
})
}
}

什么是Redux的中间件

  • 中间件指的是action和store的中间。只有redux才有action和store,所以只有redux才有中间件!(不是react,区分清楚)
  • 总的来说,redux中间件就是对dispatch()做了个升级
    • 比如:上面介绍的redux-thunk中间件,本来原始的dispatch()只能接收对象类型的action,接收以后直接传递给store。但经过redux-thunk中间件升级后,**dispatch()可以接收 函数类型的 action**,他会执行该函数。(在函数中就可以进行AJAX请求了)

示意图


Redux-saga中间件使用入门

  • 之前我们使用redux-thunk解决了异步代码的问题,通过redux-thunk将异步操作放到action函数中,方便了自动化测试和代码的拆分管理。
  • Redux-saga中间件 也是用于解决异步代码的问题的,完全可以使用Redux-saga中间件 代替 redux-thunk中间件。
  • 目前主流的react项目中有关异步的问题基本都是使用这两个中间件去完成的。

流程总结

  1. 安装Redux-saga中间件
  2. 在store文件夹下新建sagas.js文件,在index.js中配置Redux-saga中间件
    1. 引入’redux-saga’的createSagaMiddleware()用于帮助创造中间件sagaMiddleware
    2. 和thunk中间件传入方式一样,通过applyMiddleware()将中间件sagaMiddleware放入enhancer中,再将enhancer传入createStore()作为参数2
    3. 引入存储异步操作的文件sagas.js,并通过sagaMiddleware.run()让文件sagas.js运行起来
    4. 在sagas.js文件的mySaga()中通过takeEvery()捕获组件中通过dispatch传递的action,捕获到参数1的type类型的action时就执行参数2的回调函数(在该回调函数中随便打印一串字符,运行项目,测试是否可以被捕获成功)
  3. 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
2
3
4
5
function* mySaga() {

}

export default mySaga;

在这里我们先不管函数内容,先配置好Redux-saga中间件。


创建store时引入Redux-saga

(参考官方案例中的main.js)
我们需要在创建store的文件中引入Redux-saga,所以在store文件夹的index.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "./reducer";
// 1.createSagaMiddleware()用于帮助创造中间件
import createSagaMiddleware from 'redux-saga'
// 5.引入存储异步操作的文件sagas.js
import todoSagas from "./sagas"

// 2.创建redux-saga中间件sagaMiddleware
const sagaMiddleware = createSagaMiddleware()

// redux-devtools-extension中间件(开发者工具)
const composeEnhancers =
//如果REDUX_DEVTOOLS_EXTENSION_COMPOSE存在就调用它,否则就调用compose函数
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;//compose要从redux中引入

// 3.和thunk中间件传入方式一样,通过applyMiddleware传入作为createStore的参数2
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware),
);

// 4.创建store,将reducers作为第一个参数传给store
const store = createStore(reducer, enhancer);

// 6.通过sagaMiddleware.run()让文件sagas.js执行起来
sagaMiddleware.run(todoSagas);

export default store;

配置成功

重新运行后不报错说明配置成功:
效果


使用Redux-saga中间件的小案例

首先我们需要在组件TodoList.js的生命周期函数中移除AJAX请求的操作,改成一个从actionCreator.js中返回的对象类型的action(这里我们没有使用thunk中间件了,action必须是对象)

1.actionCreator.js中的getInitList()不需要返回value,只需要返回一个action对象:

1
2
3
export const getInitList = () => ({
type: GET_INIT_LIST
})

2.组件TodoList.js中调用getInitList()将返回的action对象发给store:

1
2
3
4
5
// 生命周期函数
componentDidMount() {
const action = getInitList();
store.dispatch(action);
}

3.(参考官方案例中的sagas.js)在store文件夹下的sagas.js设置,当获取到type值为GET_INIT_LIST的action时就执行sagas.js中的getInitList()
(因为中间件所以sagas.js中的mySaga()也能接收到dispatch()传给store的action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { takeEvery } from 'redux-saga/effects'
import { GET_INIT_LIST } from "./actionTypes"

function* getInitList() {
yield console.log("abc");
}

// 这里的语法涉及ES6的generator函数
function* mySaga() {
// takeEvery可捕获每次发出的action,并根据type值执行参数2的函数
//参数1为type值,参数2为对应会执行的函数
yield takeEvery(GET_INIT_LIST, getInitList);

}

export default mySaga;

流程总结:
因为在创建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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { takeEvery, put } from 'redux-saga/effects'
import { GET_INIT_LIST } from "./actionTypes"
import axios from "axios";
import { initListAction } from "./actionCreator"

function* getInitList() {
try {
// 借助 `axios` 模块发送`ajax`请求,通过`get()`获取接口路径下的数据
// 在saga中使用yield代替promise,等待axios请求成功就赋值给res
const res = yield axios.get("/list.json");
// 通过initListAction()将list转为action对象
const action = initListAction(res.data.list);
// 是用saga自带的put()替代store.dispatch(action);
yield put(action);
} catch (e) {
console.log("list.json网络请求失败");
}
}

// 这里的语法涉及ES6的generator函数
function* mySaga() {
// takeEvery可捕获每次发出的action,并根据type值执行参数2的函数
//参数1为type值,参数2为对应会执行的函数
yield takeEvery(GET_INIT_LIST, getInitList);

}

export default mySaga;

需要注意的是

  1. 在saga中发送AJAX请求,使用yield代替promise,yield会等待axios请求成功就赋值给res。
  2. 没有了Promise的then()和catch(),在这里我们把错误判断放到try...catch
  3. 在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

优点

  1. 学习成本低
  2. 把复杂的逻辑(异步操作)放到action中有利于后期自动化测试,比起放在生命周期函数中,放在action中比较方便自动化测试。

缺点:

  1. 一个异步请求的action代码过于复杂,且异步操作太分散,相对比saga就显得简单多了。
  2. action形式不统一,thunk允许action为一个函数,而原本的action是一个对象。如果不一样的异步操作,就要写多个了。

redux-saga

优点

  1. saga将异步操作直接分离到sagas.js文件中,集中处理了所有的异步操作,异步接口部分一目了然(有提供自己的方法)
  2. action是普通对象,这跟redux同步的action一模一样({type:XXX})
  3. 通过Effect,方便异步接口的测试
  4. 通过worker和watcher可以实现非阻塞异步调用,并且同时可以实现非阻塞调用下的事件监听
  5. 不再使用Promise进行异步操作,所以异步操作的流程是可以控制的,可以随时取消相应的异步操作。

缺点:学习成本高。


,