Получение будущих действий

До сих пор мы использовали helper Effect takeEvery, чтобы создать новый таск для каждого входящего action. Это немного напоминает поведение redux-thunk: например, каждый раз, когда компонент, вызывает fetchProducts Action Creator, Action Creator отправляет thunk для выполнения потока управления.

В действительности, takeEvery - всего лишь враппер для внутренней вспомогательной функции, созданной на основе более низкого уровня и более мощного API. В этом разделе мы увидим новый Effect take, который позволит построить сложный поток управления, позволяя полностью контролировать процесс наблюдения за action.

Простой логгер action-ов

Давайте рассмотрим простой пример Саги, которая следит за всеми действиями, отправленными в Store, и выводит их в консоль.

Используя takeEvery('*') (с аргументом *) мы можем отловить все отправленные action независимо от их типов.

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

function* watchAndLog() {
  yield takeEvery('*', function* logger(action) {
    const state = yield select()

    console.log('action', action)
    console.log('state after', state)
  })
}

Теперь давайте посмотрим, как использовать Effect take для реализации того же потока, что и выше

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

function* watchAndLog() {
  while (true) {
    const action = yield take('*')
    const state = yield select()

    console.log('action', action)
    console.log('state after', state)
  }
}

Effecttake похож на call и на put который мы видили раньше. Он создает еще один командный объект,1 который сообщает middleware, что-бы тот ждал определенного действия.

Результирующее поведение Effect-а call аналогично тому, когда midleware приостанавливает генератор до того пока Promise не вернет результат. В приведенном выше примере watchAndLog приостанавливается до тех пор пока не будет диспачено какое нибудь действие.

Обратите внимание на то, как мы запускаем бесконечный цикл while (true). Помните, что это функция Генератор, которая работает не также как обычные функции. Наш генератор будет останавливатся на каждой итерации в ожидании пока не произайдет диспатч.

Использование take оказывает тонкое влияние на то, как мы пишем наш код. В случае takeEvery вызываемые таски не контролируют, когда они будут вызваны. Они будут вызываться снова и снова для каждого соответствующего action. Они также не контролируют, когда остановить наблюдение.

В случае с take управление - инвертируется. Instead of the actions being pushed to the handler tasks, the Saga is pulling the action by itself. Это выглядит так, как будто Saga выполняет нормальную функцию action = getNextAction(), которая будет решать, когда action будет диспатчен.

Эта инверсия управления позволяет нам реализовать потоки управления, которые нетривиальны для традиционного подхода push.

В качестве простого примера предположим, что в нашем приложении Todo мы хотим посмотреть действия пользователя и показать поздравительное сообщение после того, как пользователь создал свои первые три заметки.

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

function* watchFirstThreeTodosCreation() {
  for (let i = 0; i < 3; i++) {
    const action = yield take('TODO_CREATED')
  }
  yield put({type: 'SHOW_CONGRATULATION'})
}

Instead of a while (true) we're running a for loop which will iterate only three times. After taking the first three TODO_CREATED actions, watchFirstThreeTodosCreation will cause the application to display a congratulation message then terminate. This means the Generator will be garbage collected and no more observation will take place.

Another benefit of the pull approach is that we can describe our control flow using a familiar synchronous style. For example, suppose we want to implement a login flow with two actions LOGIN and LOGOUT. Using takeEvery (or redux-thunk) we'll have to write two separate tasks (or thunks): one for LOGIN and the other for LOGOUT.

The result is that our logic is now spread in two places. In order for someone reading our code to understand it, they would have to read the source of the two handlers and make the link between the logic in both in their head. In other words, it means they would have to rebuild the model of the flow in their head by rearranging mentally the logic placed in various places of the code in the correct order.

Using the pull model we can write our flow in the same place instead of handling the same action repeatedly.

function* loginFlow() {
  while (true) {
    yield take('LOGIN')
    // ... выполняем логику авторизации
    yield take('LOGOUT')
    // ... выполняем логику выхода
  }
}

loginFlow более четко передает ожидаемую последовательность действий. Он знает, что за action LOGIN всегда следует действие LOGOUT и что LOGOUT всегда следует LOGIN (хороший пользовательский интерфейс должен всегда обеспечивать последовательный порядок действий, скрывая или отключая неожиданное фсешщт).

1. It creates another command object

results matching ""

    No results matching ""