RTK Query предоставляет встроенный механизм подписки на данные через
хуки, такие как useGetPostsQuery. Однако стандартная
подписка возвращает весь объект результата запроса, включая
data, isLoading, isFetching,
error и другие поля. При росте приложения это приводит к
избыточным перерендериваниям компонентов, даже если используется только
небольшая часть данных.
selectFromResult решает эту проблему, позволяя явно
указать, какие именно части результата запроса необходимы компоненту.
Это создает локальный селектор поверх кеша RTK Query и существенно
снижает количество лишних обновлений UI.
selectFromResultКаждый хук RTK Query подписывается на состояние запроса в Redux store. Без оптимизации компонент получает новый объект результата при любом изменении состояния запроса.
selectFromResult перехватывает этот процесс и
позволяет:
Ключевая особенность заключается в том, что RTK Query сравнивает возвращаемое значение селектора. Если результат не изменился по ссылке, компонент не перерендеривается.
const { data } = useGetPostsQuery(undefined, {
selectFromResult: (result) => ({
data: result.data
})
});
В данном случае компонент подписывается только на data.
Изменения в isFetching, error или
status не вызывают перерендер, если data
остается неизменным.
selectFromResult позволяет возвращать произвольную
структуру данных, включая комбинации полей состояния запроса.
const { posts, loading } = useGetPostsQuery(undefined, {
selectFromResult: (result) => ({
posts: result.data,
loading: result.isLoading
})
});
Такой подход полезен при построении UI с раздельной логикой отображения состояния загрузки и данных.
Одно из ключевых применений — фильтрация или преобразование данных прямо в селекторе запроса.
const { activePosts } = useGetPostsQuery(undefined, {
selectFromResult: (result) => ({
activePosts: result.data?.filter(post => post.isActive)
})
});
Здесь компонент получает только отфильтрованные данные, не храня промежуточные вычисления в теле компонента.
Важно понимать, что selectFromResult выполняется при
каждом обновлении состояния запроса. Однако RTK Query использует
shallow-equality для сравнения результата.
Перерендер произойдет только если:
Пример потенциальной проблемы:
selectFromResult: (result) => ({
posts: result.data?.map(x => ({ ...x }))
})
В этом случае создается новый массив и новые объекты при каждом обновлении, что приводит к постоянным перерендериваниям.
selectFromResult особенно полезен при работе с
коллекциями, когда нужен доступ к одному элементу по ID.
const { post } = useGetPostsQuery(undefined, {
selectFromResult: (result) => ({
post: result.data?.find(p => p.id === 5)
})
});
Такой подход позволяет компоненту подписаться только на конкретный объект, а не на весь массив.
selectFromResult работает не только с результатом, но и
в контексте аргументов запроса.
const { post } = useGetPostQuery(postId, {
selectFromResult: (result) => ({
post: result.data
})
});
При изменении postId создается новая подписка, но внутри
каждой подписки можно ограничить реактивность.
RTK Query уже использует внутренние селекторы, но
selectFromResult отличается тем, что:
createSelectorВ отличие от классических Redux-селекторов:
| Подход | Область применения | Кеширование |
|---|---|---|
| createSelector | глобальный store | вручную |
| selectFromResult | конкретный запрос | встроено |
В сложных интерфейсах часто используется один запрос для нескольких
визуальных блоков. Без selectFromResult каждый блок
подписан на весь результат.
Пример разделения:
const { list } = useGetUsersQuery(undefined, {
selectFromResult: (result) => ({
list: result.data
})
});
const { loading } = useGetUsersQuery(undefined, {
selectFromResult: (result) => ({
loading: result.isLoading
})
});
Несмотря на одинаковый endpoint, каждый хук подписан только на нужную часть состояния.
selectFromResultНельзя создавать новые объекты без необходимости:
selectFromResult: (result) => ({
data: [...result.data]
})
Каждый вызов создаёт новую ссылку, ломая мемоизацию.
undefinedПри работе с асинхронными данными необходимо учитывать отсутствие
data:
selectFromResult: (result) => ({
count: result.data.length
})
Такой код приведет к ошибке при data === undefined.
Корректный вариант:
selectFromResult: (result) => ({
count: result.data?.length ?? 0
})
Сложные вычисления внутри selectFromResult могут стать
узким местом, так как выполняются при каждом обновлении запроса.
transformResponseselectFromResult работает на уровне клиента, тогда как
transformResponse — на уровне получения данных.
Разделение ответственности:
transformResponse — нормализация данных при
загрузкеselectFromResult — выбор и адаптация данных под UIПример:
transformResponse: (response) => response.items
selectFromResult: (result) => ({
active: result.data?.filter(x => x.active)
})
RTK Query хранит состояние запроса как единый объект.
selectFromResult позволяет разбить его на логические
части.
const { data, errorState } = useGetOrdersQuery(undefined, {
selectFromResult: (result) => ({
data: result.data,
errorState: result.error
})
});
Это особенно полезно для UI с разными состояниями отображения ошибок.
При изменении кеша RTK Query:
selectFromResultЭто делает selectFromResult ключевым инструментом
контроля ререндеров на уровне подписки.
const { pageItems } = useGetItemsQuery(page, {
selectFromResult: (result) => ({
pageItems: result.data?.slice(0, 10)
})
});
Такой подход позволяет не дублировать логику пагинации в каждом компоненте.
selectFromResultВ больших приложениях RTK Query становится источником состояния для
множества UI-компонентов. selectFromResult выполняет роль
адаптера между:
Это позволяет:
const { isLoading } = useGetProfileQuery(id, {
selectFromResult: (result) => ({
isLoading: result.isLoading
})
});
const { user } = useGetUsersQuery(undefined, {
selectFromResult: (result) => ({
user: result.data?.find(u => u.id === userId)
})
});
const { data, status } = useGetDataQuery(undefined, {
selectFromResult: (result) => ({
data: result.data,
status: result.status
})
});
Использование selectFromResult особенно заметно в
следующих сценариях:
В этих условиях уменьшение числа перерендеров напрямую влияет на отзывчивость интерфейса.
selectFromResult не создает новый слой кеша и не
дублирует данные. Он лишь создает вычисляемую проекцию существующего
состояния RTK Query, что делает его легким, но мощным инструментом
оптимизации подписок.