React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
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
👍3
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
🔥3
Redux-saga. Прикрепление саг к родителю

Наконец-то дошли до архитектуры - посмотрим, зачем нам нужно такое дерево саг.

Мы уже знакомы с эффектом fork, который позволяет запускать новую ветку саг, не блокируя выполнение родителя. А есть еще spawn. В чем между ними разница?

- fork (от слова 'вилка') создает новую ветку, которая "прикреплена" (attached) к родителю.
- spawn (от слова 'порождать') создает новую "отделенную" (detached) ветку.

То есть разница заключается в наличии связи с "родительской" сагой, которая создала новую ветку.

- Сага является завершенной, если завершено ее собственное тело, а также все прикрепленные к ней ветки.
- Если в прикрепленной ветке возникает ошибка, то она всплывает вверх - к родительской саге (и дальше, если ее не обработать).
- При отмене саги с помощью эффекта cancel, отменяются также все прикрепленные к ней ветки.

Важно: обработка ошибки в блоке try-catch возможна только для блокирующих вызовов (эффект call`). Отдельные ветки (`fork`, `spawn`) выполняются асинхронно, поэтому с ними такое не сработает.

Ветки, созданные с помощью spawn, не имеют никакой связи со своим родителем и похожи больше на отдельные "корневые" саги - таким образом создаются новые ветки в эффекте takeEvery.

#redux #управлениесостоянием #saga
2🔥1
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


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

Эффект 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-стором и обычными экшенами - он просто особым образом их обрабатывает.

Но можно создать похожий канал для событий любого типа, не связанных со стором (например, на веб-сокеты). В документации приводится пример обратного счетчика, который отправляет событие каждую секунду, пока не дойдет до нуля. То есть мы создаем дополнительный источник экшенов, кроме стора - и так же можем на него подписаться.

Создается такой канал с помощью функции 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 экшена. И пока не закончится обработка хотя бы одного из нех, не начинать следующий.

Вот для этого нам и нужны каналы.


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
👍2
Redux-saga. Multicast-каналы

И наконец в дополнение ко всем уже разобранным каналам у нас есть 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
👍2
Redux-saga. Кастомный I/O

Обычно мы подключаем саги к стору приложения с помощью миддлвара. Эффекты 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
С использованием встроенного эффекта throttle.

- debouncing
Сохраняем активный воркер и отменяем его (`cancel`), если пришло новое событие, либо используем встроенный эффект takeLatest.

- повторное выполнение неудачных XHR-запросов
С использованием встроенного эффекта retry или комбинацией базовых эффектов call, take, delay и блока try-catch.

- функционал Отменить последнее действие
С помощью spawn и race.

- batching нескольких экшенов
С использованием библиотеки redux-batched-actions.

#redux #управлениесостоянием #документация #saga
👍3
Forwarded from TypeScript Challenge
Type Challenges #11. Tuple to object

Ссылка на задачу

Условие

Нужно превратить массив в объект, ключами в котором являются элементы исходного массива.

Решение


type TupleToObject<T extends ReadonlyArray<string | number | symbol>> = {
[P in T[number]]: P
}


Прежде всего, уточняем, что ключами объекта могут быть только строки/числа/символы. Вместо перечисления можно использовать встроенный тип PropertyKey:


type TupleToObject<T extends readonly PropertyKey[]> = {
[P in T[number]]: P
}


Для получения набора элементов массива используем конструкцию T[number].

#easy
👍6
Forwarded from TypeScript Challenge
Type Challenges #189. Awaited

Ссылка на задачу

Условие

Предположим, что у нас есть тип, завернутый в другой тип, например, Promise<string>. Нужно написать утилиту MyAwaited для получения внутреннего типа.

Решение

Чтобы решить задачку, нужно понять, какая именно обертка может быть. Посмотрим на тестовые кейсы:


type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

type cases = [
Expect<Equal<MyAwaited<X>, string>>,
Expect<Equal<MyAwaited<Y>, { field: number }>>,
Expect<Equal<MyAwaited<Z>, string | number>>,
Expect<Equal<MyAwaited<Z1>, string | boolean>>,
Expect<Equal<MyAwaited<T>, number>>,
]


Тут обычные промисы, а также кастомная структура с полем then. Кроме того, внутри промиса может быть другой промис и так далее.

Для описания обертки нам подойдет встроенная утилита PromiseLike<T>, осталось только вывести вложенный тип с помощью infer:


type MyAwaited<T> = T extends PromiseLike<infer A> ? A : never;


Но не забываем, что внутри обертки может быть еще одна обертка, поэтому нужно добавить немножко рекурсии:


type MyAwaited<T> = T extends PromiseLike<infer A> ? (A extends PromiseLike<infer B> ? MyAwaited<A> : A) : never;

#easy
👍2
Redux ToolKit: краткий конспект

Собираюсь повторить RTK Query (а затем разобраться с React Query). Поэтому краткая выжимка по Redux Toolkit:

1. Мыслим слайсами

Функция createSlice создает сразу и action creators, и редьюсер, и даже набор селекторов, если мы работаем с EntityAdapter.

2. Асинхронщина

Вся асинхронщина через createAsyncThunk + extraReducers.

3. Селекторы

Селекторы напрямую в RTK не входят, для них используем reselect.

4. Связь с компонентами

В компонентах используем классические хуки useSelector и useDispatch.

5. Создание хранилища

Стор создается с помощью configureStore.

***

В целом это все, в основном удобный синтаксический сахар, ничего сверхъестественного поверх базового Redux нет.

#управлениесостоянием #redux
👍5
Forwarded from TypeScript Challenge
Type Challenges. Уровень easy. Резюме

Итак, мы прорешали 13 задачек уровня easy.

4 - Pick
7 - Readonly
11 - Tuple to Object
14 - First of Array
18 - Length of Tuple
43 - Exclude
189 - Awaited
268 - If
533 - Concat
898 - Includes
3057 - Push
3060 - Unshift
3312 - Parameters

Проведем небольшую ретроспективу и отметим основные моменты решений.

🟢 1. extends

Самое популярное ключевое слово в решениях.

Используется для уточнения типов в дженериках (9 задач):


type MyPick<T, K extends keyof T>


Используется для сравнения в условных типах (2 задачи):


type If<C extends boolean, T, F> = C extends true ? T : F


Используется в комбинации с ключевым словом infer для выведения одних типов из других (4 задачи):


type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never


🟢 2. Копирование ключей объекта (3 задачи)

Используется для создания Mapped Types, когда новый тип повторяет ключи исходного


type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}


🟢 3. keyof

Ключевое слово для получения набора из всех ключей объекта (2 задачи)


type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}


🟢 4. Превращение массива типов в перечисление типов(1 задача)

Используем конструкцию T[number].


type TupleToObject<T extends ReadonlyArray<string | number | symbol>> = {
[P in T[number]]: P
}


🟢 5. Использование readonly структур для работы с кортежами (3 задачи)


type Length<T extends readonly any[]> = T['length']


🟢 6. Деструктуризация массивов (5 задач)


type Push<T extends any[], U> = [...T, U]


#easy
👍4
RTK Query

Освежаю знания об RTK Query

RTK Query - это часть Redux Toolkit, которая предназначена для удобной работы с API. Она собирает все, что относится к API в одном месте и дает нам много приятного синтаксического сахара.

Заводим для API отдельный слайс, где будут лежать все данные, полученные с сервера. Но не обычный слайс, который createSlice, а прокачанный слайс, который createApi.
Подключить этот api-слайс к стору посложнее, чем обычный, тут нужно и редьюсер добавить, и специальный миддлвар.

В api-слайсе определяем функцию, которая будет осуществлять запросы - baseQuery.

На этом техническая подготовка заканчивается и начинается собственно логика обмена данными - поле endpoints. Тут мы получаем builder и с его помощью (`builder.query`) создаем массив наших эндпоинтов (есть много разных настроек) . Каждый эндпоинт на выходе из слайса создает себе личный хук , который можно дернуть в любом месте приложения. А в этом хуке уже предусмотрена куча удобных поле - data, isLoading, error и т.п. И дополнительно можно закинуть ряд настроек, вроде селектора . То есть вытаскивать данные из API со всеми удобствами мы уже можем. Можно даже сочетать все это дело с EnitityAdapter.

Примечание: можно выполнить запрос и «вручную» без использования хука.

Отправка данных на сервер работает практически так же, как и вытягивание, только эндпоинт нужно немного по-другому оформить (через `builder.mutation`). Хук тоже немного другой.

Самое приятное, что запросы к серверу кешируются, однако кеш иногда приходится сбрасывать. Это можно сделать напрямую руками, с помощью метода refetch, который есть в хуках. Но лучше настроить зависимости с помощью системы тегов, чтобы RTK Query сбрасывала то, что нужно, самостоятельно.

Конечно, api-слайс можно расчленять (injectEndpoints).

RTK Query предлагает продвинутые техники работы с API, например, оптимистичное обновление, когда данные меняются сразу, а запрос выполняется уже в фоне. Еще есть возможность подписаться на источник событий (сокет-канал) , чтобы получать данные из него.

Предыдущее резюме по RTK Query, с большим количество технических подробностей.

#управлениесостоянием #rtkquery #обменданными
🔥3👍2
Forwarded from TypeScript Challenge
Type Challenges #12. Chainable Options

Ссылка на задачу
Ссылка на песочницу с тестами

Условие

У нас есть некий объект/класс с двумя методами: option(key, value) и get(). В первый можно передать имя поля (строка) и любое значение для него. Таким образом мы "набираем" себе объект с нужными полями. Причем вызовы метода option можно объединять в цепочку (чейнить). Метод get возвращает этот "собранный" объект.


declare const config: Chainable

const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()

interface Result {
foo: number
name: string
bar: { value: string }
}


Задача: типизировать класс Chainable.

Важный момент: нельзя два раза передать в option одно и то же свойство.

Решение

Тут нам нужно решить две задачи:
- сохранять переданные ключи и значения, чтобы набрать нужный тип и вернуть его в get - для этого сделаем Chainable дженериком и будем просто дописывать новые данные
- запретить передавать один ключ дважды - для этого будем проверять, если ли у нас уже такой ключ и возвращать never если нет


type Chainable<T = {}> = {
option: <K extends string, V>(
key: K extends keyof T ? never : K,
value: V
) => Chainable<Omit<T, K> & Record<K, V>>;
get: () => T;
}


#medium
👍2
React Query за 10 минут

Видео (рус): https://www.youtube.com/watch?v=4-SOv7eTfoQ

Действительно хорошее введение в React Query. За 11 минут пишем простое React-приложение, которое демонстрирует основные возможности и концепции этой библиотеки. Это будет табличка с данными, к которой затем еще прикрутится пагинация и форма создания нового элемента.

Видео объясняет, как:

- получить данные через useQuery(key, fetchFn), в том числе как сделать запрос с параметрами (например, с номером текущей страницы)
- как получить состояние запроса (isLoading, error)
- как работает кеширование и как оно помогает улучшить пользовательский опыт
- зачем React Query делает фоновые запросы
- как обновить данные на сервере через useMutation(mutationFn, config)
- как принудительно сбросить кеш запроса (инвалидация кеша)

React Query умеет все то же самое, что и RTK Query, но работает по-другому: в форме хуков, без общего стора. С RTK Query мы выносили все, что связано с api в отдельный слайс, в отдельный файл и работали с этими данными отдельно. React Query же работает как хук, внутри компонента и предоставляет все необходимое на месте.

По первому впечатлению: React Query попроще, полегче, поприятнее (обожаю хуки), чем RTK Query, но возможно не потянет более сложные задачи, код станет запутанным. RTK Query тесно работает со стором и это дает больше возможностей.

#управлениесостоянием
👍4
Forwarded from Cat in Web
Решаем задачки

Если интересно порешать задачки, то заходите 🤓:

- TypeScript Challenge
- Leetcode Challenge

#challenges #реклама
🔥2
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
👍1