Introduction to Redux Saga

Redux Saga is a library for handling side effects in Redux. When you perform an operation, the state of the application will undergo some changes, and you may need to do something derived from the state change

When to use Redux Saga

Use in applicationRedux, When you perform an operation, the state of the application changes.

When this happens, you may need to do something that stems from this state change.

For example, you might want:

  • Make HTTP calls to the server
  • Send a WebSocket event
  • FromGraphQLserver
  • Save content to cache or browser local storage

...You have an idea.

These are things that are completely unrelated or asynchronous to the application state, and you need to move them to a different place than the operation or reducer (technically speaking,can, This is not a good way to have a clean code base).

Enter Redux Saga, which is Redux middleware that can help you solve side effects.

Basic example using Redux Saga

In order to avoid investing too much theory before showing some actual code, I briefly describe how to solve the problems encountered when building the sample application.

In the chat room, when a user composes a message, I immediately display the message on the screen to provide immediate feedback. This is through aRedux actions:

const addMessage = (message, author) => ({
  type: 'ADD_MESSAGE',
  message,
  author
})

And change the status through the reducer:

const messages = (state = [], action) => {
  switch (action.type) {
    case 'ADD_MESSAGE':
      return state.concat([{
        message: action.message,
        author: action.author
      }])
    default:
      return state
  }
}

You can import Redux Saga first, and then applySagaAs the middleware of Redux Store:

//...
import createSagaMiddleware from 'redux-saga'
//...

Then, we create a middleware and apply it to our newly created Redux Store:

const sagaMiddleware = createSagaMiddleware()

const store = createStore( reducers, applyMiddleware(sagaMiddleware) )

The last step is to run the legend. We import it and pass it to the run method of the middleware:

import handleNewMessage from './sagas'
//...
sagaMiddleware.run(handleNewMessage)

We just need to write a legend in it./sagas/index.js:

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

const handleNewMessage = function* handleNewMessage(params) { const socket = new WebSocket(‘ws://localhost:8989’) yield takeEvery(‘ADD_MESSAGE’, (action) => { socket.send(JSON.stringify(action)) }) }

export default handleNewMessage

The meaning of this code is:EachThisADD_MESSAGEAction is triggered, we send toWeb socketServer, in this case will respondlocalhost:8989.

Pay attention to usefunction*, This is not a normal function, butgenerator.

How does it work in the background

become aReduxThe middleware Redux Saga can intercept Redux Actions and inject its own functions.

Here are some concepts to master, the following are the main keywords you want to keep in mind:Saga,generator,Middleware,committed to,time out,restore,influences,send,action,carry out,solve,yield,yield.

A kindSagaIs some "story", rightinfluencesCaused by your code. This may include one of the things we discussed before, such as HTTP requests or some process of saving to the cache.

We create aMiddlewareWith listSagasRun, it can be one or more, and then we connect this middleware to Redux storage.

A kindSagaIs angeneratorFeatures. Be acommitted toRun andyield, Middlewaretime outThisSagauntilcommitted toYessolve.

oncecommitted toYessolveMiddlewareResumeLegend until nextyieldFind the statement, right theretime outTill it againcommitted to solve.

In the legendary code, you will generateeffectUsed byredux-sagapackage. First, we can list:

  • takeEvery()
  • takeLatest()
  • take()
  • call()
  • put()

Be ainfluencesIs executed,SagaYesPauseduntilinfluencesYescarry out.

E.g:

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

const handleNewMessage = function* handleNewMessage(params) { const socket = new WebSocket(‘ws://localhost:8989’) yield takeEvery(‘ADD_MESSAGE’, (action) => { socket.send(JSON.stringify(action)) }) }

export default handleNewMessage

when. . . whenMiddlewarecarried outhandleNewMessagelegendStopinyield takeEveryInstructions andwait(Asynchronously, Of course) untilADD_MESSAGEAction issend. Then it runs its callback, andSagawere ablerestore.

Basic helper

The auxiliary program is an abstraction on top of the underlying saga API.

Let us introduce the most basic helpers that can be used to run effects:

  • takeEvery()
  • takeLatest()
  • take()
  • put()
  • call()

takeEvery()

takeEvery()In some examples, one of these helpers is used.

In the code:

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

function* watchMessages() { yield takeEvery(‘ADD_MESSAGE’, postMessageToServer) }

ThiswatchMessagesThe generator is suspended untilADD_MESSAGEAction shooting, andEachIt will triggerpostMessageToServerInfinite and running simultaneously (no needpostMessageToServerTerminate its execution before the new one can run)

takeLatest()

Another popular helper istakeLatest(),versustakeEvery()But only one function handler is allowed to run at a time, thus avoiding concurrency. If another operation is triggered while the handler is still running, it will cancel the operation and run it again with the latest available data.

versustakeEvery(), When the specified operation occurs, the generator will never stop and continue to run the effect.

take()

take()The difference is that it only waits once. When the action it is waiting for occurs, the promise will resolve and the iterator will resume, so it can proceed to the next instruction set.

put()

Dispatch the operation to the Redux store. No need to pass Redux storage or distribution operation to saga, you can useput():

yield put({ type: 'INCREMENT' })
yield put({ type: "USER_FETCH_SUCCEEDED", data: data })

It returns a simple object, which you can easily check in a test (more on that later).

call()

When you want to call a function in a legend, you can use a normal function call that produces a yield that returns a promise to do this:

delay(1000)

But this did not work well in the test. instead,call()Allows you to wrap the function call and return an easy-to-check object:

call(delay, 1000)

Return

{ CALL: {fn: delay, args: [1000]}}

Parallel operation effect

Use the following commands to run the effects in parallelall()withrace(), They work very differently.

all()

If you write

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

const todos = yield call(fetch, ‘/api/todos’) const user = yield call(fetch, ‘/api/user’)

secondfetch()The call will not be executed until the first call is successful.

To execute them in parallel, wrap them intoall():

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

const [todos, user] = yield all([ call(fetch, ‘/api/todos’), call(fetch, ‘/api/user’) ])

all()Until both are resolvedcall()return.

race()

race()Different fromall()Don't wait for all helpers' calls to return. It just waited for someone to return and it was done.

This is a game, see which one finishes first, and then we forget the other participants.

It is usually used to cancel background tasks that run forever until a certain situation occurs:

import { race, call, take } from 'redux-saga/effects'

function* someBackgroundTask() { while(1) { //… } }

yield race([ bgTask: call(someBackgroundTask), cancel: take(‘CANCEL_TASK’) ])

when. . . whenCANCEL_TASKAfter the operation was issued, we stopped other tasks that would run forever.

Download mine for freeResponse Handbook


More response tutorials: