前面咱们讲解过redux框架和dva框架的基本使用,由于dva框架中effects模块设计到了redux-saga中的知识点,可能有的同窗们会用dva框架,可是对redux-saga又不是很熟悉,今天咱们就来简单的讲解下saga框架的主要API和如何配合redux框架使用html
http://leonshi.com/redux-saga-in-chinese/index.htmlreact
redux-saga-Demo做者仍是按照之前的风格,提供了两个不一样的版本,简单的 CounterApp, 稍复杂的 TodoListgit
https://github.com/guangqiang-liu/redux-saga-counterAppgithub
https://github.com/guangqiang-liu/redux-saga-todoListDemoredux
redux-saga 是一个用于管理 Redux 应用异步操做的中间件(又称异步 action)。 redux-saga 经过建立 Sagas 将全部的异步操做逻辑收集在一个地方集中处理,能够用来代替 redux-thunk 中间件。
这意味着应用的逻辑会存在两个地方:api
Sagas是经过Generator函数来建立的,若是有不熟悉 Generator函数使用的,请查看阮老师对Generator的介绍promise
Sagas 不一样于thunks,thunks 是在action被建立时调用,而 Sagas只会在应用启动时调用(但初始启动的 Sagas 可能会动态调用其余 Sagas),Sagas 能够被看做是在后台运行的进程,Sagas 监听发起的action,而后决定基于这个 action来作什么:是发起一个异步调用(好比一个 fetch 请求),仍是发起其余的action到Store,甚至是调用其余的 Sagas服务器
在 redux-saga 的世界里,全部的任务都通用 yield Effects 来完成(Effect 能够看做是 redux-saga 的任务单元)。Effects 都是简单的 Javascript 对象,包含了要被 Saga middleware 执行的信息(打个比方,你能够看到 Redux action实际上是一个个包含执行信息的对象), redux-saga 为各项任务提供了各类Effect建立器,好比调用一个异步函数,发起一个action到Store,启动一个后台任务或者等待一个知足某些条件的将来的 actionapp
1、Saga 辅助函数框架
redux-saga提供了一些辅助函数,用来在一些特定的action 被发起到Store时派生任务,下面我先来说解两个辅助函数:takeEvery
和 takeLatest
例如:每次点击 Fetch 按钮时,咱们发起一个 FETCH_REQUESTED 的 action。 咱们想经过启动一个任务从服务器获取一些数据,来处理这个action
首先咱们建立一个将执行异步 action 的任务:
import { call, put } from 'redux-saga/effects' export function* fetchData(action) { try { const data = yield call(Api.fetchUser, action.payload.url); yield put({type: "FETCH_SUCCEEDED", data}); } catch (error) { yield put({type: "FETCH_FAILED", error}); } }
而后在每次 FETCH_REQUESTED action 被发起时启动上面的任务
import { takeEvery } from 'redux-saga' function* watchFetchData() { yield* takeEvery("FETCH_REQUESTED", fetchData) }
注意:上面的 takeEvery 函数可使用下面的写法替换
function* watchFetchData() { while(true){ yield take('FETCH_REQUESTED'); yield fork(fetchData); } }
在上面的例子中,takeEvery 容许多个 fetchData 实例同时启动,在某个特定时刻,咱们能够启动一个新的 fetchData 任务, 尽管以前还有一个或多个 fetchData 还没有结束
若是咱们只想获得最新那个请求的响应(例如,始终显示最新版本的数据),咱们可使用 takeLatest 辅助函数
import { takeLatest } from 'redux-saga' function* watchFetchData() { yield* takeLatest('FETCH_REQUESTED', fetchData) }
和takeEvery不一样,在任什么时候刻 takeLatest 只容许执行一个 fetchData 任务,而且这个任务是最后被启动的那个,若是以前已经有一个任务在执行,那以前的这个任务会自动被取消
2、Effect Creators
redux-saga框架提供了不少建立effect的函数,下面咱们就来简单的介绍下开发中最经常使用的几种
take(pattern)
take函数能够理解为监听将来的action,它建立了一个命令对象,告诉middleware等待一个特定的action, Generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect
用法:
function* watchFetchData() { while(true) { // 监听一个type为 'FETCH_REQUESTED' 的action的执行,直到等到这个Action被触发,才会接着执行下面的 yield fork(fetchData) 语句 yield take('FETCH_REQUESTED'); yield fork(fetchData); } }
put(action)
put函数是用来发送action的 effect,你能够简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect
用法:
export function* toggleItemFlow() { let list = [] // 发送一个type为 'UPDATE_DATA' 的Action,用来更新数据,参数为 `data:list` yield put({ type: actionTypes.UPDATE_DATA, data: list }) }
call(fn, ...args)
call函数你能够把它简单的理解为就是能够调用其余函数的函数,它命令 middleware 来调用fn 函数, args为函数的参数,注意: fn 函数能够是一个 Generator 函数,也能够是一个返回 Promise 的普通函数,call 函数也是阻塞 effect
用法:
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) export function* removeItem() { try { // 这里call 函数就调用了 delay 函数,delay 函数为一个返回promise 的函数 return yield call(delay, 500) } catch (err) { yield put({type: actionTypes.ERROR}) } }
fork(fn, ...args)
fork 函数和 call 函数很像,都是用来调用其余函数的,可是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args)
这一行代码后,会当即接着执行下一行代码语句,而不会等待fn函数返回结果后,在执行下面的语句
用法:
import { fork } from 'redux-saga/effects' export default function* rootSaga() { // 下面的四个 Generator 函数会一次执行,不会阻塞执行 yield fork(addItemFlow) yield fork(removeItemFlow) yield fork(toggleItemFlow) yield fork(modifyItem) }
select(selector, ...args)
select 函数是用来指示 middleware调用提供的选择器获取Store上的state数据,你也能够简单的把它理解为redux框架中获取store上的 state数据同样的功能 :store.getState()
用法:
export function* toggleItemFlow() { // 经过 select effect 来获取 全局 state上的 `getTodoList` 中的 list let tempList = yield select(state => state.getTodoList.list) }
3、createSagaMiddleware()
createSagaMiddleware 函数是用来建立一个 Redux 中间件,将 Sagas 与 Redux Store 连接起来
sagas 中的每一个函数都必须返回一个 Generator 对象,middleware 会迭代这个 Generator 并执行全部 yield 后的 Effect(Effect 能够看做是 redux-saga 的任务单元)
用法:
import {createStore, applyMiddleware} from 'redux' import createSagaMiddleware from 'redux-saga' import reducers from './reducers' import rootSaga from './rootSaga' // 建立一个saga中间件 const sagaMiddleware = createSagaMiddleware() // 建立store const store = createStore( reducers, 将sagaMiddleware 中间件传入到 applyMiddleware 函数中 applyMiddleware(sagaMiddleware) ) // 动态执行saga,注意:run函数只能在store建立好以后调用 sagaMiddleware.run(rootSaga) export default store
4、middleware.run(sagas, ...args)
动态执行sagas,用于applyMiddleware阶段以后执行sagas
注意:动态执行saga语句 middleware.run(sagas)
必需要在store建立好以后才能执行,在 store 以前执行,程序会报错
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import {createStore, applyMiddleware} from 'redux' import createSagaMiddleware from 'redux-saga' import rootSaga from './sagas' import Counter from './Counter' import rootReducer from './reducers' const sagaMiddleware = createSagaMiddleware() let middlewares = [] middlewares.push(sagaMiddleware) const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore) const store = createStoreWithMiddleware(rootReducer) sagaMiddleware.run(rootSaga) const action = type => store.dispatch({ type }) function render() { ReactDOM.render( <Counter value={store.getState()} onIncrement={() => action('INCREMENT')} onDecrement={() => action('DECREMENT')} onIncrementAsync={() => action('INCREMENT_ASYNC')} />, document.getElementById('root') ) } render() store.subscribe(render)
sagas.js
import { put, call, take,fork } from 'redux-saga/effects'; import { takeEvery, takeLatest } from 'redux-saga' export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); function* incrementAsync() { // 延迟 1s 在执行 + 1操做 yield call(delay, 1000); yield put({ type: 'INCREMENT' }); } export default function* rootSaga() { // while(true){ // yield take('INCREMENT_ASYNC'); // yield fork(incrementAsync); // } // 下面的写法与上面的写法上等效 yield* takeEvery("INCREMENT_ASYNC", incrementAsync) }
reducer.js
export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 case 'INCREMENT_ASYNC': return state default: return state } }
从上面的代码结构能够看出,redux-saga的使用方式仍是比较简单的,相比较以前的redux框架的CounterApp,多了一个sagas的文件,reducers文件仍是以前的使用方式
本文所讲解的基本上是redux-saga框架在开发中最常使用到的经常使用API,还有不少不经常使用API,请参照redux-saga官方文档:http://leonshi.com/redux-saga-in-chinese/docs/api/index.html
若是同窗们看到文章这里,仍是对redux-saga框架的基本使用不熟悉,概念模糊,建议看看做者提供的Demo示例
做者建议:同窗们能够将做者以前讲解的redux框架和redux-saga框架对比来学习理解,这样更清楚他们之间的区别和联系。