Redux-saga use tips (including sample code),

Address of this article: address of this article

Sample code address for this article: Sample code address, welcome star


Redux-saga: Redux-Saga: Redux-Saga: Redux-Saga: Redux-Saga: Redux-Saga: Redux-Saga: Redux-Saga: Redux-Saga If you understand these two concepts, you can continue reading this article.

  • Disadvantages of redux-thunk handling side effects
  • Redux-saga write a HelloSaga
  • Technical details of redux-Saga use
  • Redux-saga implements a login and listing sample

1. Disadvantages of redux-thunk handling side effects

(1) Side effects management of Redux

The data flow in REdux looks like this:

UI — — — – > action (plain) — – > reducer – – – > state – – – > the UI

Redux follows the rules of functional programming. In the above data flow, action is a plain object and Reducer is a pure function. For synchronous operations without side effects, the above data flow can manage data and control the update of view layer.

But what if there are side effects, such as Ajax asynchronous requests, and so on?

If there are side functions, then we need to deal with the side functions first, and then generate the original JS object. How to handle side effects, choose to use middleware to handle side effects between issuing actions and reducer processing functions in REdux.

The data flow after Redux adds middleware to handle side effects looks like this:

UI – > action (side function) – > middleware – > action (plain) – > reducer – > state – > the UI

Add middleware processing between the action that has side effects and the original action. As we can see from the figure, middleware is used to:

Transform the asynchronous operation to generate the original actions so that the Reducer function can handle the corresponding actions, changing the state and updating the UI.

(2) the story – thunk

In Redux, Thunk is the middleware provided by the authors of Redux. The implementation is extremely simple, with more than 10 lines of code:

function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument);  } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;Copy the code

If the action is a function, call the function as follows:

action(dispatch, getState, extraArgument);
Copy the code

The arguments are dispatch and getState, so we are defining the action as thunk and the general parameters as dispatch and getState.

(3) Disadvantages of redux-thunk

The downside of hunk is also obvious. Thunk only does the execution of the function and doesn’t care what’s inside the function body. That is to say, Thunk allows Redux to accept functions as actions, but inside functions can vary. For example, here is an action for an asynchronous operation to get a list of items:

