Redux-saga. Блокирующие и неблокирующие эффекты. Call vs Fork
В redux-saga есть прекрасный эффект
Логика у него довольно простая, попробуем реализовать его своими силами с помощью уже знакомых простых эффектов
Тут мы запускаем бесконечный цикл, ждем, когда произойдет событие и вызываем обработчик для него.
Но этот код будет работать не так, как предполагается. Догадались, почему?
Эффект
- произошло событие
- запустилась сага
- выполнение
- когда
Если событие
Чтобы это починить, нужно заменить блокирующий эффект
Демонстрация разницы: https://codesandbox.io/s/redux-saga-call-vs-fork-react-junior-8yhf4l?file=/src/app/saga.js
Важно понять, что с неблокирующими эффектами мы не можем использовать, например, блок try-catch, так как выполнение неблокирующей саги происходит вне контекста родительской саги - в "конкурентном" режиме.
#redux #управлениесостоянием #примерыкода #saga
В redux-saga есть прекрасный эффект
takeEvery
, который позволяет подписаться на каждый вызов события. Логика у него довольно простая, попробуем реализовать его своими силами с помощью уже знакомых простых эффектов
take
и call
:
function* worker() {
yield delay(1000);
yield console.log('click');
}
function* watcher() {
while (true) {
yield take('CLICK');
yield call(worker);
}
}
Тут мы запускаем бесконечный цикл, ждем, когда произойдет событие и вызываем обработчик для него.
Но этот код будет работать не так, как предполагается. Догадались, почему?
Эффект
call
является блокирующим. Это значит, что когда он запускается, функция-генератор приостанавливает свою работу, ждет, когда он выполнится, и только потом возобновляет свое выполнение. - произошло событие
CLICK
- запустилась сага
worker
- выполнение
worker
занимает некоторое время, так как там тоже есть блокирующий эффект delay
- когда
worker
выполнился (минимум через 1 секунду), продолжается выполнение watcher
- цикл идет на следующий кругЕсли событие
CLICK
поступит во время выполнения саги worker
, его просто никто не заметит. То есть логика эффекта takeEvery
не повторяется.Чтобы это починить, нужно заменить блокирующий эффект
call
на неблокирующий fork
. Он создаст отдельную "ветку" для новой саги, а выполнение родительской саги сразу же продолжится.
function* watcher() {
while (true) {
yield fork('CLICK');
yield call(worker);
}
}
Демонстрация разницы: https://codesandbox.io/s/redux-saga-call-vs-fork-react-junior-8yhf4l?file=/src/app/saga.js
Важно понять, что с неблокирующими эффектами мы не можем использовать, например, блок try-catch, так как выполнение неблокирующей саги происходит вне контекста родительской саги - в "конкурентном" режиме.
#redux #управлениесостоянием #примерыкода #saga
CodeSandbox
Redux-saga. Call vs Fork. React Junior - CodeSandbox
Redux-saga. Call vs Fork. React Junior by furrycat.web using @reduxjs/toolkit, react, react-dom, react-redux, react-scripts, redux-saga
👍4
Redux-saga. Отмена эффекта
Теперь попробуем повторить поведение встроенного эффекта
Помним, что все функции эффектов возвращают простой объект эффекта. Именно этот объект и нужно передать в
Демо здесь: https://codesandbox.io/s/redux-saga-cancel-react-junior-hlx6hh?file=/src/app/saga.js
#redux #управлениесостоянием #примерыкода #saga
Теперь попробуем повторить поведение встроенного эффекта
takeLatest
. Помимо того, что нам нужно отслеживать событие и запускать для него воркер, необходимо еще останавливать ранее запущенные воркеры. Для этого есть эффект cancel
.Помним, что все функции эффектов возвращают простой объект эффекта. Именно этот объект и нужно передать в
cancel
, чтобы отменить эффект.
let unique = 1;
export function* clickWorker(counter) {
yield delay(1000);
yield console.log("click", counter);
}
export function* clickWatcher() {
let lastEffect;
while (true) {
yield take("CLICK");
if (lastEffect) {
yield cancel(lastEffect);
}
lastEffect = yield fork(clickWorker, unique1++);
}
}
Демо здесь: https://codesandbox.io/s/redux-saga-cancel-react-junior-hlx6hh?file=/src/app/saga.js
#redux #управлениесостоянием #примерыкода #saga
CodeSandbox
Redux-saga. Cancel. React Junior - CodeSandbox
Redux-saga. Cancel. React Junior by furrycat.web using @reduxjs/toolkit, react, react-dom, react-redux, react-scripts, redux-saga
👍3
Redux-saga. Отмена эффекта (продолжение)
Мы научились запускать неблокирующий эффект
Пример из документации - фоновое сохранение данных, например, текста поста в админке вашего сайта. При событии
Для этого в форкнутой саге нужно использовать эффект
Эффект
#redux #управлениесостоянием #примерыкода #saga
Мы научились запускать неблокирующий эффект
fork
и затем при необходимости отменять его с помощью cancel
. Но что если до отмены наша форкнутая сага успела что-то сделать, например, отправила запрос на сервер. При отмене саги было бы хорошо отменить и этот запрос.Пример из документации - фоновое сохранение данных, например, текста поста в админке вашего сайта. При событии
START_BACKGROUND_SYNC
запускается процесс синхронизации - запрос отправляется раз в какое-то время. При событии STOP_BACKGROUND_SYNC
нужно этот процесс остановить, а также отменить последний отправленный запрос.Для этого в форкнутой саге нужно использовать эффект
cancelled
+ try-finally.
function* bgSync() {
try {
while (true) {
// получаем нужные данные/отправляем нужные запросы
// повторяем каждые 5 секунд
yield delay(5000)
}
} finally {
// блок сработает, если сага была отменена
if (yield cancelled())
// логика отмены
}
}
function* main() {
// ждем события начала синхронизации
while ( yield take('START_BACKGROUND_SYNC') ) {
// форкаем сагу с логикой синхронизации
const bgSyncTask = yield fork(bgSync)
// ждем события конца синхронизации
yield take('STOP_BACKGROUND_SYNC')
// отменяем синхронизацию
yield cancel(bgSyncTask)
}
}
Эффект
cancel
останавливает работу генератора, поэтому он перепрыгивает сразу в блок finally
. Тут мы и проверяем, вызвано ли завершение работы тем, что произошла отмена.#redux #управлениесостоянием #примерыкода #saga
redux-saga.js.org
Task Cancellation | Redux-Saga
We saw already an example of cancellation in the Non blocking calls section. In this section we'll review cancellation in more detail.
🔥3
Redux-saga. Прикрепление саг к родителю
Наконец-то дошли до архитектуры - посмотрим, зачем нам нужно такое дерево саг.
Мы уже знакомы с эффектом
-
-
То есть разница заключается в наличии связи с "родительской" сагой, которая создала новую ветку.
- Сага является завершенной, если завершено ее собственное тело, а также все прикрепленные к ней ветки.
- Если в прикрепленной ветке возникает ошибка, то она всплывает вверх - к родительской саге (и дальше, если ее не обработать).
- При отмене саги с помощью эффекта
Важно: обработка ошибки в блоке try-catch возможна только для блокирующих вызовов (эффект
Ветки, созданные с помощью
#redux #управлениесостоянием #saga
Наконец-то дошли до архитектуры - посмотрим, зачем нам нужно такое дерево саг.
Мы уже знакомы с эффектом
fork
, который позволяет запускать новую ветку саг, не блокируя выполнение родителя. А есть еще spawn
. В чем между ними разница?-
fork
(от слова 'вилка') создает новую ветку, которая "прикреплена" (attached) к родителю.-
spawn
(от слова 'порождать') создает новую "отделенную" (detached) ветку.То есть разница заключается в наличии связи с "родительской" сагой, которая создала новую ветку.
- Сага является завершенной, если завершено ее собственное тело, а также все прикрепленные к ней ветки.
- Если в прикрепленной ветке возникает ошибка, то она всплывает вверх - к родительской саге (и дальше, если ее не обработать).
- При отмене саги с помощью эффекта
cancel
, отменяются также все прикрепленные к ней ветки.Важно: обработка ошибки в блоке try-catch возможна только для блокирующих вызовов (эффект
call`). Отдельные ветки (`fork
`, `spawn`) выполняются асинхронно, поэтому с ними такое не сработает.Ветки, созданные с помощью
spawn
, не имеют никакой связи со своим родителем и похожи больше на отдельные "корневые" саги - таким образом создаются новые ветки в эффекте takeEvery
.#redux #управлениесостоянием #saga
❤2🔥1
Redux-saga. Композиция саг
👉 All
Чтобы запустить несколько саг параллельно, используем эффект
Идея такая же, как в
👉 Race
Есть и аналог
Тут мы ждем, пока выполнится запрос, но не дольше одной секунды. Если запрос успеет выполниться, то его результаты придут в переменную
Удобно сочетать
Не следует использовать эффект
#redux #управлениесостоянием #примерыкода #saga
👉 All
Чтобы запустить несколько саг параллельно, используем эффект
all
:
const results = yield all([call(task1), call(task2), ...]);
yield put(showResults(results));
Идея такая же, как в
Promise.all
- дожидаемся, пока все саги в массиве выполнятся, и продолжаем исполнять код дальше.👉 Race
Есть и аналог
Promise.race
- эффект race
, но с немного другим синтаксисом:
const {posts, timeout} = yield race({
posts: call(fetchApi, '/posts'),
timeout: delay(1000)
})
if (posts)
yield put({type: 'POSTS_RECEIVED', posts})
else
yield put({type: 'TIMEOUT_ERROR'})
Тут мы ждем, пока выполнится запрос, но не дольше одной секунды. Если запрос успеет выполниться, то его результаты придут в переменную
posts
.Удобно сочетать
race
с эффектом cancel
, чтобы отменять "проигравшие" гонку саги.Не следует использовать эффект
fork
внутри race
, так как он неблокирующий и поэтому всегда будет "выполняться" первым.#redux #управлениесостоянием #примерыкода #saga
🔥1
Redux-saga. Корневая сага
Еще пару слов об организации корневой саги с учетом новых знаний про блокирующие/неблокирующие эффекты, а также про attached/detached ветки.
В корневой саге обычно запускается несколько дочерних саг и предполагается, что это происходит одновременно. Поэтому нам нужен неблокирующий способ запуска - есть несколько вариантов.
🔹Параллельный запуск с all
Тут нужно учитывать, что сам эффект all блокирующий и если после него идет какой-то код, то он будет выполнен только после завершения всех саг в массиве.
🔸Неблокирующие вызовы с fork
🔹All + fork
Чтобы решить проблему блокирования корневой саги эффектом all, можно сочетать его с fork, чтобы вызовы всех саг были неблокирующими:
Так все саги запустятся параллельно, но при этом all не будет ждать, пока все они выполнятся.
При этом все созданные ветки остаются привязанными к корневой саге, поэтому если одна из них упадет с ошибкой, корневая сага тоже упадет.
🔸Отделенные ветки со spawn
Не самое популярное решение, но тоже имеет место быть. Можно запустить дочерние саги внутри корневой с помощью spawn. Тогда они будут отделены от родителя и их ошибки не повляют на работу корневой саги.
🔹Перезапуск при падении
И еще один примерчик на сладкое - возможность перезапуска саги, если она не завелась:
Ветки создаются с помощью spawn, чтобы не влиять на родителя и не блокировать его выполнение.
Каждая дочерняя сага запускается с помощью блокирующего эффекта call, поэтому мы можем использовать блок try-catch для обработки ошибок. Если сага удачно запустилась, то на этом все и заканчивается, если же нет, то происходит новый виток бесконечного цикла while(true) и выполняется новая попытка.
В целом все эти примеры актуальны не только для корневой саги, но и для любых других.
#redux #управлениесостоянием #примерыкода #saga
Еще пару слов об организации корневой саги с учетом новых знаний про блокирующие/неблокирующие эффекты, а также про attached/detached ветки.
В корневой саге обычно запускается несколько дочерних саг и предполагается, что это происходит одновременно. Поэтому нам нужен неблокирующий способ запуска - есть несколько вариантов.
🔹Параллельный запуск с all
function* rootSaga() {
yield all([
saga1(),
saga1()
])
}
Тут нужно учитывать, что сам эффект all блокирующий и если после него идет какой-то код, то он будет выполнен только после завершения всех саг в массиве.
🔸Неблокирующие вызовы с fork
yield fork(saga1)
yield fork(saga2)
yield fork(saga3)
🔹All + fork
Чтобы решить проблему блокирования корневой саги эффектом all, можно сочетать его с fork, чтобы вызовы всех саг были неблокирующими:
yield all([ fork(saga1), fork(saga2), fork(saga3) ])
Так все саги запустятся параллельно, но при этом all не будет ждать, пока все они выполнятся.
При этом все созданные ветки остаются привязанными к корневой саге, поэтому если одна из них упадет с ошибкой, корневая сага тоже упадет.
🔸Отделенные ветки со spawn
Не самое популярное решение, но тоже имеет место быть. Можно запустить дочерние саги внутри корневой с помощью spawn. Тогда они будут отделены от родителя и их ошибки не повляют на работу корневой саги.
yield spawn(saga1)
yield spawn(saga2)
yield spawn(saga3)
🔹Перезапуск при падении
И еще один примерчик на сладкое - возможность перезапуска саги, если она не завелась:
const sagas = [
saga1,
saga2,
saga3,
];
yield all(sagas.map(saga =>
spawn(function* () {
while (true) {
try {
yield call(saga)
break
} catch (e) {
console.log(e)
}
}
}))
);
Ветки создаются с помощью spawn, чтобы не влиять на родителя и не блокировать его выполнение.
Каждая дочерняя сага запускается с помощью блокирующего эффекта call, поэтому мы можем использовать блок try-catch для обработки ошибок. Если сага удачно запустилась, то на этом все и заканчивается, если же нет, то происходит новый виток бесконечного цикла while(true) и выполняется новая попытка.
В целом все эти примеры актуальны не только для корневой саги, но и для любых других.
#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. ActionChannel
Эффект
А что делать, если мы не хотим пропускать события, но при этом необходимо обрабатывать их последовательно, а не параллельно?
Для этого в саге есть эффект
Тут важно использовать именно
#redux #управлениесостоянием #примерыкода #saga
Эффект
takeEvery
позволяют ловить и обрабатывать каждый экшен - все обработчики выполняются параллельно.take
+ fork
делает то же самое, а take
+ call
позволяет игнорировать новые события, пока не будет обработано предыдущее.А что делать, если мы не хотим пропускать события, но при этом необходимо обрабатывать их последовательно, а не параллельно?
Для этого в саге есть эффект
actionChannel
- канал экшенов. Нужно создать канал, а потом подписаться на него (а не на сам экшен). Канал будет буферизировать поступающие экшены, пока не обработан предыдущий.
function* watchRequests() {
// создаем канал
const requestChan = yield actionChannel('REQUEST')
while (true) {
// подписываемся на него
const {payload} = yield take(requestChan)
// обрабатываем экшен
yield call(handleRequest, payload)
}
}
Тут важно использовать именно
call
для обработки, так как она должна быть блокирующей.#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. EventChannel
ActionChannel из прошлого поста предназначен для работы с redux-стором и обычными экшенами - он просто особым образом их обрабатывает.
Но можно создать похожий канал для событий любого типа, не связанных со стором (например, на веб-сокеты). В документации приводится пример обратного счетчика, который отправляет событие каждую секунду, пока не дойдет до нуля. То есть мы создаем дополнительный источник экшенов, кроме стора - и так же можем на него подписаться.
Создается такой канал с помощью функции
В
То есть мы находим внешний источник нужных нам событий, подписываемся на событие, а когда оно происходит отправляем его в канал. А на этот канал потом можно будет подписаться в любой саге, как на экшены стора.
Чтобы закрыть канал (если события перестали поступать, нужно использовать константу `END`).
Функция-подписчик должна вернуть метод для сброса эффектов (как в хуке
Использование канала событий:
Канал можно закрыть и извне, вызвав его метод
#redux #управлениесостоянием #примерыкода #saga
ActionChannel из прошлого поста предназначен для работы с redux-стором и обычными экшенами - он просто особым образом их обрабатывает.
Но можно создать похожий канал для событий любого типа, не связанных со стором (например, на веб-сокеты). В документации приводится пример обратного счетчика, который отправляет событие каждую секунду, пока не дойдет до нуля. То есть мы создаем дополнительный источник экшенов, кроме стора - и так же можем на него подписаться.
Создается такой канал с помощью функции
eventChannel
:
import { eventChannel, END } from 'redux-saga'
function createCountdownChannel(seconds) {
const channel = eventChannel(function(emit) {
const interval = setInterval(function() {
seconds--;
if (seconds > 0) emit(seconds);
else emit(END);
}, 1000);
return function() {
clearInterval(interval);
}
})
return channel;
}
В
eventChannel
нужно передать функцию, которая будет подписываться на источник событий. В качестве аргумента она получает эмиттер - функцию, которая будет диспатчить экшены в канал событий.То есть мы находим внешний источник нужных нам событий, подписываемся на событие, а когда оно происходит отправляем его в канал. А на этот канал потом можно будет подписаться в любой саге, как на экшены стора.
Чтобы закрыть канал (если события перестали поступать, нужно использовать константу `END`).
Функция-подписчик должна вернуть метод для сброса эффектов (как в хуке
useEffect
в React).Использование канала событий:
export function* saga() {
const channel = yield call(createCountdownChannel, 10)
try {
while (true) {
let seconds = yield take(channel)
console.log(`countdown: ${seconds}`)
}
} finally {
console.log('countdown terminated')
}
}
Канал можно закрыть и извне, вызвав его метод
close
:
channel.close()
#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. Простые каналы
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на что не подписаны, ни на стор, ни на внешний источник событий. Это совершенно пустой канал, в котором нет ни одного события.
Одна сага может его создать, другая сага может на него подписаться. Пушить события в него нужно вручную.
Зачем все это нужно?
Документация предлагает нам следующий кейс: с текущими инструментами мы можем либо обрабатывать экшены последовательно, не начиная новый, пока не закончится предыдущий (`take`), или же параллельно все (`takeEvery`). Но не можем, например, обрабатывать параллельно только 3 экшена. И пока не закончится обработка хотя бы одного из нех, не начинать следующий.
Вот для этого нам и нужны каналы.
Что тут происходит?
Мы создаем канал, создаем три слушателя для этого канала. Затем как обычно ловим нужный экшен и просто кладем его в канал.
При попадании экшена в канал, его получит первый свободный слушатель (если свободны несколько, получит все равно только один). Если все слушатели уже заняты, событие останется висеть в канале до тех пор, пока кто-то не освободится.
#redux #управлениесостоянием #примерыкода #saga
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на что не подписаны, ни на стор, ни на внешний источник событий. Это совершенно пустой канал, в котором нет ни одного события.
Одна сага может его создать, другая сага может на него подписаться. Пушить события в него нужно вручную.
Зачем все это нужно?
Документация предлагает нам следующий кейс: с текущими инструментами мы можем либо обрабатывать экшены последовательно, не начиная новый, пока не закончится предыдущий (`take`), или же параллельно все (`takeEvery`). Но не можем, например, обрабатывать параллельно только 3 экшена. И пока не закончится обработка хотя бы одного из нех, не начинать следующий.
Вот для этого нам и нужны каналы.
import { channel } from 'redux-saga'
function* watcher() {
const chan = yield call(channel); // создаем канал
// подписываемся на этот канал три раза
yield fork(handleRequest, chan);
yield fork(handleRequest, chan);
yield fork(handleRequest, chan);
while (true) {
// ловим экшен REQUEST и кладем в канал
const {payload} = yield take('REQUEST')
yield put(chan, payload)
}
}
function* handler(chan) {
while (true) {
// ловим событие из канала
const payload = yield take(chan)
...
}
}
Что тут происходит?
Мы создаем канал, создаем три слушателя для этого канала. Затем как обычно ловим нужный экшен и просто кладем его в канал.
При попадании экшена в канал, его получит первый свободный слушатель (если свободны несколько, получит все равно только один). Если все слушатели уже заняты, событие останется висеть в канале до тех пор, пока кто-то не освободится.
#redux #управлениесостоянием #примерыкода #saga
Telegram
React Junior
Redux-saga. ActionChannel
Эффект takeEvery позволяют ловить и обрабатывать каждый экшен - все обработчики выполняются параллельно.
take + fork делает то же самое, а take + call позволяет игнорировать новые события, пока не будет обработано предыдущее.
…
Эффект takeEvery позволяют ловить и обрабатывать каждый экшен - все обработчики выполняются параллельно.
take + fork делает то же самое, а take + call позволяет игнорировать новые события, пока не будет обработано предыдущее.
…
👍2
Redux-saga. Multicast-каналы
И наконец в дополнение ко всем уже разобранным каналам у нас есть multicast-каналы. Они работают так же, как и обычные каналы (channel), только уведомление о поступившем событии получают ВСЕ подписчики.
Таким образом, на одно событие можно подписать несколько обработчиков с разной логикой.
#redux #управлениесостоянием #примерыкода #saga
И наконец в дополнение ко всем уже разобранным каналам у нас есть multicast-каналы. Они работают так же, как и обычные каналы (channel), только уведомление о поступившем событии получают ВСЕ подписчики.
Таким образом, на одно событие можно подписать несколько обработчиков с разной логикой.
import { multicastChannel } from 'redux-saga'
function* watchRequests() {
const channel = yield call(multicastChannel)
// подписываем разные обработчики
yield fork(handler1, channel)
yield fork(handler2, channel)
while (true) {
const { payload } = yield take('REQUEST')
yield put(channel, payload)
}
}
#redux #управлениесостоянием #примерыкода #saga
Telegram
React Junior
Redux-saga. Простые каналы
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на…
У нас уже были каналы для экшенов из стора (ActionChannel), каналы для событий из внешних источников (EventChannel).
И в дополнение ко всему этому есть самые простые каналы - Channel. Их отличие в том, что изначально они ни на…
👍2
Redux-saga. Кастомный I/O
Обычно мы подключаем саги к стору приложения с помощью миддлвара. Эффекты
Но можно запустить сагу отдельно от стора, если это необходимо.
Для этого нам нужно создать кастомный "стор" с полями
-
-
- и
То есть у этого стора должен быть канал, в который будут поступать события - его создаем с помощью функции
Для запуска вызываем функцию
#redux #управлениесостоянием #примерыкода #saga
Обычно мы подключаем саги к стору приложения с помощью миддлвара. Эффекты
take
и put
связываются со стором и слушают его/диспатчат в него новые экшены.Но можно запустить сагу отдельно от стора, если это необходимо.
Для этого нам нужно создать кастомный "стор" с полями
-
channel
, -
dispatch
- и
getState
. То есть у этого стора должен быть канал, в который будут поступать события - его создаем с помощью функции
stdChannel
. Этот канал нужно соединить с внешним I/O.Для запуска вызываем функцию
runSaga
и передаем ей новый стор и собственно сагу.
import { runSaga, stdChannel } from 'redux-saga'
const emitter = new EventEmitter()
const channel = stdChannel()
emitter.on("action", channel.put)
const myIO = {
channel,
dispatch(output) {
emitter.emit("action", output)
},
getState() {
return state
}
}
runSaga(
myIO,
function* saga() { ... },
)
#redux #управлениесостоянием #примерыкода #saga
👍2
Redux-saga. Рецепты
На страничке Recipes https://redux-saga.js.org/docs/recipes в документации redux-saga есть несколько полезных сниппетов:
- throttling
С использованием встроенного эффекта
- debouncing
Сохраняем активный воркер и отменяем его (`cancel`), если пришло новое событие, либо используем встроенный эффект
- повторное выполнение неудачных XHR-запросов
С использованием встроенного эффекта
- функционал Отменить последнее действие
С помощью
- batching нескольких экшенов
С использованием библиотеки redux-batched-actions.
#redux #управлениесостоянием #документация #saga
На страничке Recipes https://redux-saga.js.org/docs/recipes в документации redux-saga есть несколько полезных сниппетов:
- throttling
С использованием встроенного эффекта
throttle
.- debouncing
Сохраняем активный воркер и отменяем его (`cancel`), если пришло новое событие, либо используем встроенный эффект
takeLatest
.- повторное выполнение неудачных XHR-запросов
С использованием встроенного эффекта
retry
или комбинацией базовых эффектов call
, take
, delay
и блока try-catch.- функционал Отменить последнее действие
С помощью
spawn
и race
.- batching нескольких экшенов
С использованием библиотеки redux-batched-actions.
#redux #управлениесостоянием #документация #saga
redux-saga.js.org
Recipes | Redux-Saga
Throttling
👍3
Redux ToolKit: краткий конспект
Собираюсь повторить RTK Query (а затем разобраться с React Query). Поэтому краткая выжимка по Redux Toolkit:
1. Мыслим слайсами
Функция createSlice создает сразу и action creators, и редьюсер, и даже набор селекторов, если мы работаем с EntityAdapter.
2. Асинхронщина
Вся асинхронщина через createAsyncThunk + extraReducers.
3. Селекторы
Селекторы напрямую в RTK не входят, для них используем reselect.
4. Связь с компонентами
В компонентах используем классические хуки
5. Создание хранилища
Стор создается с помощью configureStore.
***
В целом это все, в основном удобный синтаксический сахар, ничего сверхъестественного поверх базового Redux нет.
#управлениесостоянием #redux
Собираюсь повторить RTK Query (а затем разобраться с React Query). Поэтому краткая выжимка по Redux Toolkit:
1. Мыслим слайсами
Функция createSlice создает сразу и action creators, и редьюсер, и даже набор селекторов, если мы работаем с EntityAdapter.
2. Асинхронщина
Вся асинхронщина через createAsyncThunk + extraReducers.
3. Селекторы
Селекторы напрямую в RTK не входят, для них используем reselect.
4. Связь с компонентами
В компонентах используем классические хуки
useSelector
и useDispatch
.5. Создание хранилища
Стор создается с помощью configureStore.
***
В целом это все, в основном удобный синтаксический сахар, ничего сверхъестественного поверх базового Redux нет.
#управлениесостоянием #redux
Telegram
React Junior
Redux Toolkit. Слайсы и экшены
Переходим к отдельным редьюсерам, или слайсам (срезам). Будем рассматривать их вместе с экшенами, так как они тесно связаны, ведь редьюсеры обрабатывают экшены.
В обычном redux это довольно многословная часть кода: здесь…
Переходим к отдельным редьюсерам, или слайсам (срезам). Будем рассматривать их вместе с экшенами, так как они тесно связаны, ведь редьюсеры обрабатывают экшены.
В обычном redux это довольно многословная часть кода: здесь…
👍5
React Query vs RTK Query: всестороннее сравнение
Статья (англ.): https://www.frontendmag.com/insights/react-query-vs-rtk-query/
В статье сравниваются два популярных решения для взаимодействия React-приложений с удаленными источниками данных.
Сходство
🔹Оба инструмента используют лучшие performance-практики работы с удаленными данными:
- кеширование запросов
- предотвращение дублирующихся запросов
- повторные запросы в случае ошибок
🔹Оба инструмента предлагают решения для самых распространненных задач получения данных:
- пагинация - обновление списка данных
- оптимистические обновления
Отличие
Отличие заключается в архитектуре инструментов.
🔸React Query - это независимая библиотека. Ее функциональность реализована в виде хуков, то есть привязана к жизненному циклу React-компонентов.
🔸RTK Query - это инструмент, созданный поверх Redux Toolkit. Он работает в парадигме Redux: через редьюсеры и экшены, взаимодействует с глобальным стейтом приложения.
Кривая обучения
Автор считает, что и React Query, и RTK Query интуитивно понятны. Это довольно спорное утверждение, так как чтобы понять и то, и другое, требуется немного отформатировать свое понимание работы с данными.
Что касается RTK Query, тут вообще требуется сначала разобраться с Redux Toolkit - а это дело непростое.
Когда что использовать (привет, кэп)
RTK Query лучше использовать, если:
- В приложении уже используется Redux и Redux Toolkit. Тогда RTK Query доступна из коробки и уже интегрирована с прочими инструментами.
- Требуется сложная логика управления данными. RTK Query - это более мощный инструмент и на нем можно построить более сложную систему.
React Query лучше использовать, если:
- В приложении не используется глобальное состояние или запросы данных с ним не взаимодействуют. React Query не предоставляет никаких способов создания глобального стейта или взаимодействия с ним, все только внутри компонента.
- Требуется простая логика обработки данных.
- Не хочется долго разбираться.
- Не хочется тянуть зависимости в виде Redux Toolkit.
#управлениесостоянием #redux
Статья (англ.): https://www.frontendmag.com/insights/react-query-vs-rtk-query/
В статье сравниваются два популярных решения для взаимодействия React-приложений с удаленными источниками данных.
Сходство
🔹Оба инструмента используют лучшие performance-практики работы с удаленными данными:
- кеширование запросов
- предотвращение дублирующихся запросов
- повторные запросы в случае ошибок
🔹Оба инструмента предлагают решения для самых распространненных задач получения данных:
- пагинация - обновление списка данных
- оптимистические обновления
Отличие
Отличие заключается в архитектуре инструментов.
🔸React Query - это независимая библиотека. Ее функциональность реализована в виде хуков, то есть привязана к жизненному циклу React-компонентов.
🔸RTK Query - это инструмент, созданный поверх Redux Toolkit. Он работает в парадигме Redux: через редьюсеры и экшены, взаимодействует с глобальным стейтом приложения.
Кривая обучения
Автор считает, что и React Query, и RTK Query интуитивно понятны. Это довольно спорное утверждение, так как чтобы понять и то, и другое, требуется немного отформатировать свое понимание работы с данными.
Что касается RTK Query, тут вообще требуется сначала разобраться с Redux Toolkit - а это дело непростое.
Когда что использовать (привет, кэп)
RTK Query лучше использовать, если:
- В приложении уже используется Redux и Redux Toolkit. Тогда RTK Query доступна из коробки и уже интегрирована с прочими инструментами.
- Требуется сложная логика управления данными. RTK Query - это более мощный инструмент и на нем можно построить более сложную систему.
React Query лучше использовать, если:
- В приложении не используется глобальное состояние или запросы данных с ним не взаимодействуют. React Query не предоставляет никаких способов создания глобального стейта или взаимодействия с ним, все только внутри компонента.
- Требуется простая логика обработки данных.
- Не хочется долго разбираться.
- Не хочется тянуть зависимости в виде Redux Toolkit.
#управлениесостоянием #redux
Frontend Mag
React Query vs RTK Query: A Comprehensive Comparison - Frontend Mag
We compare and contrast React Query vs RTK Query to help developers understand the similarities and differences between these two popular data management libraries for React applications.
👍1