[redux] redux saga learning notes

Preface

  • Writing this stuff almost makes my laziness to the extreme. I don't know where to write it, so I don't want to write it. But we can only improve by forcing ourselves to write step by step...

What is Redux Saga

  • In essence, Redux saga is the middleware of redux. Here Last redux article It can be found that the middleware of redux is implemented by applyMiddleware. The essence of this function is to combine a complex function through compose to rewrite store.dispatch.
  • Reducer is a pure function. Take the previous reducer as an example:
function reducer1(state={count:5},action){//The initial value is allocated to each reducer for use
    switch (action.type){
        case 'ADD1':
            return {
                ...state,count:state.count+action.payload
            };
        case 'MINUS1':
            return {
                ...state,count:state.count-action.payload
            };
    }
    return state
}
  • Its return result only depends on the parameters, without any additional side effects.
  • But in practice, we need to do some asynchronous operations (such as request) or not pure operations (change the external state). These are called "side effects" in the functional programming paradigm.
  • Redux saga is a middleware used to deal with side effects (asynchronous tasks). It is a process manager that receives events and may trigger new events, managing complex processes for your application.

classification

  • worker saga does practical work, such as calling API, making asynchronous request and obtaining asynchronous encapsulation result.
  • The watcher saga listens to the actions of the dispatch, and calls the worker to execute the task when receiving the action or knowing that it is triggered.
  • The only entry to root saga saga.

Use

cnpm i redux react-redux redux-saga  --save
import React  from 'react';
import ReactDOM from 'react-dom'
import { createStore,applyMiddleware,combineReducers } from 'redux'
import createSagaMiddleware from 'redux-saga'
let sagaMiddleware=createSagaMiddleware()
function counter(state = {number:0}, action) {
    switch (action.type) {
      case 'ADD':
        return {...state,number:state.number+1}
      case 'MINUS':
        return {...state,number:state.number-1}
      default:
        return state
    }
  }
let combinereducer =combineReducers({
    counter
})
let store = applyMiddleware(sagaMiddleware)(createStore)(combinereducer)
function *rootsaga(){
    console.log('yehuozhili');
}
sagaMiddleware.run(rootsaga)
ReactDOM.render(<p>cxzxc</p>,document.getElementById('root'))
  • Start the project and the console will output.
  • Then modify it, make a counter component, and realize the function of adding one.

Counter.js

import React from 'react'
import {connect}from 'react-redux'
import actions from './actions'
function Counter(props) {
    return (
        <div>
        <div>{props.number}</div>
            <button onClick={()=>props.add()}>+</button>
        </div>
    )
}
export default connect(state=>state.counter, actions)(Counter)

actions.js

export default{
    add(){
        return {type:'ADD'}
    }
}

index.js

import React  from 'react';
import ReactDOM from 'react-dom'
import { createStore,applyMiddleware,combineReducers } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Counter from './counter'
import {Provider} from 'react-redux'
let sagaMiddleware=createSagaMiddleware()
function counter(state = {number:0}, action) {
    switch (action.type) {
      case 'ADD':
        return {...state,number:state.number+1}
      case 'MINUS':
        return {...state,number:state.number-1}
      default:
        return state
    }
  }
let combinereducer =combineReducers({
    counter
})
let store = applyMiddleware(sagaMiddleware)(createStore)(combinereducer)
function *rootsaga(){
    console.log('yehuozhili');
}
sagaMiddleware.run(rootsaga)
ReactDOM.render(
    <Provider store = {store}>
    <Counter></Counter>
    </Provider>
,document.getElementById('root'))
  • Click the button to add one normally.
  • Next, asynchronous plus one is implemented. Normally, only one object can be returned in an action. If thunk middleware is used, functions can be returned to realize asynchrony. But saga still returns the action to the object. Instead of passing the distributed action to the reducer, it is intercepted by the middleware and executed in the middleware. There will be some effect s in middleware, where put can send action to reducer.
  • The logic here is as follows: in saga middleware, listen to the asynchronous plus one action, and execute workersaga after listening, that is, a user-defined generator function, which is used to replace the response to action in the reducer that needs to be rewritten before. In this function, delay is used for delay, which is equivalent to some asynchronous operations, and then the action of "put plus one" is sent to the reducer to realize asynchronous plus one.

actions.js

export default{
    add(){
        return {type:'ADD'}
    },
    delayAdd(){
        return {type:'DELAYADD'}
    }
}

index.js

import React  from 'react';
import ReactDOM from 'react-dom'
import { createStore,applyMiddleware,combineReducers } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Counter from './counter'
import {Provider} from 'react-redux'
import {takeEvery,delay,put,all} from 'redux-saga/effects'

let sagaMiddleware=createSagaMiddleware()
function counter(state = {number:0}, action) {
    switch (action.type) {
      case 'ADD':
        return {...state,number:state.number+1}
      case 'MINUS':
        return {...state,number:state.number-1}
      default:
        return state
    }
  }
let combinereducer =combineReducers({
    counter
})
let store = applyMiddleware(sagaMiddleware)(createStore)(combinereducer)
function * workerADD(){//workersaga is a bit like the logic of reducer. In fact, it is the logic after being intercepted by middleware.
    yield delay(1000)
    yield put({type:'ADD'})
}
function * watcherDelayAdd(){//watchersaga is used to monitor action s
    yield takeEvery('DELAYADD',workerADD)//Monitor action, monitor to execute worker
}
function *rootsaga(){//Entry saga
    yield all([watcherDelayAdd()])//saga in all will be executed
}
sagaMiddleware.run(rootsaga)
ReactDOM.render(
    <Provider store = {store}>
    <Counter></Counter>
    </Provider>
,document.getElementById('root'))

counter.js

import React from 'react'
import {connect}from 'react-redux'
import actions from './actions'
function Counter(props) {
    return (
        <div>
        <div>{props.number}</div>
            <button onClick={()=>props.add()}>+</button>
            <button onClick={()=>props.delayAdd()}>asynchronous+</button>
        </div>
    )
}
export default connect(state=>state.counter, actions)(Counter)
  • In the actual project, each module has its own watcher and worker. After exporting, it is uniformly written in the total rootsaga all.
  • There is also a pass parameter, workersaga will receive the blocked action, so as long as the parameter is carried by the action, it can be passed to the worker.
function * workerADD(action){
    yield delay(1000)
    yield put({type:'ADD'})
}
  • Usually in the generator, yield will block the execution. This listening is actually done when run ning. You can try to change the watchersaga of the monitor, let it stop for 5 seconds and then monitor, and you will find that it did stop for 5 seconds when loading the page, and it is invalid to click asynchronous plus one at this time. After the following sdsa is printed on the console, click asynchronously add for a while to take effect.
function * watcherDelayAdd(){
    console.log('xxzc');
    yield delay(5000)
     yield   takeEvery('DELAYADD',workerADD)//Monitor action, monitor to execute worker
    console.log('sdsa');
}
  • This means that saga will do all the logic well at the beginning of the monitoring of the watcher, and the actual asynchronous point plus will not go to the watcher, but to the worker directly.
  • Every effect in saga returns an object. These objects will be judged and operated in run.
  • The operation of adding three times asynchronously is implemented as follows. In fact, you only need to change the listening function to the following:
function * watcherDelayAdd(){
    yield   take('DELAYADD')//Monitor action, monitor to execute worker 
    yield workerADD()
    yield   take('DELAYADD')//Monitor action, monitor to execute worker 
    yield workerADD()
    yield   take('DELAYADD')//Monitor action, monitor to execute worker 
    yield workerADD()
}
  • In the actual work, there will be login and logout. Let's change it a little to simulate login and logout:
function mylogin (user){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            let token = 11
            resolve(token)
        }, 1000);
    })
}
function * workerADD(action){
    let token = yield call(mylogin,action.payload)
    return token 
}
function * watcherDelayAdd(){
    while(true){
        let action =yield take('LOGIN')
        let token = yield workerADD(action)
        if(token){
            yield put({type:'ADD'})//Login succeeded, plus one
            yield take('MINUS')//Login status exit, minus one
        }
    }
  • Write a while true in this watcher to find that you can log in every time you exit. If you don't add it, you can't log in after you log out. You can only log in once.

  • Call and cancel tasks asynchronously:

function mylogin (user){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            let token = 11
            resolve(token)
        }, 2000);
    })

}
function * workerADD(action){
    let token = yield call(mylogin,action.payload)
    if(token){
        yield put({type:'ADD'})//Login succeeded, plus one
        yield take('MINUS')//Login status exit, minus one
    }
}
function * watcherDelayAdd(){
    while(true){
        let action =yield take('LOGIN')
        let task = yield fork(workerADD,action)
        action = yield take(['LOGIN','MINUS'])//
        if(action.type==='MINUS'){//When asynchronous occurs, one of the action s is monitored,
            yield cancel(task)//You can cancel this asynchronous task by processing it
        }
    }
}
  • The non blocking effect of fork is needed here. Since it is asynchronous, there is no return value, only task will be returned. You can cancel the task through task.
  • In actual projects, there are many situations where tasks need to be cancelled. For example, when submitting a form, data will be pulled. During the waiting period, users don't want to wait. They need to modify and submit again, so they can do so.
  • In addition, the failed action s in the task can also be put into the array, and then follow the subsequent logic to form a closed loop. Before and after the request, you can set a flag, which can be used freely.
  • Use race to add a number all the time and stop after listening to a specific action:
function * workerADD(){
    while(true){
        yield delay(1000)
        yield put({type:'ADD'})
    }
}
function * watcherDelayAdd(){
    yield race( [ call(workerADD),take('MINUS')])
}
  • This race can put arrays or objects, which will make all the things in it execute. In the worker, because of the dead loop, one is added all the time. The other take is to monitor the action of minus. Once this action is triggered, it will be the winner, and the other will be cancelled because the dead loop cannot win.
140 original articles published, 7 praised, 20000 visitors+
Private letter follow

Tags: React Programming

Posted on Tue, 14 Jan 2020 18:10:36 -0800 by unknown101