Export default ()=>(dispatch)=>{fetch('/ API /goodList',{//fecth returns a promise method: 'get', dataType: 'json', }).then(function(json){ var json=JSON.parse(json); if(json.msg==200){ dispatch({type:'init',data:json.data}); } },function(error){ console.log(error); }); };Copy the code

As you can see from this action, which has side effects, there is a lot of complexity inside the function. If you need to define an action like this for every asynchronous operation, it is obviously not easy to maintain.

Action is difficult to maintain because:

  • Action has different forms
  • Asynchronous operations are scattered across actions

Write a HelloSaga

In contrast to redux-thunk,redux-saga is the generator for controlling execution. In Redux-Saga, action is the original JS object, putting all asynchronous side effects inside the saga function. This unifies the form of action and allows asynchronous action sets to be processed centrally.

Redux-saga is implemented via Genetator, or escaped via babel-Polyfill if generator is not supported. Let’s implement an example that outputs HelloSaga.

(1) Create a helloSaga. Js file

export function * helloSaga() { console.log('Hello Sagas! '); }Copy the code

(2) Use redux-Saga middleware in REdux

In the main. In js:

import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import { helloSaga } from './sagas' const sagaMiddleware=createSagaMiddleware(); const store = createStore( reducer, applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(helloSaga); // Will output Hello, Sagas!Copy the code

As with any middleware that calls Redux, if you want to use redux-Saga middleware, simply call an instance of createSagaMiddleware in applyMiddleware. The only difference is that the run method needs to be called so that the generator can begin execution.

3. Technical details of use of Redux-Saga

In addition to the above advantages of unified action and centralized processing of asynchronous operations, redux-Saga uses declarative Effect and provides more delicate control flow.

(1) Declarative Effect

The most important feature of Redux-Saga is that it provides declarative effects. Declarative effects enable Redux-Saga to listen to actions in the form of original JS objects and facilitate unit testing.

  • First of all, Redux-Saga provides a series of apis, such as TAKE, PUT, All, SELECT, etc., which are defined as Effect in Redux-Saga. After these effects are executed, a description object is returned when the function resolve, and the Redux-Saga middleware resumes executing functions in the Generator based on this description object.

Redux-thunk: Redux-thunk:

Action1 (Side function) — > Redux-thunk Listen — > Execute corresponding methods with side effects — > Action2 (Plain Object)

Translating to Action2 is an action in the form of a raw JS object, and then executing the Reducer function updates the state in the store.

The general process of Redux-Saga is as follows:

Action1 (Plain Object) — > Redux-Saga Listen — > Execute corresponding Effect method — > Return description object — > Resume execution of asynchro and side Effect functions — > Action2 (Plain Object)

Compared with redux-thunk, we find that redux-Saga does not execute side effects immediately after listening to the original JS object action. It first converts it into a description object through Effect method, and then uses the description object as an identifier to resume the execution of the side effects function.

By using the Effect class function, we can facilitate unit testing, and we do not need to test the return of the side Effect function. We just need to compare the description object returned by executing the Effect method to see if it is the same as what we expect.

For example, the call method is an Effect class method:

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

function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // ...
}
Copy the code

In the code above, for example, we need to test whether api.fetch returns the expected result by calling the call method and returning a description object. This description object contains the need to call the method and implementation method of actual parameters, we think that as long as the same object description, that is to say as long as the method called and executed when the method of actual parameters, the same as the results of the final affirmation is to meet the expected, it can be convenient for unit testing, There is no need to simulate the exact return result of api.fetch.

import { call } from 'redux-saga/effects' import Api from '... ' const iterator = fetchProducts() // expects a call instruction assert.deepEqual( iterator.next().value, call(Api.fetch, '/products'), "fetchProducts should yield an Effect call(Api.fetch, './products')" )Copy the code

(2) Specific methods provided by Effect

Here are some common effects methods, ranging from low-level apis like Take, Call (apply), fork, PUT, select, etc., to higher-level apis like takeEvery and takeLatest. In this way, we can deepen our understanding of the use of redux-saga. (This section may be awkward, but it will be analyzed with specific examples in Chapter 3. In this section, we have a preliminary understanding of various effects.)

Introduction:

import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
Copy the code
  • take

The take method is used to listen for actions and returns the monitored action object. Such as:

const loginAction = {
   type:'login'
}
Copy the code

Dispatch an action in the UI Component:

dispatch(loginAction)
Copy the code

Used in Saga:

const action = yield take('login');
Copy the code

You can listen for actions passed from the UI to the middleware, and the return from the above take method is the original object of the DiPath. Once the login action is listened on, the action returned is:

{
  type:'login'
}
Copy the code
  • call(apply)

The call and apply methods are similar to call and apply in JS. Let’s take the call method as an example:

call(fn, ... args)Copy the code

The call method calls fn with args and returns a description object. However, the function fn passed in by the call method can be either a normal function or a generator. The Call method is widely used and is implemented in Redux-Saga using common Call methods such as asynchronous requests.

yield call(fetch,'/userInfo',username)
Copy the code
  • put

As mentioned earlier, the workflow for the middleware of Redux-Saga looks like this:

UI – > action1 — – > redux – saga middleware — – > action2 — – > reducer..

From the workflow, we found that after redux-Saga executed the side effect function, it had to issue an action, which was listened to by reducer to update the state. Accordingly, put here corresponds to Dispatch in REdux, and the flow chart is as follows:

It can be seen from the figure that when Redux-Saga performs the action transformation by the side Effect method, the Effect method put is similar to redux’s original dispatch, both of which can send actions, and all the sent actions are monitored by reducer. Use of put:

 yield put({type:'login'})
Copy the code
  • select

The PUT method corresponds to Dispatch in Redux, as well as select if we want to get state in middleware. The select method corresponds to the getState in redux, and the user obtains the state in store.

const state= yield select()
Copy the code
  • fork

The fork method is covered in detail in the example in Chapter 3, but it is equivalent to web work. The fork method does not block the main thread and is useful in non-blocking calls.

  • TakeEvery and takeLatest

TakeEvery and takeLatest are high level apis built on top of take and fork. For example, to listen for login actions, use the takeEvery method:

takeEvery('login',loginFunc)
Copy the code

In addition to the loginFunc method executed when takeEvery listens for the login action, takeEvery can listen for multiple identical actions at the same time.

The takeLatest method is called in the same way as takeEvery:

takeLatest('login',loginFunc)
Copy the code

Unlike takeLatest, which listens for the most recently triggered action.

4. Redux-saga implements a login and listing sample

Next, we implement a redux-Saga sample, where there is a login page, and after successful login, the list page is displayed, and, on the list page, can be

Click logout to return to the landing page. The final display of the example looks like this:

The functional flow chart of the sample is as follows:

Then we follow the above process step by step to achieve the corresponding functions.

(1)LoginPanel(Login page)

The landing page features include

  • Enter always to save the user name
  • Save your password when you type it
  • Click Sign in to request a successful login

I) Save the user name and password when entering

The user name input field and password field onchange trigger the following functions:

 changeUsername:(e)=>{
    dispatch({type:'CHANGE_USERNAME',value:e.target.value});
 },
changePassword:(e)=>{
  dispatch({type:'CHANGE_PASSWORD',value:e.target.value});
}
Copy the code

At the end of the function, two actions are dispatched: CHANGE_USERNAME and CHANGE_PASSWORD.

Listen to these two methods in the saga.js file and execute the side effects function. Finally put issues the transformed action and call the reducer function:

function * watchUsername(){ while(true){ const action= yield take('CHANGE_USERNAME'); yield put({type:'change_username', value:action.value}); } } function * watchPassword(){ while(true){ const action=yield take('CHANGE_PASSWORD'); yield put({type:'change_password', value:action.value}); }}Copy the code

Finally, the reducer received the actions change_username and Change_password from the put method of Redux-saga, and then updated the state.

II) Monitor the login event to determine whether the login is successful

The login event emitted in the UI is:

toLoginIn:(username,password)=>{
  dispatch({type:'TO_LOGIN_IN',username,password});
}
Copy the code

The action for login events is: TO_LOGIN_IN. The handler for login events is:

While (true){const action1=yield take('TO_LOGIN_IN'); const res=yield call(fetchSmart,'/login',{ method:'POST', body:JSON.stringify({ username:action1.username, password:action1.password }) if(res){ put({type:'to_login_in'}); }});Copy the code

In the above handler, first listen for the original action to extract the user name and password passed, and then ask for a successful login. If a successful login returns a value, execute the put action: to_login_IN.

(2) LoginSuccess(display page of LoginSuccess list)

The page functions after successful login include:

  • Get list information and display list information
  • Logout function, click to return to the login page

I) Obtaining list information

import {delay} from 'redux-saga'; function * getList(){ try { yield delay(3000); const res = yield call(fetchSmart,'/list',{ method:'POST', body:JSON.stringify({}) }); yield put({type:'update_list',list:res.data.activityList}); } catch(error) { yield put({type:'update_list_error', error}); }}Copy the code

To demonstrate the request process, we mock the request locally by using the redux-Saga utility function delay. Delay is equivalent to xx seconds, because there is a delay in real requests, so we can use delay locally to simulate the request delay in real scenarios.

II) Logout function

