React Junior
207 subscribers
37 photos
462 links
Изучение React с нуля
加入频道
Redux Essentials 20. RTK Query. Ручная работа с эндпоинтами

Продолжаем переписывать всю API-часть приложения с помощью RTK Query. На очереди пользователи.

Проект: https://codesandbox.io/s/redux-essentials-rtk-query-3-react-junior-mxklq3?file=/src/features/users/usersSlice.js

Создаем новый query-эндпоинт getUsers.

Ручной вызов эндпоинта

Сейчас список пользователей запрашивается в файле index.js, вне компонентов приложения, так как он нужен постоянно. RTK Query тоже позволяет так делать.

Посмотрим на apiSlice в консоли. У него много разных свойств, некоторыми из которых мы уже пользовались (apiSlice.reducer и apiSlice.middlware). Помимо прочего там есть поле endpoints, и у каждого эндпоинта есть метод initiate. Это функция-триггер, которая запускает выполнение запроса.

Таким образом, есть возможность выполнить запрос "вручную", не прибегая к хукам.

store.dispatch(apiSlice.endpoints.getUsers.initiate())

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

// подписка
const result = dispatch(api.endpoints.getPosts.initiate())
// отписка
result.unsubscribe()

Создание селектора

Теперь нужно сделать так, чтобы компоненты получали данные из apiSlice. Сейчас селекторы создаются в usersSlice, причем там мы используем entityAdapter, чтобы управлять коллекцией пользователей.

Документация предлагает временно отказаться от адаптера. Пока просто заменим существующие селекторы на новые, которые получают данные из apiSlice. Для получения данных нужен метод эндпоинта select.

export const selectUsersResult = apiSlice.endpoints.getUsers.select()

Функция select создает мемоизированный селектор. В качестве аргумента она может принимать cache key, который должен совпадать с ключом, переданным в функцию initiate (при ручном вызове) или в хук эндпоинта. Разберемся в этом позже, нам нужен весь список пользователей, так что пока можно обойтись без ключа.

Создадим и экспортируем также селекторы getUsers и getUserById, которые уже существуют, чтобы компоненты могли продолжать ими пользоваться.

Кроме того, удаляем usersReducer из configureStore.

#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
👍2
Redux Essentials 21. RTK Query. Разделение API-слайса

Проект: https://codesandbox.io/s/redux-essentials-rtk-query-4-react-junior-1h2or8?file=/src/features/users/usersSlice.js

apiSlice уже сильно разросся, пора его разделить.

RTK Query предоставляет метод injectEndpoints. С его помощью мы можем вынести всю работу с пользователями в отдельный файл - в usersSlice.

1. Вызываем метод api.injectEndpoints и передаем ему набор эндпоинтов.
2. Метод вернет обновленный объект extendedApiSlice - это тот же самый apiSlice, эндпоинты добавлены через мутацию.
3. Далее мы можем использовать extendedApiSlice вместо apiSlice для ясности.
4. Удаляем getUsers из apiSlice.
5. Отдельно экспортируем эндпоинт: extendedApiSlice.endpoints.getUsers, чтобы было удобнее инициировать его в файле index.js.

#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
👍1
Redux Essentials 22. RTK Query + EntityAdapter

Возвращаемся к EntityAdapter в userSlice. Все оказалось очень просто: при получении данных запроса в transformResponse просто вызываем метод usersAdapter.setAll, чтобы сохранить в нужном виде.

https://codesandbox.io/s/redux-essentials-rtk-query-5-react-junior-b5ebze?file=/src/features/users/usersSlice.js

const usersAdapter = createEntityAdapter();
const initialState = usersAdapter.getInitialState();

getUsers: builder.query({
// ...
transformResponse: function (responseData) {
return usersAdapter.setAll(initialState, responseData);
}
})

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

// получение данных запроса из apiSlice
export const selectUsersResult = getUsersEndpoint.select();

// извлечение данных из запроса, если он выполнен
// результат работы entity adapter ({ids, entities}) или undefined
const selectUsersData = createSelector(
selectUsersResult,
function (usersResult) {
return usersResult.data;
}
);

export const {
selectAll: getUsers,
selectById: getUserById
} = usersAdapter.getSelectors(
function(state) {
return selectUsersData(state) ?? initialState;
}
);

#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
👍2
RTK Query. Query hook: параметры

В хук запроса, сгенерированный для эндпоинта, можно передать параметры:

- queryArg (параметр для генерации урла запроса, передается в функцию query)
- queryOptions (объект с настройками).

