本文共 10106 字,大约阅读时间需要 33 分钟。
Redux-saga是一个用于管理 Redux 应用异步操作的中间件(又称异步action)
本质都是为了解决异步action的问题
Redux Saga可以理解为一个和系统交互的常驻进程,这个线程可以通过正常的Redux Action从主应用程序启动,暂停和取消,它能访问完整的Redux state,也可以dispatch Redux Action。 一个 Saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。
其中,Saga可简单定义如下的公式:
Saga = Worker + Watcher
特别提醒: redux-saga是通过ES6中的generator实现的。
下面来创建一个redux-saga例子
创建一个hellosaga.js文件
export function * helloSaga() { console.log('Hello Sagas!');}
在redux项目中使用redux-saga中间件
import React from 'react';import ReactDOM from 'react-dom';import App from './App';import { createStore, applyMiddleware,combineReducers } from 'redux';import rootReducer from './reducers';import { composeWithDevTools } from 'redux-devtools-extension';import createSagaMiddleware from 'redux-saga';import { Provider } from 'react-redux';import { watchIncrementAsync } from './sagas/counter';import { helloSaga } from './sagas'//====1 创建一个saga中间件const sagaMiddleware = createSagaMiddleware();//====2 创建storeconst store = createStore( rootReducer, composeWithDevTools( applyMiddleware(sagaMiddleware) ));//==== 3动态执行saga,注意:run函数只能在store创建好之后调用sagaMiddleware.run(helloSaga);ReactDOM.render(, document.getElementById('root'));
这样代码跑起来,就可以看到控制台输出了Hello Saga
和调用redux的其他中间件一样,如果想使用redux-saga中间件,那么只要在applyMiddleware中调用一个createSagaMiddleware的实例。唯一不同的是需要调用run方法使得generator可以开始执行。
由上图可以看书saga主要做的了三件事
监听action,每监听到一个action,就执行一次操作
允许多个请求同时执行,不管之前是否还有一个或多个请求尚未结束。
import { takeEvery } from 'redux-saga'function* incrementAsync() { // 延迟1s yield delay(1000) yield put({ type: 'increment' }) }// 监听到Action为incrementAsync就会出发incrementAsync函数function* watchIncrementAsync() { yield takeEvery('incrementAsync', incrementAsync)}// 注意watchIncrementAsync这个函数必须在主入口index中运行sagaMiddleware.run(watchIncrementAsync);
监听action,监听到多个action,只执行最近的一次
作用同takeEvery一样,唯一的区别是它只关注最后,也就是最近一次发起的异步请求,如果上次请求还未返回,则会被取消。
function* watchIncrementAsync() { yield takeLatest('incrementAsync', fetchData)}
异步阻塞调用
用来调用异步函数,将异步函数和函数参数作为call函数的参数传入,返回一个js对象。saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。
和js原生的call一样,call函数也可以指定this对象,只要把this对象当第一个参数传入call方法就
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));function* fetchData() { // 2秒后打印saga(阻塞) // yield delay(2000); yield call(delay,2000); console.log('saga'); } // 加了call和不加效果是一样的,saga引入他的主要作用是方便测试,同时也能让我们的代码更加规范化。
异步非阻塞调用,无阻塞的执行fn,执行fn时,不会暂停Generator
非阻塞任务调用机制:上面我们介绍过call可以用来发起异步操作,但是相对于 generator 函数来说,call 操作是阻塞的,只有等 promise 回来后才能继续执行,而fork是非阻塞的 ,当调用 fork 启动一个任务时,该任务在后台继续执行,从而使得我们的执行流能继续往下执行而不必一定要等待返回。
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));function* fetchData() { // 不用等待2秒,直接可以打印出saga,并发执行 yield fork(delay,2000); console.log('saga'); }
相当于dispatch,分发一个action
yield put({ type: 'incrementAsync'})
相当于getState,用于获取store中相应部分的state
function* incrementAsync(action) { let state = yield select(state => console.log('-----',state))}
监听action,暂停Generator,匹配的action被发起时,恢复执行。
export function* watchIncrementAsync() { while(true){ yield take('INCREMENT_ASYNC'); // 监听 yield fork(incrementAsync); } // yield takeLatest(INCREMENT_ASYNC, incrementAsync); //takeLatest}
创建一个Effect描述信息,针对 fork 方法返回的 task ,可以进行取消关闭。cancel(task)
创建一个Effect描述信息,指示 middleware 在多个 Effect 之间运行一个 race(与 Promise.race([…]) 的行为类似)。
race可以取到最快完成的那个结果,常用于请求超时
创建一个 Effect 描述信息,指示 middleware 并行运行多个 Effect,并等待它们全部完成。这是与标准的Promise#all相对应的 API。
import { call } from 'redux-saga/effects'// 正确写法, effects 将会同步执行const [userInfo, repos] = yield [ call(fetch, '/users'), call(fetch, '/repos')];// 这两个请求是并行的
下面是这个简单demo的目录结构
包含了同步,异步,网络请求,希望这个简单的demo带你学会redux-saga
入口文件
import React from 'react';import ReactDOM from 'react-dom';import App from './App';import { createStore, applyMiddleware,combineReducers } from 'redux';import rootReducer from './reducers';import { composeWithDevTools } from 'redux-devtools-extension';import createSagaMiddleware from 'redux-saga';import { Provider } from 'react-redux';import rootSage from './sagas';//====1 创建一个saga中间件const sagaMiddleware = createSagaMiddleware();//====2 创建storeconst store = createStore( rootReducer, composeWithDevTools( applyMiddleware(sagaMiddleware) ));//==== 3动态执行saga,注意:run函数只能在store创建好之后调用sagaMiddleware.run(rootSage);ReactDOM.render(, document.getElementById('root'));
import React, { Component } from 'react';import { connect } from 'react-redux';import { increment,incrementAsync,decrement } from './actions/counter';import './app.css'import { get_user } from './actions/user';class App extends Component { constructor(props){ super(props); } render() { const { message } = this.props.user; return ({ this.props.counter }); }}//映射组件props的数据部分const mapStateToProps = (state) => { return { counter: state.counter, user: state.user };};//映射组件props的函数部分// const mapDispatchToProps = (dispatch) => {// return {// increment:(dispatch)=>{dispatch(increment)}// }// };export default connect(mapStateToProps, { increment,incrementAsync,decrement,get_user })(App);{ message }
export const INCREMENT = 'INCREMENT';export const INCREMENT_ASYNC = 'INCREMENT_ASYNC';export const DECREMENT = 'DECREMENT'//count+1export const increment = () => { return { type: INCREMENT }};//count-1export const decrement = () => { return { type:DECREMENT }}//异步增加export const incrementAsync = () => { return { type: INCREMENT_ASYNC }};
export const get_user = () => { return { type: 'FETCH_REQUEST' }};
import { combineReducers } from 'redux';import counter from './counter';import user from './user';// 合并所有的reducesexport default combineReducers({ counter, user});
import { INCREMENT , DECREMENT} from '../actions/counter';const counter = (state = 1, action ) => { switch(action.type) { case INCREMENT: return state + 1; case DECREMENT: { return state-1 } default: return state; }}export default counter;
const initialState = { message: '等待', age:'20' }; const user = (state = initialState, action) => { switch(action.type) { case "FETCH_REQUEST": return { ...state, message: '请求中' } case "FETCH_SUCCEEDED": return { ...state, message: '詹姆斯' } case "FETCH_FAILURE": return { ...state, message: '请求失败' } default: return state; } } export default user;
import { all } from 'redux-saga/effects';import { counterSagas } from './counter';import { userSagas } from './user';// 合并所有需要监听的sagaexport default function* rootSage() { yield all([ ...counterSagas, ...userSagas ])}
//import { delay } from 'redux-saga';import { takeEvery, call, put,take,fork,takeLatest,select,all} from 'redux-saga/effects';import { INCREMENT_ASYNC ,INCREMENT_TAKE,DECREMENT} from '../actions/counter';const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));function* incrementAsync(action) { let state = yield select(state => console.log(state)) //yield fork(delay,2000); //yield delay(2000); yield call(delay,2000) yield put({ type: 'INCREMENT' }) //yield fork(()=>{return put({ type: 'INCREMENT' })}); // yield all([ // //call(delay,2000), // yield put({ type: 'INCREMENT',data:'9898' }), // yield put({ type: 'INCREMENT--' ,data:'000'}), // yield put({ type: 'INCREMENT----' }) // ]) //同步的方式来写异步代码 // yield put({ type: 'INCREMENT' });}export function* watchIncrementAsync() { while(true){ yield take('INCREMENT_ASYNC'); yield fork(incrementAsync); yield fork(()=>{console.log('--------')}) yield fork(()=>{console.log('--------')}) } yield takeLatest(INCREMENT_ASYNC, incrementAsync); //takeLatest}export const counterSagas = [ //fork(()=>{console.log('---------')}), watchIncrementAsync(), watchIncrementAsync(), watchIncrementAsync(),]
import { takeEvery, call, put,all } from 'redux-saga/effects';import axios from 'axios';const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));function* fetchUser() { try { //axios.get('https://jsonplaceholder.typicode.com/users') const user = yield call(axios.get, "https://jsonplaceholder.typicode.com/users"); yield put({type: "FETCH_SUCCEEDED"}) } catch(e) { yield put({type: "FETCH_FAILURE"}); }}function* watchFetchUser() { yield all([ takeEvery('FETCH_REQUEST', fetchUser), // 监听发出Action为FETCH_REQUEST,然后出发请求函数fetchUser ]) }export const userSagas = [ watchFetchUser()]
优点:
(1)副作用转移到单独的saga.js中,不再掺杂在action.js中,保持 action 的简单纯粹,又使得异步操作集中可以被集中处理。对比redux-thunk
(2)redux-saga 提供了丰富的 Effects,以及 sagas 的机制(所有的 saga 都可以被中断),在处理复杂的异步问题上更顺手。提供了更加细腻的控制流。
(3)对比thunk,dispatch 的参数依然是一个纯粹的 action (FSA)。
(4)每一个 saga 都是 一个 generator function,代码可以采用 同步书写 的方式 去处理 异步逻辑(No Callback Hell),代码变得更易读。
(5)同样是受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理。
缺点:
(1)generator 的调试环境比较糟糕,babel 的 source-map 经常错位,经常要手动加 debugger 来调试。
(2)redux-saga 不强迫我们捕获异常,这往往会造成异常发生时难以发现原因。因此,一个良好的习惯是,相信任何一个过程都有可能发生异常。如果出现异常但没有被捕获,redux-saga 的错误栈会给你一种一脸懵逼的感觉。
转载地址:http://epqni.baihongyu.com/