This article mainly introduces the first introduction and use of redux-saga. Now I share it with you and give you a reference.
redux-saga is a middleware that manages asynchronous operations of Redux applications. Its function is similar to redux-thunk async/await. It stores all asynchronous operation logic in one place for centralized processing by creating Sagas.
effects of redux-saga
Effects in redux-saga is a plain text JavaScript object that contains some functions that will be executed by the saga middleware instruction. The operations performed by these instructions include the following three types:
Initiate an asynchronous call (such as sending an Ajax request)
Initiate other action to update the Store
Call other Sagas
There are many instructions included in Effects, which can be found in the asynchronous API reference
Features of redux-saga
Convenient for testing, for example:
assert.deepEqual(iterator.next().value, call(Api.fetch, '/products'))
action can maintain its purity, and asynchronous operations are concentrated in saga for processing
Working form of watch/worker (monitoring->execution)
Is implemented as a generator
It has good support for application scenarios containing complex asynchronous logic
More Implement asynchronous logic in a fine-grained manner to make the process clearer and easier to track and resolve bugs.
Write asynchronous logic in a synchronous manner, which is more in line with human thinking logic
From redux-thunk to redux-saga
Suppose there is a scenario now: when the user logs in, he needs to verify whether the user's username and password meet the requirements.
Use redux-thunk to implement
The logic of obtaining user data (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 verification logic (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
All asynchronous logic can be written into saga.js:
export function* loginSaga() { while(true) { const { user, pass } = yield take(LOGIN_REQUEST) //等待 Store 上指定的 action LOGIN_REQUEST try { let { data } = yield call(loginRequest, { user, 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 }); } }
##Difficult Interpretation
Regarding redux-saga, there are still many things that are difficult to understand and obscure. Below, the author will sort out the concepts that I find more confusing:Use of take
take and takeEvery both monitor an action, but their functions are inconsistent. takeEvery responds every time the action is triggered, while take only responds when the execution flow reaches the take statement. response. takeEvery only listens to the action and executes the corresponding processing function. It does not have much control over when the action is executed and how to respond to the action. The called tasks cannot control when they are called, and they cannot control when to stop listening. It can only be called over and over every time an action is matched. But take can decide when to respond to an action and the subsequent operations after the response in the generator function.For example, to perform logger operation when monitoring all types of action triggers, use takeEvery to implement the following:
import { takeEvery } from 'redux-saga' function* watchAndLog(getState) { yield* takeEvery('*', function* logger(action) { //do some logger operation //在回调函数体内 }) }
import { take } from 'redux-saga/effects' function* watchAndLog(getState) { while(true) { const action = yield take('*') //do some logger operation //与 take 并行 }) }
Blocking and non-blocking
The call operation is used to initiate asynchronous operations. For generators, call is a blocking operation. It cannot be used before the Generator call is completed. Perform or process any other matter. , but fork is a non-blocking operation. When fork mobilizes a task, the task will be executed in the background. At this time, the execution flow can continue to execute without waiting for the result to be returned. For example, the following login scenario: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')) } } }
Execute multiple at the same time Task
If you encounter a scenario where you need to perform multiple tasks at the same time, such as requesting users data and products data, you should use the following method:import { call } from 'redux-saga/effects' //同步执行 const [users, products] = yield [ call(fetch, '/users'), call(fetch, '/products') ] //而不是 //顺序执行 const users = yield call(fetch, '/users'), products = yield call(fetch, '/products')
Source code interpretation
In every project using redux-saga, the main file will have the following logic for adding sagas middleware to the Store:const sagaMiddleware = createSagaMiddleware({sagaMonitor}) const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(rootSaga)
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 } } ... }
这段逻辑主要是执行了 sagaMiddleware(),该函数里面将 runSaga 赋值给 sagaMiddleware.run 并执行,最后返回 middleware。 接着看 runSaga() 的逻辑:
export function runSaga(options, saga, ...args) { ... const task = proc( iterator, channel, wrapSagaDispatch(dispatch), getState, context, { sagaMonitor, logger, onError, 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 就执行了 effectTriggerd() 和 runEffect(),也就是执行 effect,其中 runEffect() 中定义了不同 effect 执行相对应的函数,每一个 effect 函数都在 proc.js 实现了。
除了一些核心方法之外,redux-saga 还提供了一系列的 helper 文件,这些文件的作用是返回一个类 iterator 的对象,便于后续的遍历和执行, 在此不具体分析。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
The above is the detailed content of How to use redux-saga, and what are the methods and techniques for using redux-saga?. For more information, please follow other related articles on the PHP Chinese website!