Настройки есть следующие:

- skip - пропустить выполнение запроса для текущего рендера.
- pollingInterval - для автоматического перезапроса данных.
- selectFromResult - позволяет изменить возвращаемое хуком значение (оптимизировано для рендера).
- refetchOnMountOrArgChange
- refetchOnFocus - refetch при возвращении фокуса на вкладку браузера.
- refetchOnReconnect - refetch при восстановлении сетевого соединения, если оно было потеряно.

#обменданными #rtkquery #документация
👍1
Redux Essentials 23. Выбор данных из запроса

Теперь займемся страницей пользователя, на которой выводятся отфильтрованные по автору посты.

https://codesandbox.io/s/redux-essentials-rtk-query-6-react-junior-kfzw2t?file=/src/features/users/UserPage.js

Воспользуемся хуком useGetPostsQuery, чтобы получить полный список постов и настройкой selectFromResult (параметры хука запроса), чтобы отфильтровать статьи по автору.

const { postsForUser } = useGetPostsQuery(undefined, {
selectFromResult: function(result) {
return {
...result,
postsForUser: (result.data || []).filter(function(post) {
return post.user === userId
})
}
}
});


При любом изменении state массив postsForUser будет пересоздаваться, вызывая перерендер компонента, поэтому нужно обернуть извлечение данных в мемоизированный селектор.

#redux #управлениесостоянием #rtkquery #обменданными #документация #примерыкода
👍1
Трансформация данных запроса: 3 подхода

Мы получаем данные из API в одном виде, но для использования может потребоваться их изменение. Сделать это можно в трех местах:

1. Сразу после получения. Преобразуем ответ с помощью transformResponse и сохраняем в кеше уже измененные данные. Имеет смысл, если новый формат подходит всем потребителям. Можно сочетать с entity adapter.

2. В компоненте. Получаем данные, сохраненные в кеше, и извлекаем из них то, что нужно. Рекомендуется использовать useMemo для избежания ненужных перерендерингов. Полезно, если одному компоненту нужны данные в другом формате.

3. В хуке запроса. Отбираем нужные данные с помощью опции selectFromResult. Полезно, если компоненту нужна только часть данных.

#redux #rtkquery #обменданными #паттерны
👍2
Redux Essentials 24. RTK Query. Оптимистичные обновления

Переходим к реакциям на статьи.

https://codesandbox.io/s/redux-essentials-rtk-query-7-react-junior-uzedjr?file=/src/features/api/apiSlice.js

Добавляем эндпоинт-мутацию addReaction.

После того, как пользователь поставил реакцию, нужно отправить запрос и обновить данные статьи, чтобы показать изменения.

addReaction: builder.mutation({
query: function({ postId, reaction }) {},
invalidatesTags: function(result, error, arg) {
return [
{ type: 'Post', id: arg.postId }
];
}
})


Но ради такого мелкого изменения не хочется по новой запрашивать целый список. Для таких ситуаций можно воспользоваться техникой "оптимистичного обновления".

По умолчанию мы предполагаем, что обновление пройдет успешно, и хотим сразу показать пользователю изменение счетчика. Можно подправить уже сохраненные данные, а если запрос вдруг не выполнится, просто отменить эту правку.

Для эндпоинта можно указать метод onQueryStarted. Это функция, которая при вызове получит два параметра - arg и thunkApi, в котором нас интересуют два метода - dispatch для отправки экшена и queryFulfilled - промис отправленного запроса.

Таким образом, запрос отправляется, но мы не хотим ждать его результата, а хотим внести изменения немедленно. У apiSlice есть свойство utils, а в нем метод updateQueryData. Он позволяет изменить ранее сохраненные данные. Для этого нужно указать название эндпоинта, ключ кеширования (если нужно) и собственно функцию для обновления.

const patchThunk = apiSlice.util.updateQueryData('getPosts', undefined, function(draft) {
// ... вносим изменения в данные draft
// данные можно мутировать
});


Теперь нужно только задиспатчить этот thunk, чтобы изменения отобразились в компоненте.

const patchResult = thunkApi.dispatch(patchThunk);


Если же запрос на сервер (queryFulfilled) не выполнится, патч можно отменить, вызвав метод patchResult.undo().

Осталось только внести изменения в компонент ReactionButton, чтобы он использовал хук новой мутации.

#redux #управлениесостоянием #rtkquery #обменданными #документация #примерыкода
👍1
Redux Essentials 25. RTK Query. Потоковое обновление кеша

