Redux-Saga
中文文档, 英文文档
我们之前学习过Redux-promise
,Redux-thunk
等处理异步操作的Redux
中间件, 这里的redux-saga
同样是redux
的一个中间件, 与前两个不同的是,redux-saga
是一个用于管理应用程序 Side Effect
(副作用,例如异步获取数据,访问浏览器缓存等)的 中间件,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。前两者在redux
中的使用都有不同程度的破坏redux
中的action
(redux-thunk将其变成一个函数, redux-promise将其变成一个promise对象
),但是redux-saga
不一样,它能非侵入式的结合redux
进行开发,即不修改action
的前提上实现异步操作。
可以想像为,一个 saga
就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga
是一个redux
中间件,意味着这个线程可以通过正常的 redux action
从主应用程序启动,暂停和取消,它能访问完整的 redux state
,也可以 dispatch redux action
。
redux-saga
使用了 ES6
的 Generator
功能,让异步的流程更易于读取,写入和测试。通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript
代码。有点像 async
/await
功能。
实现流程:
由图中我们可以看到, saga
会监听异步操作的action
,一旦有新的异步action
就会白它捕获到,随后吐出一个新的完成了异步处理后的action
随后返回给reducer
再更新到全局的store
中
实现原理:
redux-saga
就是使用内置的副作用函数(fork
,take
,put
,call
)去捕获带有异步操作的dispatch
操作(我们自己定义的字段),随后在saga
生成器函数内部使用call
去执行异步操作取回数据,再使用put
生成一个新的action
对象让dispatch
去捕获!
基本使用:
- 安装
案例展示:
- 模拟异步请求,在页面上设置一个按钮,第一次点击获取数据,第二次点击则获取缓存数据(
全局store中的数据
)
App.js
(组件)
import React, { Component } from 'react' import store from './redux/store'
export default class App extends Component { render() { return ( <div> <button onClick={()=>{ if(store.getState().list.length === 0){ // dispatch更新全局数据 store.dispatch({// 请求数据(异步操作!) type:"get-list" }) }else{ console.log('使用缓存',store.getState().list); } }}>点击获取数据(异步缓存)</button> </div> ) } }
|
store.js
(全局store
)
import { createStore,applyMiddleware } from "redux"; import reducer from './reducer.js' import createSagaMidlleWare from 'redux-saga' import watchSaga from './saga'
const SagaMidlleWare = createSagaMidlleWare() const store = createStore(reducer,applyMiddleware(SagaMidlleWare))
SagaMidlleWare.run(watchSaga)
export default store
|
saga.js
(设置异步捕获处理)
import {take,fork,put,call } from 'redux-saga/effects'
function *watchSaga(){ while(true){ yield take("get-list") yield fork(getList) } }
function *getList(){ let res = yield call(getListAction)
yield put({ type:"change-list", payload:res }) }
function getListAction(){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(["数据1","数据2","数据3"]) },1000) }) }
export default watchSaga
|
reducer.js
(更新全局store
中的状态)
function reducer(preState={ list:[], },action={}){ var newState = {...preState} switch(action.type){ case "change-list": console.log('请求到的数据:',action) newState.list= action.payload return newState default: return preState } }
export default reducer
|
结果展示:
当有多个异步操作时使用saga
进行监听
- 原理与上述是一样的,只不过是当有多个异步
action
需要监听时,我们就设置一个saga
文件夹(如下所示),在这个saga
文件夹中存这捕获对应dispatch
字段的saga
操作,随后在saga.js
文件中引入all
方法,创建一个watchSaga
函数去集中管理对应的saga
异步操作罢了
代码展示:
App.js
(组件)
import React, { Component } from 'react' import store from './redux/store'
export default class App extends Component { render() { return ( <div> <button onClick={() => { if (store.getState().list1.length === 0) { // dispatch更新全局数据 store.dispatch({// 请求数据(异步操作!) type: "get-list1" }) } else { console.log('使用缓存', store.getState().list1); } }}>点击获取数据-1</button>
<button onClick={() => { if (store.getState().list2.length === 0) { //dispatch store.dispatch({ type: "get-list2" }) } else { console.log("缓存", store.getState().list2) }
}}>点击获取数据-2</button> </div> ) } }
|
store.js
(全局状态),与上面的代码一致
import { createStore,applyMiddleware } from "redux"; import reducer from './reducer.js' import createSagaMidlleWare from 'redux-saga' import watchSaga from './saga'
const SagaMidlleWare = createSagaMidlleWare() const store = createStore(reducer,applyMiddleware(SagaMidlleWare))
SagaMidlleWare.run(watchSaga)
export default store
|
saga1.js
(无链式操作)[saga文件夹内
]
import {take,fork,put,call,takeEvery} from 'redux-saga/effects' function *watchSaga1(){ while(true){ yield take("get-list1") yield fork(getList1) } } function *getList1(){ let res = yield call(getListAction1)
yield put({ type:"change-list1", payload:res }) }
function getListAction1(){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(["数据1","数据2","数据3"]) },2000) }) } export default watchSaga1
|
saga2.js
(有链式操作)[saga文件夹内
]
import {take,fork,put,call,takeEvery} from 'redux-saga/effects' function *watchSaga2(){ while(true){ yield take("get-list2") yield fork(getList2) } } function *getList2(){
let res1 = yield call(getListAction2_1) let res2 = yield call(getListAction2_2,res1)
yield put({ type:"change-list2", payload:res2 }) }
function getListAction2_1(){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(["数据4","数据5","数据6"]) },2000) }) }
function getListAction2_2(data){ console.log('接收到前一个yield的数据',data); return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve([...data,"数据7","数据8","数据9"]) },2000) }) } export default watchSaga2
|
saga.js
(集中管理saga
操作)
import {all} from 'redux-saga/effects' import watchSaga1 from './saga/saga1' import watchSaga2 from './saga/saga2'
function *watchSaga(){ yield all([watchSaga1(),watchSaga2()]) }
export default watchSaga
|
reducer
function reducer(prevState = { list1: [],// 状态1 list2: []// 状态2 }, action = {}) { var newState = { ...prevState } switch (action.type) { case "change-list1": console.log('请求到的数据1:',action) newState.list1 = action.payload return newState case "change-list2": console.log('请求到的数据2:',action) newState.list2 = action.payload return newState default: return prevState } }
export default reducer
|
结果展示: