Home  >  Article  >  Web Front-end  >  How to use redux-saga, and what are the methods and techniques for using redux-saga?

How to use redux-saga, and what are the methods and techniques for using redux-saga?

亚连
亚连Original
2018-05-31 14:13:393694browse

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:

  1. Initiate an asynchronous call (such as sending an Ajax request)

  2. Initiate other action to update the Store

  3. 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'))

  1. action can maintain its purity, and asynchronous operations are concentrated in saga for processing

  2. Working form of watch/worker (monitoring->execution)

  3. Is implemented as a generator

  4. It has good support for application scenarios containing complex asynchronous logic

  5. More Implement asynchronous logic in a fine-grained manner to make the process clearer and easier to track and resolve bugs.

  6. Write asynchronous logic in a synchronous manner, which is more in line with human thinking logic

  7. 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 //在回调函数体内
 })
}

Use take to implement the following:

import { take } from 'redux-saga/effects'

function* watchAndLog(getState) {
 while(true) {
  const action = yield take('*')
  //do some logger operation //与 take 并行 
 })
}

where (true) means that once the last step of the process (logger) is reached, a new iteration (logger process) is started by waiting for a new arbitrary action.

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'))
  }
 }
}

If the result is not returned when calling to request authorize, but the user triggers it again at this time LOGOUT action, the LOGOUT at this time will be ignored and not processed, because loginFlow is blocked in authorize and is not executed to take('LOGOUT')

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')

When yield is followed by an array, the operations in the array will be executed according to the execution rules of Promise.all, and the generator will block until all effects are executed

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)

createSagaMiddleware is the method exported in the redux-saga core source code file 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
  }
 }
 ...
 
 }

这段逻辑主要是执行了 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 的对象,便于后续的遍历和执行, 在此不具体分析。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

JavaScript 隐性类型转换步骤浅析

JavaScript的数据类型转换原则

p5.js入门教程之小球动画示例代码

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!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn