Redux Essentials 12. Уведомления
Добавляем функционал уведомлений - страницу со списком и возможность обновить список (получить "новые" уведомления), нажав на кнопку.
https://codesandbox.io/s/redux-essentials-notifications-react-junior-qmk9tf
Так как это просто демка Redux, уведомления будут довольно куцые.
API
В фейковый апи добавлен метод для генерации рандомных сообщений. Он получает время последнего сообщения и создает несколько "новых" сообщений.
Слайс
Добавляем новую фичу
Этот фрагмент состояния представляет собой простой массив уведомлений, никакие статусы тут отслеживаться не будут.
Экшен всего один -
Есть два селектора -
Thunk
Добавляем также thunk creator
Из интересного - функция принимает два параметра - первый пользовательский, если в thunk нужно передать какие-то данные, второй -
Нужно также добавить редьюсер для экшена
Список
Добавляем компонент
Кроме того, добавляем в
#redux #управлениесостоянием #документация #примерыкода
Добавляем функционал уведомлений - страницу со списком и возможность обновить список (получить "новые" уведомления), нажав на кнопку.
https://codesandbox.io/s/redux-essentials-notifications-react-junior-qmk9tf
Так как это просто демка Redux, уведомления будут довольно куцые.
API
В фейковый апи добавлен метод для генерации рандомных сообщений. Он получает время последнего сообщения и создает несколько "новых" сообщений.
Слайс
Добавляем новую фичу
features/notifications
и создаем новый слайс, который нужно будет добавить в метод configureStore
в app/store.js
.Этот фрагмент состояния представляет собой простой массив уведомлений, никакие статусы тут отслеживаться не будут.
Экшен всего один -
allNotificationsRead
. У каждого уведомления будет статус "прочитано/не прочитано", чтобы можно было подсвечивать новые. Этот экшен будет помечать все обновления прочитанными.Есть два селектора -
getNotifications
и getUnreadNotificationsCount
.Thunk
Добавляем также thunk creator
fetchNotifications
, который будет обращаться к "серверу" и получать новые уведомления.Из интересного - функция принимает два параметра - первый пользовательский, если в thunk нужно передать какие-то данные, второй -
thunkAPI
- объект с методами для работы с хранилищем (getStore
, dispatch
).Нужно также добавить редьюсер для экшена
fetchNotifications.fulfilled
в секцию extraReducers
. Раньше мы всегда оформляли ее как функцию и добавляли все обработчики через объект builder
. Но тут используется другой синтаксис - объект, поля которого представляют собой имена экшенов. Так тоже можно.extraReducers: {Выглядит более понятно, чем вариант с функцией, но тот обычно удобнее.
[fetchNotifications.fulfilled]: function(state, action) {}
}
Список
Добавляем компонент
NotificationsList
, который будет рендерить список уведомлений, а также помечать все уведомления прочитанными. Для этого используем хук useLayoutEffect.Кроме того, добавляем в
App.js
новый роут notifications
, а в шапку - новую ссылку. Плюс в шапке еще появилась кнопка для обновления списка (получения новых уведомлений). Если ее нажать, появится количество непрочитанных сообщений, которое обнулится при переходе на страницу списка.#redux #управлениесостоянием #документация #примерыкода
CodeSandbox
Redux Essentials. Notifications. React Junior - CodeSandbox
Redux Essentials. Notifications. React Junior by furrycat.web using @reduxjs/toolkit, date-fns, react, react-dom, react-redux, react-router-dom, react-scripts, styled-components
Redux Essentials 13. Мемоизация селекторов
В текущей версии проекта (https://qmk9tf.csb.app/users/1) перейдем на страницу пользователя, откроем в панели разработчика вкладку Profiler (из React DevTools) и нажмем на кнопку записи.
Теперь обновим список уведомлений (кнопка в шапке) и после этого остановим запись.
Увидим, что помимо шапки (в которой появилось кол-во непрочитанных сообщений), перерендерилась еще и страница
Впрочем, мы уже знаем, в чем дело - в селекторе
Чтобы решить проблему, используем метод createSelector (из библиотеки reselect, берем из пакета @reduxjs/toolkit).
У него будут два входных селектора - уже существующий
Обновленный код проекта: https://codesandbox.io/s/redux-essentials-memoized-selectors-react-junior-e8u8e6?file=/src/features/posts/postsSlice.js
Сам проект: https://e8u8e6.csb.app/users/1
#redux #управлениесостоянием #документация #примерыкода
В текущей версии проекта (https://qmk9tf.csb.app/users/1) перейдем на страницу пользователя, откроем в панели разработчика вкладку Profiler (из React DevTools) и нажмем на кнопку записи.
Теперь обновим список уведомлений (кнопка в шапке) и после этого остановим запись.
Увидим, что помимо шапки (в которой появилось кол-во непрочитанных сообщений), перерендерилась еще и страница
UserPage
. Казалось бы, с чего это вдруг, в этом компоненте изменений не было. Впрочем, мы уже знаем, в чем дело - в селекторе
getUserPosts
, который возвращает новый массив, так как используем метод filter
.Чтобы решить проблему, используем метод createSelector (из библиотеки reselect, берем из пакета @reduxjs/toolkit).
У него будут два входных селектора - уже существующий
getPosts
, который возвращает массив всех статей, и еще один, который возвращает только id пользователя. Выходной селектор уже фильтрует полученный массив. Таким образом, если общий список не изменится, перерендера не будет.Обновленный код проекта: https://codesandbox.io/s/redux-essentials-memoized-selectors-react-junior-e8u8e6?file=/src/features/posts/postsSlice.js
Сам проект: https://e8u8e6.csb.app/users/1
#redux #управлениесостоянием #документация #примерыкода
👍1
Redux Essentials 14. Мемоизация компонентов
Еще одна проблема производительности в текущей версии проекта (https://qmk9tf.csb.app/).
Если на главной странице (со списком постов) поставить реакцию под одной из статей, то перерендерятся ВСЕ статьи, а не только та, которая изменилась.
Дело в том, что при изменении одного объекта статьи меняется весь массив статей. Из-за этого перерендеривается весь компонент
Есть несколько способов решить эту проблему.
Первый - обернуть компонент
Обновленный код проекта: https://codesandbox.io/s/redux-essentials-memoized-selectors-react-junior-e8u8e6?file=/src/features/posts/PostsList.js
Сам проект: https://e8u8e6.csb.app/
Второй - переписать селектор
Третий - переделать состояние в целом, чтобы в нем хранился массив айдишников, который изменяется только при изменении набора постов. Другими словами, нормализовать состояние. Этим займемся в следующий раз.
#redux #управлениесостоянием #документация #примерыкода
Еще одна проблема производительности в текущей версии проекта (https://qmk9tf.csb.app/).
Если на главной странице (со списком постов) поставить реакцию под одной из статей, то перерендерятся ВСЕ статьи, а не только та, которая изменилась.
Дело в том, что при изменении одного объекта статьи меняется весь массив статей. Из-за этого перерендеривается весь компонент
PostsList
, а с ним и все его потомки.Есть несколько способов решить эту проблему.
Первый - обернуть компонент
PostExcerpt
в React.memo
. Тогда он не будет перерендериваться при перерендере родителя (если у него не поменяются пропсы).Обновленный код проекта: https://codesandbox.io/s/redux-essentials-memoized-selectors-react-junior-e8u8e6?file=/src/features/posts/PostsList.js
Сам проект: https://e8u8e6.csb.app/
Второй - переписать селектор
getPosts
, чтобы он возвращал только айдишники статей, и мемоизировать его с помощью shallowEqual. А в компоненте PostExcerpt
получать только id
и по нему находить нужный пост.Третий - переделать состояние в целом, чтобы в нем хранился массив айдишников, который изменяется только при изменении набора постов. Другими словами, нормализовать состояние. Этим займемся в следующий раз.
#redux #управлениесостоянием #документация #примерыкода
👍1
Плохие привычки Middle React разработчиков
Статья (англ.): https://dev.to/srmagura/bad-habits-of-mid-level-react-developers-b41
Автор статьи перечисляет частые ошибки и плохие практики, которые видит, делая ревью чужого кода. В комментариях согласны не со всем :)
Итак, React-разработчики часто:
👎 Дублируют состояние
Плохо выглядит и может привести к рассинхрону.
👎 Не используют редьюсеры
Для работы с более-менее сложным состоянием редьюсеры (useReducer, redux) предпочтительнее простого useState. В частности, автор рекомендует работать через редьюсер с любым массивом, который можно редактировать.
👎 Не пишут юнит-тесты
Речь в статье идет о тестах, написание которых не занимает много времени, но приносит большую пользу, например, о тестах для редьюсеров.
👎 Недостаточно используют React.memo, useMemo и useCallback
Что приводит к проблемам с производительностью.
👎 Неправильно используют useEffect
Эффект запускается либо слишком часто, либо недостаточно часто. Из полезного: автор рекомендует помещать функции, которые задействованы в эффекте в useRef, чтобы сохранять их идентичность.
👎 Забывают про юзабилити
Не связано напрямую с React и вообще много где встречается.
👎 Не стремятся к совершенствованию знаний в CSS и веб-дизайне
Добавка к предыдущему пункту.
В заключение автор дает несколько советов-"хороших практик":
👍 Использовать исключительно TypeScript
👍 Использовать библиотеку для запросов (data-fetching)
👍 Использовать серверный рендеринг, только если он вам действительно нужен
👍 Использовать модульный CSS и связывать стили с компонентами
#ссылки #паттерны
Статья (англ.): https://dev.to/srmagura/bad-habits-of-mid-level-react-developers-b41
Автор статьи перечисляет частые ошибки и плохие практики, которые видит, делая ревью чужого кода. В комментариях согласны не со всем :)
Итак, React-разработчики часто:
👎 Дублируют состояние
Плохо выглядит и может привести к рассинхрону.
👎 Не используют редьюсеры
Для работы с более-менее сложным состоянием редьюсеры (useReducer, redux) предпочтительнее простого useState. В частности, автор рекомендует работать через редьюсер с любым массивом, который можно редактировать.
👎 Не пишут юнит-тесты
Речь в статье идет о тестах, написание которых не занимает много времени, но приносит большую пользу, например, о тестах для редьюсеров.
👎 Недостаточно используют React.memo, useMemo и useCallback
Что приводит к проблемам с производительностью.
👎 Неправильно используют useEffect
Эффект запускается либо слишком часто, либо недостаточно часто. Из полезного: автор рекомендует помещать функции, которые задействованы в эффекте в useRef, чтобы сохранять их идентичность.
👎 Забывают про юзабилити
Не связано напрямую с React и вообще много где встречается.
👎 Не стремятся к совершенствованию знаний в CSS и веб-дизайне
Добавка к предыдущему пункту.
В заключение автор дает несколько советов-"хороших практик":
👍 Использовать исключительно TypeScript
👍 Использовать библиотеку для запросов (data-fetching)
👍 Использовать серверный рендеринг, только если он вам действительно нужен
👍 Использовать модульный CSS и связывать стили с компонентами
#ссылки #паттерны
DEV Community
Bad Habits of Mid-Level React Developers
If you're a mid-level React developer looking to become an advanced React developer, this post is for...
👍3❤1
Redux Essentials 15. Нормализация состояния
Преимущества хранения состояния в нормализованном виде мы уже обсуждали. Помимо прочего это еще и улучшает производительность. Список элементов представлен в виде массива их идентификаторов, поэтому если состояние одного из элементов меняется, на сам список это не влияет.
https://codesandbox.io/s/redux-essentials-normalized-state-react-junior-q2x3te?file=/src/App.js
Для создания нормализованного состояния и управления им используется метод createEntityAdapter.
PostsSlice
Создаем адаптер
Для получения начального состояния - метод
Теперь нужно внести изменения в редьюсеры и селекторы. Данные постов теперь хранятся в
Компоненты
Изменилась концепция получения данных в компонентах. Компонент
Таким образом, компонент
Теперь можно повторить эксперимент. Открываем проект (https://q2x3te.csb.app/), включаем запись, ставим лайк одному из постов, и видим, что перерендерился только он.
Пользователи и уведомления
По аналогии вносим изменения в
#redux #управлениесостоянием #документация #примерыкода
Преимущества хранения состояния в нормализованном виде мы уже обсуждали. Помимо прочего это еще и улучшает производительность. Список элементов представлен в виде массива их идентификаторов, поэтому если состояние одного из элементов меняется, на сам список это не влияет.
https://codesandbox.io/s/redux-essentials-normalized-state-react-junior-q2x3te?file=/src/App.js
Для создания нормализованного состояния и управления им используется метод createEntityAdapter.
PostsSlice
Создаем адаптер
postsAdapter
. Метод createEntityAdapter
может принимать ряд настроек, например, sortComparer
- функцию для сортировки массива. Сейчас у нас сортировка (по дате) происходит прямо в компоненте PostsList.Для получения начального состояния - метод
getInitialState
.Теперь нужно внести изменения в редьюсеры и селекторы. Данные постов теперь хранятся в
state.entities
(объект), а все операции совершаются через методы адаптера.Компоненты
Изменилась концепция получения данных в компонентах. Компонент
PostsList
теперь должен получать только список идентификаторов, это защитит нас от перерендеринга при изменении отдельных статей.Таким образом, компонент
PostExcerpt
теперь должен самостоятельно получать данные статьи, чтобы отобразить их.Теперь можно повторить эксперимент. Открываем проект (https://q2x3te.csb.app/), включаем запись, ставим лайк одному из постов, и видим, что перерендерился только он.
Пользователи и уведомления
По аналогии вносим изменения в
features/users
и features/notifications
: создаем адаптеры, изменяем редьюсеры и селекторы.#redux #управлениесостоянием #документация #примерыкода
Telegram
React Junior
Нормализация состояния
Во всех предыдущих примерах элементы хранились в store в виде массива. Но в больших проектах со сложным состоянием разумно использовать другую структуру данных - словарь с доступом по идентификатору (обычный JavaScript-объект).
Это…
Во всех предыдущих примерах элементы хранились в store в виде массива. Но в больших проектах со сложным состоянием разумно использовать другую структуру данных - словарь с доступом по идентификатору (обычный JavaScript-объект).
Это…
👍3
Композиция компонентов в React: как правильно?
Статья (англ.): https://www.developerway.com/posts/components-composition-how-to-get-it-right
Секрет создания масштабируемых приложений на React - это извлечение "правильных" компонентов в правильное время. Хороший компонент - это понятный компонент, который не делает ничего лишнего.
Хороший компонент обычно можно описать короткой фразой в духе
"компонент, который что-то реализует" или "компонент, который объединяет несколько компонентов". Старайтесь не смешивать эти сущности.
Компоненты
Автор выделяет два основных типа компонентов: "простые" компоненты и "компоненты-контейнеры". Основное различие между ними в том, что компоненты-контейнеры используют проп
Принципы разработки
Основные принципы, которых автор советует придерживаться, чтобы из чтобы избежать "переусложнения" кода:
- всегда начинайте реализацию "сверху"
- извлекайте компоненты только при реальной необходимости
- начинайте с "простых" компонентов, используйте техники композиции только при реальной необходимости
Когда наступает подходящий момент для выделения отдельных компонентов?
- если компонент слишком большой, так что его неудобно читать.
- если компонент занимается "не своим делом", содержит состояние, которое к нему не относится. В статье приведен хороший пример с кнопкой, клик по которой открывает модальное окно.
Контейнерные компоненты
Извлекайте "контейнерные компоненты", если появляется необходимость переиспользовать какую-то логику (визуал или поведение) для элементов, которые должны оставаться под контролем пользователя. То есть если эта логика не связана с элементами напрямую, например, сворачивающиеся секции меню.
Кроме того, "контейнеры" - это прекрасное решение проблем с производительностью, так как Компоненты, вставленные через props.children, не обновляются при рендере родительского компонента.
#ссылки #компоненты #паттерны
Статья (англ.): https://www.developerway.com/posts/components-composition-how-to-get-it-right
Секрет создания масштабируемых приложений на React - это извлечение "правильных" компонентов в правильное время. Хороший компонент - это понятный компонент, который не делает ничего лишнего.
Хороший компонент обычно можно описать короткой фразой в духе
"компонент, который что-то реализует" или "компонент, который объединяет несколько компонентов". Старайтесь не смешивать эти сущности.
Компоненты
Автор выделяет два основных типа компонентов: "простые" компоненты и "компоненты-контейнеры". Основное различие между ними в том, что компоненты-контейнеры используют проп
children
, то есть в них может быть вложен совершенно посторонний код. Принципы разработки
Основные принципы, которых автор советует придерживаться, чтобы из чтобы избежать "переусложнения" кода:
- всегда начинайте реализацию "сверху"
- извлекайте компоненты только при реальной необходимости
- начинайте с "простых" компонентов, используйте техники композиции только при реальной необходимости
Когда наступает подходящий момент для выделения отдельных компонентов?
- если компонент слишком большой, так что его неудобно читать.
- если компонент занимается "не своим делом", содержит состояние, которое к нему не относится. В статье приведен хороший пример с кнопкой, клик по которой открывает модальное окно.
Контейнерные компоненты
Извлекайте "контейнерные компоненты", если появляется необходимость переиспользовать какую-то логику (визуал или поведение) для элементов, которые должны оставаться под контролем пользователя. То есть если эта логика не связана с элементами напрямую, например, сворачивающиеся секции меню.
Кроме того, "контейнеры" - это прекрасное решение проблем с производительностью, так как Компоненты, вставленные через props.children, не обновляются при рендере родительского компонента.
#ссылки #компоненты #паттерны
Developerway
React components composition: how to get it right
What is components composition? How do you know when to start splitting a big component into smaller pieces and how to compose them properly? What makes a good component?
👍3
Redux Essentials 16. RTK Query. Создание слайса
RTK Query - это еще один уровень абстракции над абстракциями Redux, позволяющий инкапсулировать работу с API: методы получения/обновления/удаления данных, состояние выполнения запроса, синхронизация состояния на клиенте и на сервере, оптимистичные обновления клиентского стейта, кеширование и т.д.
При этом все, что связано с API, выносится в отдельный слайс. Если у нас есть в приложении несколько разных запросов к одному и тому же серверу (работа со статьями, уведомлениями) - все это будет в отдельном слайсе, и методы, и сами данные.
RTK Query предоставляет две основные функции:
-
-
Они находятся в пакете
Есть еще
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Создание и добавление слайса
Слайс для API создаем в файле
Важно правильно подключить новый слайс в методе
Сам сгенерированный редьюсер находится в
Кроме того, нужно добавить миддлвар
#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
RTK Query - это еще один уровень абстракции над абстракциями Redux, позволяющий инкапсулировать работу с API: методы получения/обновления/удаления данных, состояние выполнения запроса, синхронизация состояния на клиенте и на сервере, оптимистичные обновления клиентского стейта, кеширование и т.д.
При этом все, что связано с API, выносится в отдельный слайс. Если у нас есть в приложении несколько разных запросов к одному и тому же серверу (работа со статьями, уведомлениями) - все это будет в отдельном слайсе, и методы, и сами данные.
RTK Query предоставляет две основные функции:
-
createApi
для создания слайса;-
fetchBaseQuery
- небольшая утилитарная обертка вокруг метода fetch
Они находятся в пакете
@reduxjs/toolkit/query
- это базовая версия библиотеки.Есть еще
@reduxjs/toolkit/query/react
- те же самые функции специально для React. Они автоматически генерируют хуки на базе созданных методов. Мы будем использовать именно этот пакет.Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Создание и добавление слайса
Слайс для API создаем в файле
features/api/apiSlice.js
. export const apiSlice = createApi({Помимо прочих настроек метод
reducerPath: 'api',
// ...
})
createApi
принимает поле reducerPath
, в котором нужно указать, где именно в общем state
будут лежать данные, полученные из API. По умолчанию значение этого поля api
- то есть данные нужно будет извлекать из state.api
.Важно правильно подключить новый слайс в методе
configureStore
. Чтобы не ошибиться, используем в качестве названия поля свойство apiSlice.reducerPath
.Сам сгенерированный редьюсер находится в
apiSlice.reducer
.Кроме того, нужно добавить миддлвар
apiSlice.middlewar
для кеширования данных. Сделать это можно в методе configureStore
в настройке middleware
. Она принимает функцию, которой в качестве аргумента передается метод getDefaultMiddleware
. Вернуть нужно обновленный массив миддлваров, поэтому вызываем этот метод и добавляем в него новый миддлвар.#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
👍4
Redux Essentials 16. RTK Query. Выполнение запросов
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
baseQuery
Основная (обязательная) настройка в методе
В качестве такой функции можно использовать встроенную
Внутри одного API-слайса предполагается, что все запросы осуществляются к одному серверу (это необязательно). Так что здесь можно установить базовые настройки запроса, например,
Так как в моем проекте реального API нет, придется использовать кастомную функцию
Вместо этого можно, например, реализовать запросы с помощью библиотеки axios.
#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
baseQuery
Основная (обязательная) настройка в методе
createApi
- это baseQuery
. Это функция, которая непосредственно выполняет запросы. В качестве такой функции можно использовать встроенную
fetchBaseQuery
- обертку над стандартным методом fetch
.Внутри одного API-слайса предполагается, что все запросы осуществляются к одному серверу (это необязательно). Так что здесь можно установить базовые настройки запроса, например,
baseUrl
- фрагмент урла, с которого начинаются все эндпоинты.const apiSlice = createApi({Но можно использовать и собственную функцию для выполнения запросов. Важно, чтобы она возвращала ответ в нужном формате. Полученные данные должны находиться в поле
// ...
baseQuery: fetchBaseQuery({ baseUrl: '/fakeApi' })
})
data
, а ошибка, если она есть, в поле error
. Так как в моем проекте реального API нет, придется использовать кастомную функцию
customFetchBaseQuery
, которая вместо настоящих запросов просто обращается к модулю fakeApi
.Вместо этого можно, например, реализовать запросы с помощью библиотеки axios.
#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
CodeSandbox
Redux Essentials. RTK Query. React Junior - CodeSandbox
Redux Essentials. RTK Query. React Junior by furrycat.web using @reduxjs/toolkit, date-fns, react, react-dom, react-redux, react-router-dom, react-scripts, styled-components
👍2
Redux Essentials 16. RTK Query. Эндпоинты
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Теперь самое интересное - добавление эндпоинтов для реальной работы с данными.
Поле
У билдера есть метод
-
-
Начнем с добавления эндпоинта
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Теперь самое интересное - добавление эндпоинтов для реальной работы с данными.
Поле
endpoints
в настройках метода createApi
- это функция, которая в качестве аргумента принимает builder
, а вернуть должна коллекцию (объект) эндпоинтов. У билдера есть метод
query
, который принимает объект настроек запроса. Настроек очень много, но можно пока обойтись основными:-
query
- функция, которая возвращает урл запроса. Этот урл будет присоединен к baseUrl
, если он был указан ранее.-
transformResponse
- функция для обработки ответа, если он не соответствует ожидаемому формату.Начнем с добавления эндпоинта
getPosts
для получения списка постов.const apiSlice = createApi({#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
// ...
endpoints: function(builder) {
return {
getPosts: builder.query({
query: function() { return "/posts" },
transformResponse: function(response) { return response.data }
})
}
}
})
CodeSandbox
Redux Essentials. RTK Query. React Junior - CodeSandbox
Redux Essentials. RTK Query. React Junior by furrycat.web using @reduxjs/toolkit, date-fns, react, react-dom, react-redux, react-router-dom, react-scripts, styled-components
👍2
Redux Essentials 16. RTK Query. Использование данных
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/posts/PostsList.js
У нас есть функция для выполнения запросов, описанный эндпоинт, осталось только получить данные (список статей) в компоненте
Хуки запросов
RTK Query (в React-версии - @reduxjs/toolkit/query/react) автоматически генерирует хук для каждого созданного эндпоинта. Название хука формируется по шаблону
👉
Кроме запросов еще есть мутации, но мы до них пока не дошли.
Таким образом, для эндпоинта
Использование хуков в компоненте
При использовании этот хук возвращает массу полезных данных:
-
#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/posts/PostsList.js
У нас есть функция для выполнения запросов, описанный эндпоинт, осталось только получить данные (список статей) в компоненте
PostsList
и отобразить их.Хуки запросов
RTK Query (в React-версии - @reduxjs/toolkit/query/react) автоматически генерирует хук для каждого созданного эндпоинта. Название хука формируется по шаблону
👉
use
+ имя эндпоинта + query
query
добавляется для запросов, созданных через builder.query
. Кроме запросов еще есть мутации, но мы до них пока не дошли.
Таким образом, для эндпоинта
getPosts
у нас есть хук apiSlice.useGetPostsQuery
.Использование хуков в компоненте
При использовании этот хук возвращает массу полезных данных:
-
data
- собственно данные из api
- isLoading
- isSuccess
- isError
- error
С ними можно построить интерфейс компонента для всех состояний запроса.#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
👍3👏1
Redux Essentials 16. RTK Query. Потерянный функционал
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/posts/PostsList.js
Перейдя на новый способ работы с данными через RTK Query, мы потеряли реализованный ранее функционал сортировки статей по дате. Документация обещает позже рассказать, как это делать правильно, а пока ограничимся простой сортировкой прямо в компоненте.
Используем useMemo, чтобы сортировка происходила только при изменении массива
#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
Проект: https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/posts/PostsList.js
Перейдя на новый способ работы с данными через RTK Query, мы потеряли реализованный ранее функционал сортировки статей по дате. Документация обещает позже рассказать, как это делать правильно, а пока ограничимся простой сортировкой прямо в компоненте.
Используем useMemo, чтобы сортировка происходила только при изменении массива
posts
.#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
👍3
Redux Essentials 17. RTK Query. Страница статьи
Так как список постов мигрировал в API-слайс, перестала работать страница отдельного поста.
Чтобы получить данные одной статьи, можно, например, получать весь список с помощью
https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Кеширование запросов
Посмотрим, что там с кешированием: https://lced2j.csb.app/
Открываем главную страницу:
- выполняется экшен
- заглянем в State: в
Теперь переходим на страницу поста:
- снова выполняются экшены
- в
Если теперь вернуться на главную и снова зайти на страницу этой же статьи, мы увидим экшен
#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
Так как список постов мигрировал в API-слайс, перестала работать страница отдельного поста.
Чтобы получить данные одной статьи, можно, например, получать весь список с помощью
useGetPostsQuery
, а затем фильтровать его. Хук даже принимает объект настроек с методом selectFromResult
:const { post, isLoading } = useGetPostsQuery(null, {Но лучше добавить отдельный эндпоинт
selectFromResult: function(result) {
if (result.isLoading) {
return {
isLoading: true
}
}
if (result.isSuccess) {
const post = result.data.find(function(post) {
return post.id === id;
});
return { post }
}
return {
post: null
}
}
})
getPost
для получения одной статьи и использовать хук useGetPostQuery
.https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Кеширование запросов
Посмотрим, что там с кешированием: https://lced2j.csb.app/
Открываем главную страницу:
- выполняется экшен
api/executeQuery/pending
, а затем api/executeQuery/fulfilled
. Это запрашивается список статей.- заглянем в State: в
api.queries
появилось поле getPosts(undefined)
, а в нем данные. Это кеш для запроса к эндпоинту getPosts
без параметров.Теперь переходим на страницу поста:
- снова выполняются экшены
api/executeQuery/pending
и api/executeQuery/fulfilled
. Это запрашиваются данные поста.- в
state.api.queries
появилось новое поле getPost(E2-wMYL5CESZ6hDRMqsf2)
, в котором сохранены результаты запроса.Если теперь вернуться на главную и снова зайти на страницу этой же статьи, мы увидим экшен
api/executeQuery/rejected
. Данные для запроса к этому эндпоинту с этим параметром уже закешированы, поэтому заново запрос не осуществляется.#redux #rtkquery #обменданными #управлениесостоянием #документация #примерыкода
Redux Essentials 18. RTK Query. Обновление данных
Запрашивать данные из API мы умеем, теперь нужно научиться их отправлять. Для этого в RTK Query существуют мутации.
https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Создание нового поста
Добавляем новый эндпоинт
Сгенерированный хук для этого эндпоинта будет называться
Использование хука мутации немного отличается от хука запроса. Он возвращает не один, а два элемента - функцию-триггер, вызов которой запускает хук, и объект с состоянием запроса.
Обновление списка постов
После создания нового поста нужно обновить список - по новой запросить его с сервера.
Можно, например, добавить кнопку, при нажатии на которую список будет обновляться (добавлена в компоненте
Используем для этого функцию
Инвалидация кеша
Заставлять пользователя вручную обновлять список при добавлении поста - неправильно. Лучше сделать это автоматически. Для этого нужно заставить RTK Query сбросить кеш запроса и выполнить его заново. Чтобы показать, какой именно кеш нужно сбросить, используются теги:
#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
Запрашивать данные из API мы умеем, теперь нужно научиться их отправлять. Для этого в RTK Query существуют мутации.
https://codesandbox.io/s/redux-essentials-rtk-query-react-junior-lced2j?file=/src/features/api/apiSlice.js
Создание нового поста
Добавляем новый эндпоинт
addNewPost
для создания новой статьи. Вместо метода builder.query
используем builder.mutation
. Настройка query
будет немного посложнее, вместо простой строки с урлом она возвращает объект с настройками запроса: url
, method
, body
.Сгенерированный хук для этого эндпоинта будет называться
useAddNewPostMutation
.Использование хука мутации немного отличается от хука запроса. Он возвращает не один, а два элемента - функцию-триггер, вызов которой запускает хук, и объект с состоянием запроса.
const [ addNewPost, { isLoading }] = useAddNewPostMutation();В компоненте
AddPostForm
заменяем dispatch
на addNewPost
.Обновление списка постов
После создания нового поста нужно обновить список - по новой запросить его с сервера.
Можно, например, добавить кнопку, при нажатии на которую список будет обновляться (добавлена в компоненте
PostsList
).Используем для этого функцию
refetch
, которую возвращает хук useGetPostsQuery
:const { data, isFetching, isSuccess, isError, refetch } = useGetPostsQuery();Кроме того, нужно заменить флаг
isLoading
на isFetching
. Первый отображает состояние только самого первого запроса, а второй - любого обновления (и первого запроса, и последующих). Инвалидация кеша
Заставлять пользователя вручную обновлять список при добавлении поста - неправильно. Лучше сделать это автоматически. Для этого нужно заставить RTK Query сбросить кеш запроса и выполнить его заново. Чтобы показать, какой именно кеш нужно сбросить, используются теги:
const apiSlice = createApi({Сначала обозначаем используемые теги в массиве
// ...
tagTypes: ["Post"],
endpoints: function(builder) {
return {
getPosts: builder.query({
// ...
providesTags: ["Post"]
}),
addNewPost: builder.mutation({
// ...
invalidatesTags: ["Post"]
})
}
}
})
tagTypes
. Затем привязываем тег Post
к эндпоинту getPosts
с помощью providesTags
. И наконец инвалидируем кеш для тега Post
в мутации addNewPost
с помощью invalidatedTags
.#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
CodeSandbox
Redux Essentials. RTK Query. React Junior - CodeSandbox
Redux Essentials. RTK Query. React Junior by furrycat.web using @reduxjs/toolkit, date-fns, react, react-dom, react-redux, react-router-dom, react-scripts, styled-components
👍2
Redux Essentials 19. RTK Query. Редактирование статьи
Технически редактироание статьи не сильно отличается от добавления новой.
https://codesandbox.io/s/redux-essentials-rtk-query-2-react-junior-5chi5e?file=/src/features/api/apiSlice.js
1. Создаем мутацию
3. Подключаем хук
Интересны тут два момента: кеширование данных между компонентами и инвалидация кеша только для нужного поста.
Кеширование
При открытии страницы поста мы запрашиваем данные для него (
Если уйти с этой страницы, RTK Query под капотом удалит эту подписку. А также запустит таймер. Если в течение определенного времени (по умолчанию 60 секунд) на тот же самый запрос никто не подпишется, то данные тоже удалятся, так как они не нужны приложению.
Отписку и удаление данных можно наблюдать в панели разработчика (вкладка Redux) - экшены
Проект: https://5chi5e.csb.app/
Но если со страницы просмотра поста перейти на страницу его редактирования, то сразу после отписки будет произведена новая подписка. Поэтому для формы редактирования будут использоваться те же самые данные, которые были получены для страницы просмотра.
Инвалидация кеша для отдельного поста
После редактирования поста мы сразу переходим на его страницу для просмотра, поэтому необходимо сбросить кеш запроса и получить обновленные данные. Кроме того, нужно сбросить кеш запроса списка постов.
В прошлый раз мы использовали теги для инвалидации кеша. Можно попробовать добавить тег 'Post' к запросу
Необходимо "уточнить" тег, добавить в него параметры, чтобы он указывал на конкретную сущность. Вместо строки 'Post' мы можем указать объект
Поле
Поле
Технически редактироание статьи не сильно отличается от добавления новой.
https://codesandbox.io/s/redux-essentials-rtk-query-2-react-junior-5chi5e?file=/src/features/api/apiSlice.js
1. Создаем мутацию
editPost
, экспортируем новый хук useEditPostMutation
.endpoints: {2. В компоненте
//...
editPost: builder.mutation({
query: post => ({
url: '',
method: 'PATCH',
body: post
})
})
}
EditPostForm
сначала получаем данные поста (хук useGetPostQuery
), чтобы подставить их в форму. 3. Подключаем хук
useEditPostMutation
, получаем из него функцию updatePost
.const { data: post } = useGetPostQuery(postId);4. При отправке формы вызываем
const [updatePost, { isLoading }] = useEditPostMutation();
updatePost
с новыми данными.Интересны тут два момента: кеширование данных между компонентами и инвалидация кеша только для нужного поста.
Кеширование
При открытии страницы поста мы запрашиваем данные для него (
useGetPostQuery
), другими словами "подписываемся" на результаты запроса getPost(post_id)
. Если уйти с этой страницы, RTK Query под капотом удалит эту подписку. А также запустит таймер. Если в течение определенного времени (по умолчанию 60 секунд) на тот же самый запрос никто не подпишется, то данные тоже удалятся, так как они не нужны приложению.
Отписку и удаление данных можно наблюдать в панели разработчика (вкладка Redux) - экшены
api/subscriptions/unsubscribeQueryResult
и api/queries/removeQueryResult
.Проект: https://5chi5e.csb.app/
Но если со страницы просмотра поста перейти на страницу его редактирования, то сразу после отписки будет произведена новая подписка. Поэтому для формы редактирования будут использоваться те же самые данные, которые были получены для страницы просмотра.
Инвалидация кеша для отдельного поста
После редактирования поста мы сразу переходим на его страницу для просмотра, поэтому необходимо сбросить кеш запроса и получить обновленные данные. Кроме того, нужно сбросить кеш запроса списка постов.
В прошлый раз мы использовали теги для инвалидации кеша. Можно попробовать добавить тег 'Post' к запросу
getPost
и сбрасывать его в мутации editPost
. Но это приведет к сбросу всех ранее запрошенных постов, даже если они не редактировались.Необходимо "уточнить" тег, добавить в него параметры, чтобы он указывал на конкретную сущность. Вместо строки 'Post' мы можем указать объект
{type: 'Post', id: 123 }
.Поле
providesTags
может принимать функцию. После выполнения запроса эта функция вызывается, а в качестве параметров ей передаются результаты запроса. На их основе можно сформировать массив тегов:getPosts: builder.query({Таким образом, каждый пост (и отдельный, и в списке) теперь помечен собственным тегом.
// ...
providesTags: function (result = [], error, arg) {
return [
'Post',
...result.map(function(id) { return { type: 'Post', id }; })
]
}
}),
getPost: builder.query({
// ...
providesTags: function(result, error, arg) {
return [
{ type: 'Post', id: arg }
]
}
})
Поле
invalidatesTags
также может принимать функцию. После выполнения запроса она получит его данные и сможет точно определить, какие статьи нужно сбросить:editPost: builder.mutation({Важно: для эндпоинта
// ...
invalidatesTags: function(result, error, arg) {
return [
{ type: 'Post', id: arg.id }
]
}
})
getPosts
в список тегов включен простой тег 'Post' без уточнения. Это нужно, чтобы список сбрасывался целиком при добавлении новой статьи. Но это также сбросит все отдельные статьи. Имеет смысл уточнить и этот тег, который относится только к списку: { type: 'Post', id: 'LIST' }#redux #управлениесостоянием #rtkquery #обменданными #документация #примерыкода
CodeSandbox
Redux Essentials. RTK Query 2. React Junior - CodeSandbox
Redux Essentials. RTK Query 2. React Junior by furrycat.web using @reduxjs/toolkit, date-fns, react, react-dom, react-redux, react-router-dom, react-scripts, styled-components
👍1
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-эндпоинт
Ручной вызов эндпоинта
Сейчас список пользователей запрашивается в файле
Посмотрим на
Таким образом, есть возможность выполнить запрос "вручную", не прибегая к хукам.
Теперь нужно сделать так, чтобы компоненты получали данные из
Документация предлагает временно отказаться от адаптера. Пока просто заменим существующие селекторы на новые, которые получают данные из
Создадим и экспортируем также селекторы
Кроме того, удаляем
#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
Продолжаем переписывать всю 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
RTK Query предоставляет метод
1. Вызываем метод
2. Метод вернет обновленный объект
3. Далее мы можем использовать
4. Удаляем
5. Отдельно экспортируем эндпоинт:
#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
Проект: 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. Все оказалось очень просто: при получении данных запроса в
https://codesandbox.io/s/redux-essentials-rtk-query-5-react-junior-b5ebze?file=/src/features/users/usersSlice.js
Возвращаемся к 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#redux #управлениесостоянием #обменданными #rtkquery #документация #примерыкода
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;
}
);
CodeSandbox
Redux Essentials. RTK Query 5. React Junior - CodeSandbox
Redux Essentials. RTK Query 5. React Junior by furrycat.web using @reduxjs/toolkit, date-fns, react, react-dom, react-redux, react-router-dom, react-scripts, styled-components
👍2
RTK Query. Query hook: параметры
В хук запроса, сгенерированный для эндпоинта, можно передать параметры:
-
-
Настройки есть следующие:
-
-
-
-
-
-
#обменданными #rtkquery #документация
В хук запроса, сгенерированный для эндпоинта, можно передать параметры:
-
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
Воспользуемся хуком
При любом изменении
#redux #управлениесостоянием #rtkquery #обменданными #документация #примерыкода
Теперь займемся страницей пользователя, на которой выводятся отфильтрованные по автору посты.
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. Сразу после получения. Преобразуем ответ с помощью
2. В компоненте. Получаем данные, сохраненные в кеше, и извлекаем из них то, что нужно. Рекомендуется использовать
3. В хуке запроса. Отбираем нужные данные с помощью опции
#redux #rtkquery #обменданными #паттерны
Мы получаем данные из API в одном виде, но для использования может потребоваться их изменение. Сделать это можно в трех местах:
1. Сразу после получения. Преобразуем ответ с помощью
transformResponse
и сохраняем в кеше уже измененные данные. Имеет смысл, если новый формат подходит всем потребителям. Можно сочетать с entity adapter.2. В компоненте. Получаем данные, сохраненные в кеше, и извлекаем из них то, что нужно. Рекомендуется использовать
useMemo
для избежания ненужных перерендерингов. Полезно, если одному компоненту нужны данные в другом формате.3. В хуке запроса. Отбираем нужные данные с помощью опции
selectFromResult
. Полезно, если компоненту нужна только часть данных.#redux #rtkquery #обменданными #паттерны
👍2