const action2=yield take('TO_LOGIN_OUT');
yield put({type:'to_login_out'});
Copy the code

Similar to login, the logout function accepts action:TO_LOGIN_OUT from the UI and forwards action:TO_LOGIN_OUT

(3) complete implementation of login and logout and list display code

function * getList(){ try { yield delay(3000); const res = yield call(fetchSmart,'/list',{ method:'POST', body:JSON.stringify({}) }); yield put({type:'update_list',list:res.data.activityList}); } catch(error) { yield put({type:'update_list_error', error}); }} function * watchIsLogin(){while(true){const action1=yield take('TO_LOGIN_IN'); const res=yield call(fetchSmart,'/login',{ method:'POST', body:JSON.stringify({ username:action1.username, password:action1.password }) }); If (res.status===10000){yield PUT ({type:'to_login_in'}); Yield Call (getList); } const action2=yield take('TO_LOGIN_OUT'); yield put({type:'to_login_out'}); }}Copy the code

Check whether the login is successful through the request status code. After the login is successful, you can click:

yield call(getList)
Copy the code

Call getList, the function that gets the list of activities. This may seem fine at first, but note that the call method call blocks the main thread, specifically:

  • Statements following the call method cannot be executed until the call method call ends

  • If call(getList) is delayed, the const action2= yieldTake (‘TO_LOGIN_OUT’) statement following call(getList) cannot be executed until the call method returns the result

  • Logout operations during the delay are ignored.

Block diagram can be used for clearer analysis:

The effect of the call method call blocking the main thread is shown in the following GIF:

White screen is the waiting time for the request list. At this point, we click the logout button and cannot respond to the logout function. After the request list is successfully displayed and the list information is displayed, click the logout button and the corresponding logout function will not appear. That is, the call method blocks the main thread.

(4) Non-blocking call

In Chapter 2, we showed that the fork method can be similar to Web Work in that it does not block the main thread. Applying the above example, we can apply:

yield call(getList)
Copy the code

Is amended as:

yield fork(getList)
Copy the code

The result of this presentation is:

The fork method does not block the main thread. If the screen is blank, you can click logout to respond to the logout function immediately and return to the login page.

5. To summarize

Through the above sections, we can summarize all the advantages of Redux-Saga as redux middleware:

  • Unify the form of the action. In Redux-Saga, the action dispatched from the UI is the original object

  • Centralize logic with side effects such as asynchrony

  • By converting the Effects function, unit testing is easy

  • Perfect and rigorous process control can clearly control complex logic.