redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。
如果你的Redux需要异步管理,请放心的使用saga来进行管理,如果使用同步可以,那么无需使用saga
saga采用ES6的Generator生成器函数,来实现异步的管理。最常见的就是链式调用,在ES7新出的版本中,async await关键字能更方便的实现链式调用,而且写代码的感觉就像是同步。下面从生成器函数开始;
一、生成器函数Generators
生成器函数是一种特殊类型的函数,与普通函数的不同之处在于: 它可以通过yield关键字来暂停执行并返回一个值,然后再继续执行。
定义方式:
function *gen(){
yield 1
yield 2
yield 3
}
const myGen = gen()]
console.log(myGen.next()) // {value: 1,done: false}
console.log(myGen.next()) // {value: 2,done: false}
console.log(myGen.next()) // {value: 3,done: false}
console.log(myGen.next()) // {value: undefined,done: true}
yield表示在此处停止,等待其后的语句执行完毕返回结果,当调用next时继续往下执行。
需要注意的是:生成器函数不会立即执行,而是在迭代器首次调用next()方法时会执行。
Saga
saga是用于处理Redux的异步编程的,首先先了解一下redux 的基本工作过程:
- 创建store: 使用Redux中的createStore()创建一个Redux store,传入一个reducer函数,reducer是一个纯函数(类似于数组的slice方法,不改变原数组,纯函数是指不改变输入的值的函数),reducer用于来根据当前状态和操作类型来更改状态。
- 分发action: 使用dispatch函数将一个action分发到store中,注意action对象必须是一个包含有type属性和其他的可能数据的对象。
- Reducer处理action: 当一个action被分发到store中时,reducer函数会接收到当前状态和action,然后根据action的type值更新状态。
- 更新store中的状态:reducer函数会返回新的状态并替换当前的状态,并保存在store中。
- 订阅state的变化: 在需要访问store的组件中进行订阅,使用store.subscribe()来监听store的变化,每当状态发生变化时,订阅者都会进行调用重新执行。
Redux在处理异步时,需要借助ReduxThunk或者Redux Promise来进行操作,同样也可以借助saga来进行操作。
Saga更改的是上述步骤的第而步和第三步: saga是通过先创建一个saga任务,监听actions,当监听到action时立即同步执行action对应的异步请求,当请求返回数据后再使用内部函数put分发一个新的action,使用reduer进行更新状态
Saga的使用
- 安装: npm i redux-saga
- 使用:
1.创建saga中间件: 使用createSagaMiddleware()函数创建一个saga中间件
2.定义saga任务: 使用generator函数定义saga任务,该函数通过函数和yield关键字来描述副作用
3.监听action: 通过在saga中使用takeEvery(),takeLastest()等函数监听指定的action’
4.触发action:在组件中触发已监听的action匹配的动作时,redux-saga会自动调用相应的saga任务
5.执行saga任务: saga中间件将启动saga任务,并以非阻塞式的方式执行generators函数
6.处理副作用: 在saga 函数中可以使用一系列的effect函数处理不同类型的操作,例如:使用call函数调用一个需要异步操作的函数,使用put分发一个新的action
7.监控任务状态: 根据需要,使用take,put,takeEvery,all等effect函数执行saga任务的顺序
8.终止saga任务: 可以使用cancel或相关的effect函数来发送一个取消指令‘
9.运行saga中间件: 将saga中间件作为Redux store的参数之一,通过applyMiddleware()函数将其应用到Redux store中。
总的来说:Redux-saga的工作原理是通过监听和相应不同的action触发generators函数中描述的副作用。中间件会以非阻塞式的方式来执行这些副作用,并且提供了内置函数来处理副作用。
使用示例
store.js
import { createStore,applyMiddleware } from 'redux'
import reducer from './reducer'
import createSagaMiddleWare from 'redux-saga'
import WatchSage from './saga'
//利用redux-saga生成一个对象,再将对象传入applyMiddleWare
const SagaMiddleWare = createSagaMiddleWare()
const store = createStore(reducer,
applyMiddleware(SagaMiddleWare))
SagaMiddleWare.run(WatchSage) // 运行saga任务,需要自己手写
export default store
saga.js
import { all } from 'redux-saga/effects'
import watchSaga1 from "./saga/saga1";
import watchSaga2 from "./saga/saga2";
function *WtachSaga(){
// all的作用是将所有的saga函数进行聚合,统一地进行监听
yield all([watchSaga1(),watchSaga2()])
}
export default WtachSaga
saga1.js
import { take, fork,call,put,takeEvery} from 'redux-saga/effects'
// function *watchSaga1(){
// while (true){
// //take函数: 监听组件发来的action
// //fork函数: 同步执行异步处理函数
// yield take("get-list1")
// yield fork(getListArr1)
// }
// }
//第二中写法
function *watchSaga1(){
//takeEvery接受两个参数: 第一个是组件传来的action 第二个参数是向reducer传递的action
yield takeEvery('get-list1',getListArr1)
}
//getListArr专门做异步处理
function *getListArr1(){
// 发异步请求 call函数发异步请求,阻塞式的调用
// put函数发出新的action,非阻塞式的执行
let res = yield call(getListAction1) //call的参数要求是一个返回值是promise对象的函数
yield put({
type: 'change-list1',
payload: res
})
}
function getListAction1(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([1,2,3,4,5])
},2000)
})
}
export default watchSaga1
export {getListArr1}
reducer中的处理:
function reducer(prevState = {
list: [],
list2:[]
},action={}){
const newState = {...prevState}
switch (action.type){
case 'change-list1':
console.log(action)
newState.list = action.payload
return newState
case 'change-list2':
newState.list2 = action.payload
return newState
default:
return newState
}
}
export default reducer
最后在app.js进行触发:
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
另外,采用takeEvery函数也可以实现链式调用和传参
import { all,takeEvery } from 'redux-saga/effects'
import watchSaga1 from "./saga/saga1";
import watchSaga2 from "./saga/saga2";
import {getListArr1} from "./saga/saga1";
import {getListArr2} from './saga/saga2'
function *WtachSaga(){
// all的作用是将所有的saga函数进行聚合,统一地进行监听
yield yield takeEvery('get-list1',getListArr1)
yield yield takeEvery('get-list2',getListArr2)
}
export default WtachSaga
大概就是这样一个过程,只是多加了一步用生成器函数处理异步请求再进行分发触发reducer更新状态