redux-saga 初识
原文链接 redux-saga 是一个管理 Redux 应用异步操作的中间件,功能类似 redux-saga 的 effectsredux-saga中的 Effects 是一个纯文本 JavaScript 对象,包含一些将被 saga middleware 执行的指令。这些指令所执行的操作包括如下三种:
Effects 中包含的指令有很多,具体可以异步API 参考进行查阅 redux-saga 的特点
assert.deepEqual(iterator.next().value,call(Api.fetch,'/products'))
从 redux-thunk 到 redux-saga假如现在有一个场景:用户在登录的时候需要验证用户的 username 和 password 是否符合要求。 使用 redux-thunk 实现获取用户数据的逻辑(user.js): // user.js import request from 'axios'; // define constants // define initial state // export default reducer export const loadUserData = (uid) => async (dispatch) => { try { dispatch({ type: USERDATA_REQUEST }); let { data } = await request.get(`/users/${uid}`); dispatch({ type: USERDATA_SUCCESS,data }); } catch(error) { dispatch({ type: USERDATA_ERROR,error }); } } 验证登录的逻辑(login.js): import request from 'axios'; import { loadUserData } from './user'; export const login = (user,pass) => async (dispatch) => { try { dispatch({ type: LOGIN_REQUEST }); let { data } = await request.post('/login',{ user,pass }); await dispatch(loadUserData(data.uid)); dispatch({ type: LOGIN_SUCCESS,data }); } catch(error) { dispatch({ type: LOGIN_ERROR,error }); } } redux-saga异步逻辑可以全部写进 saga.js 中: export function* loginSaga() { while(true) { const { user,pass } = yield take(LOGIN_REQUEST) //等待 Store 上指定的 action LOGIN_REQUEST try { let { data } = yield call(loginRequest,pass }); //阻塞,请求后台数据 yield fork(loadUserData,data.uid); //非阻塞执行loadUserData yield put({ type: LOGIN_SUCCESS,data }); //发起一个action,类似于dispatch } catch(error) { yield put({ type: LOGIN_ERROR,error }); } } } export function* loadUserData(uid) { try { yield put({ type: USERDATA_REQUEST }); let { data } = yield call(userRequest,`/users/${uid}`); yield put({ type: USERDATA_SUCCESS,data }); } catch(error) { yield put({ type: USERDATA_ERROR,error }); } } 难点解读对于 redux-saga, 还是有很多比较难以理解和晦涩的地方,下面笔者针对自己觉得比较容易混淆的概念进行整理: take 的使用take 和 takeEvery 都是监听某个 action,但是两者的作用却不一致,takeEvery 是每次 action 触发的时候都响应,而 take 则是执行流执行到 take 语句时才响应。takeEvery 只是监听 action,并执行相对应的处理函数,对何时执行 action 以及如何响应 action 并没有多大的控制权,被调用的任务无法控制何时被调用,并且它们也无法控制何时停止监听,它只能在每次 action 被匹配时一遍又一遍地被调用。但是 take 可以在 generator 函数中决定何时响应一个 action 以及 响应后的后续操作。 import { takeEvery } from 'redux-saga' function* watchAndLog(getState) { yield* takeEvery('*',function* logger(action) { //do some logger operation //在回调函数体内 }) } 使用 take 实现如下: import { take } from 'redux-saga/effects' function* watchAndLog(getState) { while(true) { const action = yield take('*') //do some logger operation //与 take 并行 }) } 其中 阻塞和非阻塞call 操作是用来发起异步操作的,对于 generator 来说,call 是阻塞的操作,它在 Generator 调用结束之前不能执行或处理任何其他事情。,但是 fork 却是非阻塞操作,当 fork 调动任务时,该任务会在后台执行,此时的执行流可以继续往后面执行而不用等待结果返回。 例如如下的登录场景: function* loginFlow() { while(true) { const {user,password} = yield take('LOGIN_REQUEST') const token = yield call(authorize,user,password) if(token) { yield call(Api.storeItem({token})) yield take('LOGOUT') yield call(Api.clearItem('token')) } } } 若在 call 在去请求 authorize 时,结果未返回,但是此时用户又触发了 LOGOUT 的 action,此时的 LOGOUT 将会被忽略而不被处理,因为 loginFlow 在 authorize 中被堵塞了,没有执行到 同时执行多个任务如若遇到某个场景需要同一时间执行多个任务,比如 请求 users 数据 和 products 数据,应该使用如下的方式: import { call } from 'redux-saga/effects' //同步执行 const [users,products] = yield [ call(fetch,'/users'),call(fetch,'/products') ] //而不是 //顺序执行 const users = yield call(fetch,products = yield call(fetch,'/products') 当 yield 后面是一个数组时,那么数组里面的操作将按照 源码解读在每一个使用 redux-saga 的项目中,主文件中都会有如下一段将 sagas 中间件加入到 Store 的逻辑: const sagaMiddleware = createSagaMiddleware({sagaMonitor}) const store = createStore( reducer,applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(rootSaga) 其中 createSagaMiddleware 是 redux-saga 核心源码文件 src/middleware.js 中导出的方法: export default function sagaMiddlewareFactory({ context = {},...options } = {}) { ... function sagaMiddleware({ getState,dispatch }) { const channel = stdChannel() channel.put = (options.emitter || identity)(channel.put) sagaMiddleware.run = runSaga.bind(null,{ context,channel,dispatch,getState,sagaMonitor,logger,onError,effectMiddlewares,}) return next => action => { if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } const result = next(action) // hit reducers channel.put(action) return result } } ... } 这段逻辑主要是执行了 export function runSaga(options,saga,...args) { ... const task = proc( iterator,wrapSagaDispatch(dispatch),context,{ sagaMonitor,middleware },effectId,saga.name,) if (sagaMonitor) { sagaMonitor.effectResolved(effectId,task) } return task } 这个函数里定义了返回了一个 task 对象,该 task 是由 proc 产生的,移步 proc.js: export default function proc( iterator,stdChannel,dispatch = noop,getState = noop,parentContext = {},options = {},parentEffectId = 0,name = 'anonymous',cont,) { ... const task = newTask(parentEffectId,name,iterator,cont) const mainTask = { name,cancel: cancelMain,isRunning: true } const taskQueue = forkQueue(name,mainTask,end) ... next() return task function next(arg,isErr){ ... if (!result.done) { digestEffect(result.value,parentEffectId,'',next) } ... } } 其中 digestEffect 就执行了 除了一些核心方法之外,redux-saga 还提供了一系列的 helper 文件,这些文件的作用是返回一个类 iterator 的对象,便于后续的遍历和执行,在此不具体分析。 参考文档
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |