This article will explain the basic usage of REdux-saga with examples. You need to have some knowledge of ES6_Generator/Redux/ React-redux /React.

1. Redux-saga Overview and Project initialization:

  • Description: Redux-Saga is redux’s middleware, responsible for handling side effects from action distribution to update store.

  • Project initialization:

    • Create a React app using the create-react-app method provided by React. Create a React app using the create-react-app method provided by React.

    • 2. After creating our react app, we need to add our own installation packages, including redux, react-Redux, and React-Saga. See the package.json screenshot below. For example, to install redux, if you have the NPM package management tool, you can directly NPM I redux.

    • 3, now, we have built a simple application, the react installed package of what we need at the same time, readers can according to the sample code in the article after pasted into your own project running, each of which will be the file path annotations file at the top, if don’t see the path annotations, here also posted my own project path screenshots for the reader, Readers create corresponding files based on my path screenshots,

2. Start using Redux-Saga

We’ll use create-react-app to create our own React app, which will perform the +1 behavior by pressing a button.

  • Here is the react entry file. We used Redux, React-Redux for state management, and redux-Saga middleware.

        // Current path: SRC /index.js
    
        // Third-party modules are introduced
        import React from 'react'  
        import ReactDom from 'react-dom'
        import { Provider } from 'react-redux'
        import { createStore, applyMiddleware } from 'redux'
        import createSagaMiddleware from 'redux-saga'
    
        // Custom modules are introduced
        // 1. Reducer import in redux
        import rootReducer from './LearningSaga/reducer'
        // 2, react Counter component introduced
        import Counter from './LearningSaga/component'
        // 3, redux-saga middleware saga file introduction
        import rootSaga from './LearningSaga/saga'
    
        // create a redux-Saga middleware
        const sagaMiddleware = createSagaMiddleware()
        // 5, add redux-Saga middleware to redux
        const store = createStore(rootReducer, {}, applyMiddleware(sagaMiddleware))
        Sagamiddleware. run(rootSaga) can only be performed after applyMiddleware(sagaMiddleware)
        sagaMiddleware.run(rootSaga)
    
        // mount the React component
        ReactDom.render(<Provider store={store}><Counter /></Provider>.document.getElementById('root'))
    
    Copy the code
  • Note 1 is the reducer we need to use. The following is the content of the Reducer file. It is very simple, we created counterReducer, and used the combineReducers provided by Redux to process and return the reducer. This is a simple reducer that incrementalize state.counter.

    / / the current path: SRC/LearningSaga/reducer/index. Js
    
    import { combineReducers } from 'redux'
    
    function counterReducer(state = 1, action = {}) {
        switch (action.type) {
            case "increment": return state + 1;
            default: return state
        }
    }
    
    const rootReducer = combineReducers({ counter: counterReducer })
    
    export default rootReducer
    Copy the code
  • React-redux is a Counter component that we have mounted on react. This is a very simple component that uses react-redux to handle Counter. This lets you get the state in redux and the dispatch method in this.props.

    / / the current path: SRC/LearningSaga/component/index. Js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    class Counter extends React.Component {
        // assign an action of type increment_saga
        add = () = > this.props.dispatch({ type: 'increment_saga' })
        render() {
            return (
                <div>
                    <span>{this.props.counter}</span>
                    <button onClick={this.add}>add1-sync</button>
                </div>)}}const mapStateToProps = state= > ({ counter: state.counter })
    export default connect(mapStateToProps)(Counter)
    Copy the code
  • Note 4567 completes the creation of REdux, the use of the Redux-Saga middleware in Redux, and the mount of the Counter component. Of course, the Redux-Saga middleware may handle this slightly differently than other middleware.

  • Before we get to note 3, let’s review the use of the Counter component. First, the Counter component renders the Counter data in the Store (Redux) and an increment button add1-sync, which will issue an action of type increment_saga. What we expect is that when this action is sent, the counter value in the store will increment by 1, but if that’s all we need to do, we can just send an action of type increment, This is because we have implemented the action processing of type INCREMENT in the Reducer of comment 1. We need to use the Redux-Saga middleware to do this for us. We can think about what the Redux-Saga middleware needs to do to accomplish this increment behavior. First, intercept the action, then do some processing, and finally update the counter data in the store to achieve +1. Yes, this is the flow of the Redux-Saga middleware, so looking back, here we send an action of type increment_saga, which the Redux-Saga middleware gets because this behavior is purely +1, The Redux-Saga middleware then calls Dispatch ({type:’increment’}) to increment the counter data in the store.

  • Now back to note 3, this is the saga file in Redux-saga. Here is the code for this file. We see that the functions in this file are all generator functions. The side effects of dispatch are handled by these generator functions, which we call Saga (although we have not yet touched upon any strongly side effects in our current +1 example). The content after yield in saga is called Effects(the task unit of Redux-Saga), in which we can start other sagas and handle side Effects.

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, takeEvery } from 'redux-saga/effects'
    
    function* increment() {
        // Dispatch ({type: 'increment'})
        yield put({ type: 'increment'})}function* watchIncrement() {
        // Monitor increment_saga action to increment
        yield takeEvery('increment_saga', increment) 
    }
    
    function* rootSaga() {
        / / start watchIncrement
        yield all([watchIncrement()])
    }
    export default rootSaga
    Copy the code
    • The rootSaga function yields all([watchIncrement()]), where all is a method provided by Redux-saga to start one or more Effects. Here we just started a watchIncrement saga.

    • The watchIncrement function contains only one line, yield takeEvery(‘increment_saga’, increment), where takeEvery is a method provided by Redux-saga. The method passes in two parameters (type,saga), where type corresponds to the type in the action, and saga is the saga that needs to be started when the Redux-Saga middleware matches the action of the corresponding type. So this line of code simply listens for an action of type increment_saga and starts the corresponding saga to process that action. Now suppose watchIncrement listens for increment_saga and starts increment saga. So let’s go into increment and see what happens.

    • Increment add ({type:); ‘increment’}), the put in this line of code is also a method provided by Redux-Saga, whose parameter is an action, which is used to generate an Effect of the dispatch behavior, and the action is the parameter action in put. We can understand that this action is equivalent to dispatch({type: ‘increment’}). So an action of type INCREMENT will be sent to update the state in the store.

    • Now that the saga file is complete, let’s retrace the process from the moment the Add1-sync button is clicked on the Counter component to the moment the Counter data in the store is incrementing by 1.

      • RootSaga starts the saga with yield all([watchIncrement()]) and waits for it to complete

      • Increment_saga increment_saga increment_saga increment_saga increment_saga increment_saga

      • 3, now click the add1-sync button in the Counter component and send an action of type INCREment_saga.

      • WatchIncrement The saga listens for this action (type:’increment_saga’) and increments the action.

      • 5. Yield PUT ({type: ‘increment’}) to increment an action whose type is INCREMENT

      • 6. Reducer received the action of type increment, performed the corresponding update behavior and completed the counter data +1 in the store.

      • 7, finally update the this.props. Counter data in the Counter component (this automatic update is done by react-redux).

3, Counter data updates with side effects

We will add an add1-Async button on the basis of the original Counter component. Clicking this button will add +1 to the Counter data 1s later

  • Added add1-Async button to the original Counter component

    / / the current path: SRC/LearningSaga/component/index. Js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    class Counter extends React.Component {
        add = () = > this.props.dispatch({ type: 'increment_saga' })
        // The addAsync function will issue an action of type incrementAsync_saga
        addAsync = () = > this.props.dispatch({ type: 'incrementAsync_saga' })
        render() {
            return (
                <div>
                    <span>{this.props.counter}</span>
                    <button onClick={this.add}>add1-sync</button>
                    <button onClick={this.addAsync}>add1-async</button>
                </div>)}}const mapStateToProps = state= > ({ counter: state.counter })
    export default connect(mapStateToProps)(Counter)
    Copy the code
  • We will also add incrementAsync_saga action to the saga file for processing

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, takeEvery, delay } from 'redux-saga/effects'
    
    function* increment() {
        yield put({ type: 'increment' }) // Dispatch ({type: 'increment'})
    }
    function* incrementAsync() {
        / / delay 1 s
        yield delay(1000)
        // Dispatch ({type: 'increment'})
        yield put({ type: 'increment'})}function* watchIncrement() {
        yield takeEvery('increment_saga', increment) // Monitor increment_saga action to increment
    
        // Listen for action of type incrementAsync_saga to start incrementAsync
        yield takeEvery('incrementAsync_saga', incrementAsync)
    }
    function* rootSaga() {
        yield all([watchIncrement()]) / / start watchIncrement
    }
    
    export default rootSaga
    Copy the code
    • We added a new line of code to watchIncrement, yield takeEvery(‘incrementAsync_saga’, incrementAsync). As we learned in the previous section, This line listens for an action of type incrementAsync_saga until incrementAsync is started.

    • IncrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync incrementAsync You can essentially view it as this function.

      // delay is equivalent to delay_ here
      function delay_(timeout) {
          return new Promise(r= > {
              setTimeout(() = >r(), timeout); })}Copy the code
    • Yield PUT ({type: ‘increment’}) = dispatch({type: ‘increment’})

  • When we click the add1-Async button continuously, there will be a delay of 1s, and counter will increment successively if we expect to respond to the last dispatch in multiple clicks ({type: ‘increment_saga’}), we can replace takeEvery with takeLatest, another listener provided by Redux. TakeLatest listens for the dispatch of the last action and automatically cancels tasks that were already started and still running. Yes, this is very similar to our anti-shake. The following is the saga file code after replacement. The other file codes remain unchanged.

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, takeEvery, delay,takeLatest } from 'redux-saga/effects'
    
    // ... other code
    
    function* watchIncrement() {
        // ... other code
    
        // Replace every with takeLatest, so that if the next action is listened on and the previous saga task incrementAsync is still running, the previous saga task will be cancelled and the current saga task will be executed
        yield takeLatest('incrementAsync_saga', incrementAsync)
    }
    
    // ... other code
    Copy the code

4. Now, let’s review the use of the Redux-Saga method we have learned and introduce some nouns.

  • takeLatest(pattern,saga,… Args): monitor the distribution of action of pattern type. When the action of pattern type is monitored, the second parameter saga will be executed. If the saga that was started last time is still running, takeLatest will cancel the running of the last saga

    • Pattern: takeLatest listens for distribution of actions of type PATTERN

    • Saga: Listen to the corresponding action and start the corresponding saga.

    • Args: Arguments passed to the saga function. If takeLatest does not pass args, the saga function takes only one argument, which is an action of type Pattern. If takeLatest passes in other args arguments, the arguments to the saga function will look like this (… The args, action).

      • The first takeLatest parameter is *. Instead of matching the type of a specific action, it matches all actions. Now when we send an action of type increment_saga, Below the code is the parameter output for printSagaParams.

        / / the current path: / SRC/LearningSaga saga/index js
        
        import { all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects'
        // 3, printSagaParams will print out the parameters passed in
        function* printSagaParams(. params) {
            console.log('params:', params);
        }
        // 2, listen for all actions, execute printSagaParams with no other arguments
        function* watchPrintSagaParams() {
            yield takeLatest(The '*', printSagaParams)
        }
        // start watchPrintSagaParams
        function* rootSaga() {
            yield all([watchPrintSagaParams()]) 
        }
        
        export default rootSaga
        Copy the code

      • Now we will modify the watchPrintSagaParams parameter slightly, add two parameters ‘hello’, ‘saga’, now send an action of type increment_saga, below the printSagaParams parameter output result.

        / / the current path: / SRC/LearningSaga saga/index js
        
        import { all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects'
        
        function* printSagaParams(. Params) {
            console.log('Params:', Params);
        }
        function* watchPrintSagaParams() {
            // Add two parameters: 'hello', 'saga'
            yield takeLatest(The '*', printSagaParams, 'hello'.'saga')}function* rootSaga() {
            yield all([watchPrintSagaParams()]) / / start watchPrintSagaParams
        }
        
        export default rootSaga
        Copy the code

  • takeEvery(pattern,saga,… args): Listen on the distribution of action of pattern type. When the action of pattern type is listened on, the second argument saga will be executed, and args will be passed to saga as an argument. The only difference with takeLatest is that it does not cancel saga tasks that previously listened on actions of type Pattern.

  • Delay (timeout,[val]): generates a blocking Effect (Effect=> task unit), blocks timeout milliseconds, and returns val (val is not mandatory). Yield delay(1000,’Love U’) will block the current code execution for 1000ms and return ‘Love U’.

  • Put (Action): Creates an Effect that commands the middleware to initiate this action to the store. This action is non-blocking. Equivalent to dispatch(Action).

  • All ([…effects]): Command middleware runs multiple effects in parallel and waits for them to complete, returning all effCTS results. Behavior equivalent to promise.all. This method is used in the normal root Saga file, which starts all saga tasks that the project needs to run at the same time.

    • With the following code, we start a task with delay(3000, ‘Love U’)

      / / the current path: / SRC/LearningSaga saga/index js
      
      import { all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects'
      
      function* rootSaga() {
          // Start delay(3000, 'Love U'), delayRes will not receive the returned 'Love U' value until 3s
          const delayRes = yield all([delay(3000.'Love U')])
          ['Love U']
          console.log('delayRes', delayRes); 
      }
      
      export default rootSaga
      Copy the code
    • In the following code, we start both a Delay task and a listener task, watchPrintSagaParams

      / / the current path: / SRC/LearningSaga saga/index js
      
      import { all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects'
      
      function* printSagaParams(. Params) {
          console.log('Params:', Params);
      }
      function* watchPrintSagaParams() {
          yield takeLatest(The '*', printSagaParams, 'hello'.'saga')}function* rootSaga() {
          // Start the delay task and the watchPrintSagaParams task concurrently
          // Delay task will complete after 3s, output 'Love U'
          // The watchPrintSagaParams takeLatest is always listening, so the watchPrintSagaParams will not complete
          // So we can't wait for the output of res here
          const res = yield all([delay(1000.'Love U'), watchPrintSagaParams()])
         // Can not wait for output result
         console.log('res', res);
      }
      
      export default rootSaga
      Copy the code
    • When Effects is run concurrently with ALL, the middleware pauses the Generator (rootSaga above) until any of the following occurs and the Generator continues.

      • All effects complete successfully: return an array of all effects results and resume Generator.

      • Any Effect is rejected until all effects are complete: The Generator throws a REJECT error.

  • All (effects): has the same function as all([…effects]) except that it passes in an object instead of an array.

    function* rootSaga() {
        const res = yield all([delay(1000.'Love U')])
        ['Love U']
        console.log(res);
    }
    function* rootSaga() {
        const res = yield all({ delayRes: delay(1000.'Love U')}){delayRes:'Love U'}
        console.log(res);
    }
    Copy the code
  • Saga: These generator functions we see are saga

  • Put ({type: ‘increment’}) creates an Effect object using the put method provided by Redux-Saga. The output of put({type: ‘increment’}) is as follows: ‘increment’}) creates an object containing some information directly. Yield PUT ({type: ‘increment’}) yields an Effect and tells the middleware to initiate a increment action. Effects are essentially simple objects that contain instructions for the middleware to execute. When the middleware gets a saga yield Effect, it suspends the current saga until the Effect is complete, and then the saga resumes execution.

    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, takeEvery, delay, takeLatest } from 'redux-saga/effects'
    
    function* rootSaga() {
        // We use yield put({type: 'increment'}).
        // Add ({type: 'increment'});
        console.log(`put({ type: 'increment' }):`, put({ type: 'increment' }));
    }
    
    export default rootSaga
    Copy the code

  • Blocking calls/non-blocking calls: Blocking calls mean that saga waits for the result to return after yield Effect before continuing to execute the next instruction in saga. Non-blocking calls mean that saga resumes execution immediately after yield Effect. Here is an example of blocking versus non-blocking calls.

    • Blocking call: In the code below, call is a method that produces a blocking call, the output of which is shown in the figure below code.

      / / the current path: / SRC/LearningSaga saga/index js
      
      import { delay, call } from 'redux-saga/effects'
      
      function* say() {
          console.log(`The ${new Date().getSeconds()}:Hi ~`);
          yield delay(1000)
          console.log(`The ${new Date().getSeconds()}:I love u ~`);
          yield delay(1000)}function* rootSaga() {
          // The call method is a blocking method
          Yield a call Effect that tells the middleware to say
          // Since call is a blocking method, console.log will wait for say to complete
          yield call(say)
          console.log(`The ${new Date().getSeconds()}:I love u too ! `);
      }
      export default rootSaga
      
      Copy the code

    • Non-blocking calls: fork is a method of non-blocking calls, the output of which is shown below in code.

      / / the current path: / SRC/LearningSaga saga/index js
      
      import { delay, fork } from 'redux-saga/effects'
      
      function* say() {
          console.log(`The ${new Date().getSeconds()}:Hi ~`);
          yield delay(1000)
          console.log(`The ${new Date().getSeconds()}:I love u ~`);
          yield delay(1000)}function* rootSaga() {
          // the fork method is a non-method. We haven't talked about it yet
          Yield a fork Effect that tells the middleware to execute say
          // Since fork is a non-blocking call, we execute say first. If we encounter a block in SAY, we will yield fork(say) and execute console.log
          // console.log Is executed in say
          yield fork(say)
          console.log(`The ${new Date().getSeconds()}:I love u too ! `);
      }
      export default rootSaga
      Copy the code

5, use Redux-Saga to write a page with login and logout functions

This time we will use Redux-Saga to complete a simple login and logout page

  • Here is our page code

    / / the current path: SRC/LearningSaga/component/index. Js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    // A page for login and logout
    class Home extends React.Component {
        // 1, maintain the user name and password for controllable input
        state = { name: 'admin'.password: 'admin' }
        // 2, user name input update state user name
        nameChange = e= > this.setState({ name: e.target.value })
        // 3, enter the password to update the state password
        passwordChange = e= > this.setState({ password: e.target.value })
        // 4, log out, an action of type loginOut will be issued
        loginOut = () = > this.props.dispatch({ type: 'loginOut' })
        // 5, login, an action of type login will be issued, along with the parameters username and password
        login = () = > this.props.dispatch({
            type: 'login'.account: {
                name: this.state.name,
                password: this.state.password
            }
        })
    
        render() {
            return (
                <div>{/* User name input */}<div>User name:<input onChange={this.nameChange} value={this.state.name} /></div>{/* Controllable password input */}<div>Password:<input onChange={this.passwordChange} value={this.state.password} /></div>{/* Login button */}<button onClick={this.login}>The login</button>{/* Logout button */}<button onClick={this.loginOut}>logout</button>{/* If the login succeeds, redux will update logininfo. success to true, and then it will display XXX user login successfully */} {/* If the login fails/not login, Will not show this text * /} {this. Props. LoginInfo. Success?<div>{this. Props. LoginInfo. Name} the user login successfully</div> : null}
                </div>)}}// 6, use connect to connect the component to Redux's store and add data to this. Props to get the logon information from store
    const mapStateToProps = state= > ({ loginInfo: state.loginInfo })
    export default connect(mapStateToProps)(Home)
    Copy the code
  • Here is the Reducer code

    / / the current path: SRC/LearningSaga/reducer/index. Js
    
    import { combineReducers } from 'redux'
    
    // Save the login information to store
    function setLoginInfoReducer(state = {}, action = {}) {
        switch (action.type) {
            case "loginSuccess": return{... state, ... action.loginInfo };default: return state
        }
    }
    
    const rootReducer = combineReducers({ loginInfo: setLoginInfoReducer })
    
    export default rootReducer
    Copy the code
  • The following is the code for invoking the login interface. In order to facilitate the direct use of Promise simulation, if the user name and password are both admin, the login will be judged as successful; otherwise, the login will be judged as failed.

    / / the current path: SRC/LearningSaga/service/request. Js
    
    export function loginService({ name, password }) {
        return new Promise((resolve, reject) = > {
            setTimeout(() = > {
                if (name === 'admin' && password === 'admin') {
                    resolve({ success: true, name, password })
                } else {
                    reject({ success: false.msg: 'Account is not valid'})}},1000)})}Copy the code
  • Finally, our Saga file code

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, take, takeLatest, call } from 'redux-saga/effects'
    import { loginService } from '.. /service/request'
    
    // Login function saga
    function* login(action) {
        try {
            // Call our login interface to get information about successful login failure
            const loginInfo = yield call(loginService, action.account)
            // If login succeeds, update loginInfo in store
            yield put({ type: 'loginSuccess', loginInfo })
        } catch (error) {
            // Login failure Message is displayed indicating login failure
            alert(error.msg)
        }
    }
    // Log out from saga and update store loginInfo to {success: false}
    function* loginOut() {
        yield put({ type: 'loginSuccess'.loginInfo: { success: false.name: ' '.password: ' '}})}// watchLogin listens for the login action (type:login) and logout action (type:loginOut) and executes the corresponding saga
    function* watchLogin() {
        yield takeLatest('login', login)
        yield takeLatest('loginOut', loginOut)
    }
    // rootSaga is responsible for launching watchLogin
    function* rootSaga() {
        yield all([watchLogin()])
    }
    export default rootSaga
    
    Copy the code
  • This is what our page ended up looking like

  • Describes the workflow of the login and logout application in detail

    • 1. When we enter the user name and password, click Login, an action of type login will be issued, and the user name and password will be passed in

          // Click the login button to dispatch an action of type login
          login = () = > this.props.dispatch({
              type: 'login'.account: {
                  name: this.state.name,
                  password: this.state.password
              }
          })
      Copy the code
    • 2. WatchLogin in the saga file listens to the login action and starts the corresponding login saga to process the login

      function* watchLogin() {
          // Start login by listening to the login action
          yield takeLatest('login', login)
          yield takeLatest('loginOut', loginOut)
      }
      Copy the code
    • 3. Login executes, first invokes the loginService interface method, and passes in the user name and password carried in the Action to The loginService to determine whether the login succeeds or fails.

      function* login(action) {
          try {
              // Invoke loginService to obtain information about the login failure
              const loginInfo = yield call(loginService, action.account)
              yield put({ type: 'loginSuccess', loginInfo })
          } catch (error) {
              alert(error.msg)
          }
      }
      Copy the code
      • Here we use a new method provided by Redux-Saga: call(fn,… Args), which creates an Effect that commands the middleware to call FN with the args argument, which can be a Generator function or a normal function that returns Pormise or any other value. So here const loginInfo = yield call(loginService, action.account)Call loginService, pass in the action. Account argument, get the result (get the Promise value if you return a Promise) and pass it to loginInfo.
    • 4, loginService executes, where loginService simulates the process of calling the interface, and if the user name and password are passed in correctly, the

      export function loginService({ name, password }) {
          return new Promise((resolve, reject) = > {
              // The simulated login interface returns success or failure after 1s
              setTimeout(() = > {
                  LoginInfo {success: true, name, password} is returned if the user name and password are admin.
                  // otherwise return loginInfo {success: false, MSG: 'account invalid'}
                  if (name === 'admin' && password === 'admin') {
                      resolve({ success: true, name, password })
                  } else {
                      reject({ success: false.msg: 'Account is not valid'})}},1000)})}Copy the code
    • 5, return to our login saga. If loginService fails to login, a failed Promise will be returned and the failed error will be caught.

      function* login(action) {
         try {
             Failed to invoke loginService
             const loginInfo = yield call(loginService, action.account)
             yield put({ type: 'loginSuccess', loginInfo })
         } catch (error) {
             // Fail to enter catch and a failure message is displayed
             alert(error.msg)
         }
      }
      Copy the code
    • 6. If loginService succeeds, yield PUT ({type: ‘loginSuccess’, loginInfo}), that is, an action of type loginSuccess is sent to the reducer to update loginInfo as successful data in the store

      function* login(action) {
         try {
             // Invoke loginService successfully
             const loginInfo = yield call(loginService, action.account)
             // Issue an action of type loginSuccess to update loginInfo in store
             yield put({ type: 'loginSuccess', loginInfo })
         } catch (error) {
             alert(error.msg)
         }
      }
      Copy the code
    • 7, the login is successful, the loginInfo data in the store will be updated by reducer as {success: true, name:’admin’, password:’admin’}

      LoginInfo: {success: true, name:'admin', password:'admin'}
      function setLoginInfoReducer(state = {}, action = {}) {
          switch (action.type) {
              case "loginSuccess": return{... state, ... action.loginInfo };default: return state
          }
      }
      Copy the code
    • LoginInfo in store is updated to {success: True, name: admin, password: ‘admin’}, so this. Props. LoginInfo. Success is true, at this time the user login page display XXX success.

      render() {
              return (
                  <div>
                      <div>User name:<input onChange={this.nameChange} value={this.state.name} /></div>
                      <div>Password:<input onChange={this.passwordChange} value={this.state.password} /></div>
                      <button onClick={this.login}>The login</button>
                      <button onClick={this.loginOut}>logout</button>{/ * this props. LoginInfo. Success is true, showing the admin user login successfully. */} {this.props.loginInfo.success ?<div>{this. Props. LoginInfo. Name} the user login successfully</div> : null}
                  </div>)}}Copy the code
    • 9. Effect of successful login page

    • 10. Finally, we test the logout button and first issue an action of type loginOut

      loginOut = () = > this.props.dispatch({ type: 'loginOut' })
      Copy the code
    • WatchLogin listens to this action and starts to log out saga: loginOut

      function* watchLogin() {
          yield takeLatest('login', login)
          / / loginOut startup
          yield takeLatest('loginOut', loginOut)
      }
      Copy the code
    • 12, loginOut starts and sends a data with success value false to update loginInfo in store

      function* loginOut() {
          yield put({ type: 'loginSuccess'.loginInfo: { success: false.name: ' '.password: ' '}})}Copy the code
    • 15, log out, the loginInfo data in the store will be updated by reducer as {success: false, name: “, password: “}

      LoginInfo {success: true, name: ", password: "}
      function setLoginInfoReducer(state = {}, action = {}) {
          switch (action.type) {
              case "loginSuccess": return{... state, ... action.loginInfo };default: return state
          }
      }
      Copy the code
    • LoginInfo in store is updated to {success: False, the name: “‘ the password:” ‘}, so this. Props. LoginInfo. Successfalse, user login page does not display shows XXX success at this time.

      render() {
              return (
                  <div>
                      <div>User name:<input onChange={this.nameChange} value={this.state.name} /></div>
                      <div>Password:<input onChange={this.passwordChange} value={this.state.password} /></div>
                      <button onClick={this.login}>The login</button>
                      <button onClick={this.loginOut}>logout</button>{/ * this props. LoginInfo. Success to false * /} {this. Props. LoginInfo. Success?<div>{this. Props. LoginInfo. Name} the user login successfully</div> : null}
                  </div>)}}Copy the code
    • 15. Page effect after logout

Now we have made some minor changes to the original login and logout page. First, we expect the logout button to be available only in the login state, and second, the logout button will not be available after being used once. As shown in the following GIF, the logout button only works when you log in for the first time, but does not work when you click the logout button when you log in again.

  • First of all, let’s make our logout button available only in the login state. The login and logout page is changed as follows. Simply add disabled={! This. Props. LoginInfo. Success}.

    / / the current path: SRC/LearningSaga/component/index. Js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    // A page for login and logout
    class Home extends React.Component {
        state = { name: 'admin'.password: 'admin' }
        nameChange = e= > this.setState({ name: e.target.value })
        passwordChange = e= > this.setState({ password: e.target.value })
        loginOut = () = > this.props.dispatch({ type: 'loginOut' })
        login = () = > this.props.dispatch({type: 'login'.account: { name: this.state.name, password: this.state.password }})
    
        render() {
            return (
                <div>
                    <div>User name:<input onChange={this.nameChange} value={this.state.name} /></div>
                    <div>Password:<input onChange={this.passwordChange} value={this.state.password} /></div>
                    <button onClick={this.login}>The login</button>{/ * 1, logout button, disabled only in the enclosing props. LoginInfo. Success is true to false, namely the logout button in the login state only available * /}<button disabled={! this.props.loginInfo.success} onClick={this.loginOut}>logout</button>
                    {this.props.loginInfo.success ? <div>{this. Props. LoginInfo. Name} the user login successfully</div> : null}
                </div>)}}const mapStateToProps = state= > ({ loginInfo: state.loginInfo })
    export default connect(mapStateToProps)(Home)
    Copy the code
  • Now let’s make sure that the logout button can only be used once. The code changes as follows. Only two lines of watchLogin in the saga file are changed, that is, the original yield takeLatest(‘loginOut’, loginOut) is removed. Both yield take(‘loginOut’) and yield Call (loginOut) are added.

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, take, takeLatest, call } from 'redux-saga/effects'
    import { loginService } from '.. /service/request'
    
    function* login(action) {
        try {
            const loginInfo = yield call(loginService, action.account)
            yield put({ type: 'loginSuccess', loginInfo })
        } catch (error) {
            alert(error.msg)
        }
    }
    
    function* loginOut() {
        yield put({ type: 'loginSuccess'.loginInfo: { success: false.name: ' '.password: ' '}})}// Monitor login and logout of saga
    function* watchLogin() {
        yield takeLatest('login', login)
        // 0, remove the previous takeLatest listening for action type loginOut
        // yield takeLatest('loginOut', loginOut)
        // 1, use take to wait for an action of type loginOut to arrive, take will block the current Generator
        yield take('loginOut')
        // 2, take listens for the action of type loginOut and performs the yield call(loginOut)
        yield call(loginOut)
    }
    
    function* rootSaga() {
        yield all([watchLogin()])
    }
    export default rootSaga
    Copy the code
    • 1. Observe the watchLogin method and yield takeLatest(‘login’, login). This method creates a takeLatest Effect that listens for the login action to execute the corresponding saga. We talked about this before. The essence of this is to create a high-level API using take and fork. To put it simply, it is to create a task that is always executing. The function of this task is to listen on the action of type login to execute the corresponding saga. TakeLatest is non-blocking, that is, the middleware receives the Effect created by takeLatest and creates a task that will always execute while continuing to execute the code after yield takeLatest(‘login’, login).

    • 2. Since takeLatest is non-blocking, the middleware creates the takeLatest task and then executes the next line of code, yield take(‘loginOut’). Here we use a new redux-saga providing method take. Take takes the type of action that matches the type of action. Take creates an Effect in which the command middleware waits for the specified action (i.e., an action of type loginOut). The current Generator is paused, that is, the take is blocked, and the subsequent yield call(loginOut) will not be executed until the action arrives.

    • 3. When the yield Take (‘loginOut’) waits for an action of type loginOut, the Generator continues to execute the following code, yield Call (loginOut), which creates an Effect. The command middleware executes the loginOut method for subsequent logout operations, and since the call method is blocking, the current Generator waits for the loginOut to complete. When loginOut is complete, the Generator will complete.

    • 4. After the Generator function (watchLogin) completes and exits, only the listener task created by yield takeLatest(‘login’, login) is still running in the background. Therefore, the middleware will ignore the subsequent logout actions sent from the next logout action without any task to keep monitoring them. Therefore, there will be no corresponding saga processing for subsequent logout operations, so as to realize the previously expected logout button can only be used once.

In addition to the above code, we add a small function, that is, in the current page console output job information, the job information call interface returns.

  • Let’s first implement our job message interface. Again, for the sake of simple and direct use of Promise simulation, the job message will return after 5s. The job message interface function is the inviteInfoService method below.

/ / the current path: SRC/LearningSaga/service/request. Js
// Login interface
export function loginService({ name, password }) {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            if (name === 'admin' && password === 'admin') {
                resolve({ success: true, name, password })
            } else {
                reject({ success: false.msg: 'Account is not valid'})}},1000)})}// The simulated interface obtains the recruitment information and returns the recruitment information after 5 seconds
export function inviteInfoService() {
    return new Promise((resolve, reject) = > {
        setTimeout(() = > {
            resolve("We need 100 excellent front-end development engineers, if interested, please contact wechat: Tsuki_")},5000)})}Copy the code
  • Secondly, there are only three changes in our Saga file, namely comments 1, 2 and 3.

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { all, put, take, takeLatest, call } from 'redux-saga/effects'
    // 1, inviteInfoService is introduced
    import { loginService, inviteInfoService } from '.. /service/request'
    
    // Login function saga
    function* login(action) {
        try {
            const loginInfo = yield call(loginService, action.account)
            yield put({ type: 'loginSuccess', loginInfo })
        } catch (error) {
            alert(error.msg)
        }
    }
    // Log out from saga and update store loginInfo to {success: false}
    function* loginOut() {
        yield put({ type: 'loginSuccess'.loginInfo: { success: false.name: ' '.password: ' '}})}// 2, get the job information and output it to the console saga
    function* getInviteInfo() {
        // 2.1, get recruitment information
        const inviteInfo = yield call(inviteInfoService)
        // 2.2, output to console
        console.log('Hiring:', inviteInfo);
    }
    // Monitor login and logout of saga
    function* watchLogin() {
        // 3, execute getInviteInfo when the page enters to get the recruitment information and output it to the console.
        yield call(getInviteInfo)
    
        yield takeLatest('login', login)
        yield take('loginOut')
        yield call(loginOut)
    }
    // rootSaga is responsible for launching watchLogin
    function* rootSaga() {
        yield all([watchLogin()])
    }
    export default rootSaga
    
    Copy the code
    • Note 1: Introduce the recruitment information interface function

    • Note 2: Create a saga (getInviteInfo) that gets the job information and prints it to the console

    • Note 3: Execute getInviteInfo to get the job information and print it to the console

  • Here is a GIF of the page with the feature added

  • We found that clicking on login didn’t respond until the job Posting appeared on the console, and clicking on login didn’t respond until after the job Posting appeared, so we went back to watchLogin to see what happened.

     // ... other code
     // Monitor login and logout of saga
    function* watchLogin() {
        // 3, execute getInviteInfo when the page enters to get the recruitment information and output it to the console.
        yield call(getInviteInfo)
    
        yield takeLatest('login', login)
        yield take('loginOut')
        yield call(loginOut)
    }
    // rootSaga is responsible for launching watchLogin
    function* rootSaga() {
        yield all([watchLogin()])
    }
    export default rootSaga
    Copy the code
    • First, the page loads and rootSaga starts.

    • 2. RootSaga continues to start watchLogin internally

    • 3, watchLogin starts to execute its internal code. The first code that gets the job information output to the console is yield Call (getInviteInfo). This code will not execute until getInviteInfo completes the call. The code is as follows:

      function* watchLogin() {
          // 1, execute yield Call (getInviteInfo), and call is blocking call
          yield call(getInviteInfo)
          // 2, so the following code will not be executed until the yield call(getInviteInfo) completes
          yield takeLatest('login', login)
          yield take('loginOut')
          yield call(loginOut)
      }
      Copy the code

      So when we run the Yield Call (getInviteInfo), the login listener is not started, so there is no response to the login at this point.

  • Now that we know that the login button is not responding because the Yield Call (getInviteInfo) is blocked, is there a good way to solve this problem? Of course, use fork instead of call. In contrast to call, the fork method creates a non-blocking task. So we’ll change call to fork.

    function* watchLogin() {
     
        // yield call(getInviteInfo)
        // Change call to fork to prevent blocking later code execution
        yield fork(getInviteInfo)
    
        yield takeLatest('login', login)
        yield take('loginOut')
        yield call(loginOut)
    }
    Copy the code
  • Here is what happens when the Yield Call (getInviteInfo) switches to yield fork(getInviteInfo). Obviously fork is useful.

  • Now let’s review some of the new methods used in this section

    • call(fn,… The args) : Call creates an Effect that commands the middleware to call FN with the parameter args, where FN can be a Generator function or a normal function that returns a Promise or any other value. We illustrate the behavior of call with the following testSaga function with several different FN functions.

      function* testSaga(){
        const data = yield call(fn,'lov_500')
        console.log(data)  
      }
      Copy the code
      • 1. When fn is a Generator function, the code is as follows:

         function* fn(msg) {
             yield delay(2000)
             return msg
         }
        Copy the code
        • First yield call(fn,’lov_500′) middleware calls fn, passing in ‘lov_500’

        • The middleware finds that fn returns an iterator object and continues to suspend testSaga and execute the Generator function

        • After 2s, the Generator returns to lov_500, testSaga resumes, and console.log(data) is executed to output lov_500.

      • When fn is a normal function that returns a Promise, the code is as follows:

        function fn(msg) {
            return new Promise(r= > {
                setTimeout(() = > {
                    r('hi ~ ')},2000); })}Copy the code
        • First of all,yield call(fn,'lov_500')The middleware calls fn, passing in parameters'lov_500'
        • The middleware finds that fn returns a Promise instance and continues to suspend testSaga until the Promise instance is resolved/Reject.
        • After 2s, the Promise status changes to success and value is'hi ~ ', testSaga continuesconsole.log(data)The outputhi ~ .
      • 3. When fn is a normal function, the result is neither an iterator object nor a Promise instance, with the following code:

        function fn(msg) {
            console.log(msg);
            return 'May'
        }
        Copy the code
        • First yield call(fn,’lov_500′) middleware executes fn, passing in parameter ‘lov_500’

        • Fn perform the console. The log (MSG); Print LOV_500 and return a string May

        • The middleware finds that the FN result is neither an iterator object nor a Promise, and immediately returns the result to testSaga.

        • TestSaga obtains the string May returned by fn, and continues to execute console.log(data) to output May.

      • To sum up, the Effect created by call commands the middleware to call the function passed in and check its result. If the result is an iterator object or a Promise instance, the middleware will pause the current saga until the iterator object or Promise instance is processed. If the returned result is not an iterator object, the middleware immediately passes the value to the saga of the call so that the saga can resume execution in a synchronous manner.

    • call( [context,fn] , … Args) : equivalent to call(fn,… Args), but fn context is context.

    • call( [context,fnName] , … Args) : fnName is a string that functions like call(context[fnName],… The args).

    • Take (pattern) : Take creates an Effect in which the command middleware waits for an action to be specified and the Generator function on which the current take is located is paused until an action matching the pattern arrives. So this is a method that blocks the call.

      • The middleware (Redux-Saga) also provides a special actionENDIf the END action is initiated, saga blocked by take Effect will be terminated regardless of the pattern. If the terminated saga’s next bifurcated task is still running, the saga will wait for all its subtasks to be terminated before terminating.
    • fork(fn, … Args) : fork creates an Effect where the command middleware calls fn in a non-blocking form and returns a task, similar to a non-blocking call. Fork takes the form of creating a forked task to execute fn (like multithreading), and the saga of fork will not be suspended by middleware while fn returns the result, instead, it will resume execution as soon as FN is called.

6. Use low order take to implement takeEvery and takeLatest

In this section we will use take, fork to implement the takeEvery and takeLatest provided by Redux-Saga.

Implement takeEvery:

Review takeEvery(pattern,saga,… If the args parameter is detected, start saga. If there is no args parameter, the saga parameter is default to the monitored action. If there is args parameter, it is args.

  • Let’s start with our new React component code: when we click the button to get a front-end job Posting and when we click the button to get a back-end job Posting, we will issue an action of type getInfo with javascript and Java language respectively. Take the button to get the front-end recruitment information for example. After clicking the button, the recruitment information will be displayed below the button 1s later: We need 100 front-end excellent javascript development engineers, please contact wechat: Tsuki_ if you are interested

    / / the current path: SRC/LearningSaga/component/index. Js
    
    import React from 'react'
    import { connect } from 'react-redux'
    
    // Click the two buttons of this page to get different job information, and the job information will be displayed at the bottom of the page
    class InviteInfoPage extends React.Component {
        // 1, click the button to get the front-end recruitment information, and an action of type getInfo will be issued with the parameter language:javascript
        // 2, click the button to get the backend job information and an action of type getInfo will be issued with the parameter language: Java
        getInfo = language= > this.props.dispatch({ type: 'getinfo', language })
        render() {
            return (
                <div>
                    <button onClick={this.getInfo.bind(this, 'javascript')} >Access to front-end recruitment information:</button>
                    <button onClick={this.getInfo.bind(this, 'java')} >Get back-end job information:</button>{/* 3, store inviteInfo display */}<div>{this.props.inviteInfo}</div>
                </div>)}}const mapStateToProps = state= > ({ inviteInfo: state.inviteInfo })
    export default connect(mapStateToProps)(InviteInfoPage)
    Copy the code
  • Looking at our new Reducer file, it is also very simple

    / / the current path: SRC/LearningSaga/reducer/index. Js
    
    import { combineReducers } from 'redux'
    
    // When receiving an action of type setInviteInfo, update the action parameter inviteInfo to inviteInfo in the store
    function setInviteInfoReducer(state = ' ', action = {}) {
        switch (action.type) {
            case "setInviteInfo": return action.inviteInfo
            default: return state
        }
    }
    
    const rootReducer = combineReducers({ inviteInfo: setInviteInfoReducer })
    
    export default rootReducer
    Copy the code
  • After 1s, inviteInfoService returns string data. We need 100 front-end excellent ${language} development engineers. If interested, please contact wechat: Tsuki_, which is the job information that will be displayed on our component page.

    // The simulated interface obtains the recruitment information, that is, the interface returns the recruitment information one second later
    export function inviteInfoService(language) {
        return new Promise((resolve, reject) = > {
            setTimeout(() = > resolve('We need 100 front end excellence${language}Development engineer, interested please contact wechat: Tsuki_ '), 1000)})}Copy the code
  • Finally look at the saga file, how to implement a takeEvery yourself

    
    / / the current path: / SRC/LearningSaga saga/index js
    
    import { put, take, call, fork } from 'redux-saga/effects'
    
    import { inviteInfoService } from '.. /service/request'
    
    // 2.1, implement takeEvery, takeEvery takes three arguments:
    // Pattern: type of the listening action
    // saga: listen to the saga function required by the current action
    // args: other arguments to takeEvery
    function* takeEvery(pattern, saga, ... args) {
        function* help() {
            while (true) {
                const action = yield take(pattern)
                yieldfork(saga, ... args.concat(action)) } }yield fork(help)
    }
    // 3, get the recruitment information interface data and update it to store
    function* getInfo(action) {
        // 3.1, getInfo calls the inviteInfoService interface function using call and passes in the action parameter language
        const inviteInfo = yield call(inviteInfoService, action.language)
        // 3.2, get the job information data returned by the interface, use put to send the setInviteInfo action to update the job information inviteInfo to the store
        yield put({ type: 'setInviteInfo', inviteInfo })
    }
    // 2, use takeEvery to listen for action of type getInfo to execute getInfo
    function* watchGetInfo() {
        yield takeEvery('getinfo', getInfo)
    }
    // 1, root saga, start watchGetInfo
    function* rootSaga() {
        yield watchGetInfo()
    }
    
    export default rootSaga
    
    Copy the code
  • Now let’s comb through the whole process of clicking the “Get Job information” button to display the job information using self-made takeSaga.

    • The redux-Saga middleware in the react entry js file runs rootSaga, i.e. Sagamiddleware. run(rootSaga).

      // Current path: SRC /index.js
      // react entry js file
      
      // ... other code ...
      
      // 1, create redux and use redux-Saga middleware
      const sagaMiddleware = createSagaMiddleware()
      const store = createStore(rootReducer, {}, applyMiddleware(sagaMiddleware))
      // 2, redux-saga middleware starts root saga
      sagaMiddleware.run(rootSaga)
      
      // ... other code ...
      
      Copy the code
    • 2.1, back to the saga file, rootSaga starts with the following code, that is, run watchGetInfo

      // Root saga, start watchGetInfo
      function* rootSaga() {
          yield watchGetInfo()
      }
      Copy the code
    • WatchGetInfo: watchGetInfo: watchGetInfo: watchGetInfo: watchGetInfo: watchGetInfo: watchGetInfo

      // Use takeEvery to listen for action of type getInfo to execute getInfo
      function* watchGetInfo() {
          yield takeEvery('getinfo', getInfo)
      }
      Copy the code
    • 2.3. The specific code to realize takeEvery is as follows, where takeEvery accepts three parameters: pattern, saga, args, The three parameters corresponding to the current scenario are action type getInfo, saga function getInfo, and the remaining parameters [](no other parameters are given here, so the remaining parameters are empty).

      function* takeEvery(pattern, saga, ... args) {
          function* help() {
              while (true) {
                  const action = yield take(pattern)
                  yieldfork(saga, ... args.concat(action)) } }yield fork(help)
      }
      Copy the code
    • 2.3.1, takeEvery first declares a generator function help, the body of the function is an infinite loop while (true) {}

      function* help() {
          while (true) {
              const action = yield take(pattern)
              yieldfork(saga, ... args.concat(action)) } }Copy the code
      • Const action = yield take(pattern), where pattern corresponds to the current specific example getInfo, so this line of code is waiting for an action of type getInfo. Pause the current generator function help until it arrives

      • When an action of type getInfo arrives, resume the current generator help and assign getInfo action to the action

      • Start a non-blocking fork task yield fork(saga,… Args. Concat (action)), where args is empty and action is getInfo’s action, so… The result of args.concat(Action) is [action], whose saga corresponds to the current example, which is the saga function getInfo. So yield fork(saga,… Args.concat (Action)) starts a non-blocking task, calls the saga function getInfo, and passes in the argument [action]

      • Because the entire help function is in an infinite loop, yield fork(saga,… Args.concat (Action)) starts and continues const action = yield take(pattern), that is, waits for the next action of type getInfo to pause the current generator function help until it arrives. Keep repeating the process.

    • 2.3.2, after the introduction of the function help, the only code left for takeEvery is yield fork(help), i.e., start help in a non-blocking form.

    • 2.3.3, takeEvery has been implemented by now. We use low-order API: take, which is completed with while loop and fork. Once again, let’s comb through the following takeEvery implementation.

      • First, the takeEvery function listens on the action and executes the corresponding saga function, and takeEvery is non-blocking.

      • We use take to wait for the action to arrive, once the action arrives we use fork to run the saga function corresponding to the action, and continue to use take to wait for the next action to arrive and repeat the process. Implement takeEvery to listen to an action.

    • 3, the function in the saga file has been run, now we click the component button to get the front-end recruitment information: send an action of type getInfo, and pass in the parameter language:javascript, the specific code is as follows:

         // ... other code ...
         getInfo = language= > this.props.dispatch({ type: 'getinfo', language })
         // ... other code ...
         <button onClick={this.getInfo.bind(this.'javascript'</button>// ... other code ...
      Copy the code
    • / / select * from saga; / / take every, const action = yield take(pattern)

      function* takeEvery(pattern, saga, ... args) {
          function* help() {
              while (true) {
                  const action = yield take(pattern)
                  yieldfork(saga, ... args.concat(action)) } }yield fork(help)
      }
      Copy the code
    • 5, continue to execute the saga function getInfo, the code is as follows, very simple, call inviteInfoService to obtain the recruitment data of the corresponding language, after obtaining a setInviteInfo action, Action also carries inviteInfo, which is the job information data returned by the interface.

      function* getInfo(action) {
          // getInfo calls the interface function inviteInfoService using call, passing in the action parameter language
          const inviteInfo = yield call(inviteInfoService, action.language)
          // After receiving the job information returned by the interface, use put to send the setInviteInfo action to update the job information inviteInfo to the store
          yield put({ type: 'setInviteInfo', inviteInfo })
      }
      Copy the code
    • 6. SetInviteInfoReducer receives an action of type setInviteInfo and updates the inviteInfo data in the store.

          function setInviteInfoReducer(state = ' ', action = {}) {
              switch (action.type) {
                  case "setInviteInfo": return action.inviteInfo
                  default: return state
              }
          }
      Copy the code
    • In the end, the props data in the component was updated with the react-redux to trigger the rendering of the component. The inviteInfo data in this.props.

Continue implementing takeLatest:

Review takeLatest(Pattern,saga,… If multiple action listeners arrive, takeLatest will cancel the unfinished saga and only execute the last saga.

  • TakeLatest implementation, the code is as follows, there are two strange things, lastTask and Redux-Saga provided by the cancel method, we will analyze one by one.

    import { put, take, call, fork, cancel } from 'redux-saga/effects'
    
    function* takeLatest(pattern, saga, ... args) {
        // 0, declare lastTask
        let lastTask = null
        function* help() {
            // 2, similar to takeEvery's help, is the body of an infinite loop
            while (true) {
                // 3, pause help and wait for an action of type PATTERN to arrive
                const action = yield take(pattern)
                // 4, if there is lastTask, cancel the corresponding task of lastTask
                if (lastTask) yield cancel(lastTask)
                // 5, execute the fork task and save the returned task to lastTask
                lastTask = yieldfork(saga, ... args.concat(action)) } }// 1, start the help function without blocking
        yield fork(help)
    }
    Copy the code
    • 1, before we examine the code, let’s review the fork, fork creates a non-blocking task with a return value task, const task = yield fork(saga,… Args), task is an object with some utility methods and attributes. A task is like a process running in the background. In a Redux-Saga application, multiple tasks can be run, and tasks can be created through the fork function.

    • Cancel (task) is used to cancel a task created by a fork. The argument task is the task returned by the fork.

    • 3. Now that you’ve introduced the Cancel method and Task, start analyzing the code.

    • 3.1. Fork starts the help function first, which in turn starts a while loop

    • 3.2 in the while loop, const action = yield take(pattern) that is, help is paused for an action of type PATTERN to arrive.

    • 3.3, if (lastTask) yield cancel(lastTask) that is, if there is lastTask, the task will be cancelled

    • 3.4, lastTask = yield fork(saga,… Args. Concat (Action)), fork creates a task to process actions of type PATTERN and saves the task returned by fork to lastTask

    • 3.5, 3.3, 3.4, we can see that if an action of pattern type is continuously arrived, help will cancel the last fork task and only execute the latest fork task, that is, takeLates will execute only the last corresponding action saga.

  • Log (‘action’, action) in the saga file. If we use takeEvery to click continuously to get the job information, the console will output successive action information, while takeLatest will output only the last action information.

    // 3, get the recruitment information interface data and update it to store
    function* getInfo(action) {
        console.log('action', action);
        // 3.1, getInfo calls the inviteInfoService interface function using call and passes in the action parameter language
        const inviteInfo = yield call(inviteInfoService, action.language)
        // 3.x, to test whether takeLatest can execute only the last getInfo
        console.log('action', action);
        // 3.2, get the job information data returned by the interface, use put to send the setInviteInfo action to update the job information inviteInfo to the store
        yield put({ type: 'setInviteInfo', inviteInfo })
    }
    Copy the code
  • TakeEvery effect display: you can see the continuous output action information

  • TakeLatest effect display: you can see that only one action is output

  • Why takeEvery and takeLatest are non-blocking?

    Use the yield fork(help) to answer this question. The yield fork(help) is only used in the takeEvery function. Help has an infinite loop, and while pauses and starts, but this does not affect the rest of our code. Help runs in a separate process. This does not affect the main process, so whatever help does does not affect the code outside of takeEvery. TakeEvery can be seen as forking a process by running help, so takeEvery and takeLatest are non-blocking.

    function* takeEvery(pattern, saga, ... args) {
        function* help() {
            while (true) {
                const action = yield take(pattern)
                yieldfork(saga, ... args.concat(action)) } }yield fork(help)
    }
    Copy the code

7. Redux-saga provides cancellation, concurrent and race methods (Cancel, All, race)

Due to the word limit triggered by this article, the rest of the content is in the Redux-Saga mother-level tutorial (part 2).