И наконец, осталось только переделать уведомления.

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

https://codesandbox.io/s/redux-essentials-rtk-query-7-react-junior-uzedjr?file=/src/features/notifications/notificationsSlice.js

Для демонстрационных целей напишем фейковый сокет-сервер, который будет через некоторый интервал генерировать новые уведомления.

Создание эндпоинта

Создадим новый эндпоинт getNotifications, который будет получать исходный список. Для удобства вынесем его в файл notificationsSlice.js и подключим с помощью метода apiSlice.injectEndpoints.

Экспортируем хук useGetNotificatinsQuery и используем его в компоненте NotificationsList. Тут ничего нового.

Подписка на новые уведомления

У эндпоинта есть метод жизненного цикла onCacheEntryAdded. Он вызывается в тот момент, когда эндпоинт добавляется в кеш, то есть для него "бронируется" там место (данные при этом еще не получены).

Внутри этого метода мы можем подписаться на сокет-канал.

Метод принимает два параметра: arg и thunkApi. В последнем есть несколько полезных методов и данных:

const {
updateCachedData,
cacheDataLoaded,
cacheEntryRemoved,
dispatch
} = thunkApi;


Последовательность действий:

- ждем, пока выполнится основной запрос (cacheDataLoaded)
- подписываемся на сокет-канал, при получении новых уведомлений добавляем их к кешированным ранее данным (updateCachedData)
- отслеживаем удаление кеша (cacheEntryRemoved), закрываем канал

Непрочитанные уведомления

Теперь список уведомлений хранится в apiSlice. Но нам еще нужно хранить их состояние (непрочитанные/прочитанные). Используем для этого уже существующий notificationsSlice, только вместо самих уведомлений поместим туда данные об их состоянии.

При добавлении новых уведомлений в кеш, нужно добавлять их и в notificationsSlice. Добавление происходит при выполнении базового запроса (экшен extendedApi.endpoints.getNotifications.matchFulfilled), а также при получении новых данных через сокет. Для второго случая создадим отдельный экшен notificationsReceived (используем утилитарную функцию createAction).

В extraReducers отслеживаем нужные экшены (builder.addMatcher) и добавляем новые элементы в список.

Осталось только в компоненте NotificationsList получить состояние уведомлений и отметить их как нужно.

#redux #управлениесостоянием #rtkquery #обменданными #документация #примерыкода
👍1
RTK Query. Резюме

Небольшое подведение итогов по RTK Query после прохождения руководства.

RTK Query работает поверх Redux Toolkit, используя его основные концепции.

Предполагается, что вся работа с API в проекте будет собрана в отдельный слайс, созданный с помощью функции createApi. Тут можно настроить, как именно должен выполняться запрос (`baseQuery`), а также указать набор эндпоинтов (`endpoints`).

Эндпоинт может быть запросом (query) или мутацией (mutation).

У каждого эндпоинта есть поле query, формирующее урл запроса. Полученный ответ можно обработать в методе transformResponse. Кроме того, можно форматировать данные прямо в компоненте (три подхода к форматированию данных)

При необходимости, часть эндпоинтов можно перенести в отдельный файл и подключить к слайсу через apiSlice.injectEndpoints (разделение эндпоинтов).

Все полученные данные хранятся в кеше. Если на эндпоинт нет подписок, его кеш через некоторое время сбрасывается.

Каждый эндпоинт можно пометить тегом (строка или объект), который можно инвалидировать (принудительно сбросить кеш и вызвать перезапуск запроса) в другом эндпоинте (уточненные теги).

Для эндпоинта генерируется хук (хуки запросов https://yangx.top/react_junior/342), который можно использовать в компонентах. Результат работы хука помимо данных содержит информацию о статусе запроса (`isFetching`, isLoading, `isError`). Кроме того, хук может принимать параметры для автоматического перезапроса данных и для выборки нужных данных из кеша (`selectFromResult`) (параметры хука запроса).

Можно работать с эндпоинтами и без хуков, они предоставляют ряд методов (`initiate()`, `select()`) (ручная работа с эндпоинтами).

RTK Query поддерживает оптимистичные обновления кеша, для этого используем метод жизненного цикла onQueryStarted и метод обновления apiSlice.util.updateQueryData.

Кроме того, возможно обновление кеша "по необходимости", например, при подписке на сокет-канал (потоковое обновление кеша). Для этого используем метод жизненного цикла onCacheEntryAdded.

#redux #rtkquery #обменданными
👍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