Подключение API запросов RTK с редуктором и селектором redux

#reactjs #react-redux #redux-toolkit #rtk-query

Вопрос:

Я думаю, что мне чего-то не хватает в отношении инструментов запросов Redux и RTK. Для этого я также использую кодовый код RTK-запроса OpenAPI.

Прямо сейчас у меня есть API, который выглядит так ->

 export const api = generatedApi.enhanceEndpoints({
  endpoints: {
    getMaterials: {
      transformResponse: response => normalize(response),
    },
  },
})
 

Это возвращает мне нормализованные данные в моем компоненте:

 const Materials = () => {
  const { data, isLoading, error } = useGetMaterialsQuery()

  /*
    Cool this gives me data back like:
    {
      [id]: {
        id,
        prop1: 'blah',
        prop2: 'blah2'
      }
    }
  */

  console.log(data)

  // but now I want to structure this data differently using selector

  const newDataStructure = useSelector(addSomeMetaDataAndStructureDifferentlySelector)

  return <MyComponent structuredData={newDataStructure} />
}
 

Но когда я смотрю на состояние в этом селекторе, оно выглядит так:

фрагмент данных

Когда в селекторе я действительно хочу использовать что-то вроде — >

 const addSomeMetaDataAndStructureDifferentlySelector = state => 
    // create new data structure from state.materials.byId that I will pass to `MyComponent`
 

Мой магазин сейчас выглядит вот так ->

 import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query'
import { api } from 'gen/rtk-openapi'
// import materialsReducer from 'state/materials/materialsSlice'

const store = configureStore({
  reducer: {
    [api.reducerPath]: api.reducer,
  },
  middleware: getDefaultMiddleware =>
    getDefaultMiddleware().concat(api.middleware),
})

setupListeners(store.dispatch)

export default store
 

Так нужно ли мне передавать data непосредственно из useGetMaterialsQuery в функцию и просто преобразовывать ее?

Или я могу каким-то образом создать новый срез редуктора, который каким-то образом правильно инициализируется данными, чтобы я мог получить доступ state.materials.byId в селекторе?

Или я в корне чего-то здесь не понимаю?

Ответ №1:

Вы упустили важный момент: RTK-Запрос-это не нормализованный кэш, а кэш документов.

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

Это может показаться недостатком, но создание реального «нормализованного» кэша практически невозможно в качестве универсальной библиотеки, особенно для чего-то неоднозначного, такого как API REST. Даже для GraphQL это очень сложная проблема, и даже решения, специализированные на GraphQL (например, apollo), имеют множество проблем и заканчиваются большим количеством написанного от руки кода для обработки таких случаев, как добавление/удаление из коллекций.

Таким образом, в конце концов, у вас остается два варианта: написать свое собственное нормализованное решение для кэширования вручную, явно ориентированное на ваши структуры данных, или использовать кэш документов.

В большинстве ситуаций достаточно только кэша документов. И именно здесь появляется RTK-запрос, который пытается предоставить вам как можно больше инструментов для «синхронизации» данных между вашими документами: автоматическая повторная установка и возможность вручную выполнять оптимистичные обновления для управления конкретными записями кэша.

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

Что касается того, как его использовать: Как правило, вы просто вызываете useGetMaterialsQuery() все компоненты, которым нужны материалы, и просто отфильтровываете то, что вам нужно в вашем компоненте, или используете selectFromResult опцию, чтобы просто выбрать то, что вам действительно нужно.

Комментарии:

1. как используется selectFromResult?

2. const result = useGetMaterialsQuery(undefined, { selectFromResult: result => ({ ...result, data: result.data.foo.bar }) })

3. Этот ответ имеет смысл для меня, но я все еще пытаюсь понять, где в rtk-запросе вы храните изменения в кэше документов, необходимые для ваших компонентов. Я знаю, что копирование кэша в другой срез не одобряется, поэтому следует ли мне создавать дополнительные пользовательские крючки для добавления/фильтрации/обновления значений (только на стороне клиента, ничего не возвращается на сервер) или мне следует внести какие-либо изменения бизнес-логики в ответ сервера где-то вроде transformResponse ? Например, если бы я хотел объединить два поля в ответе объекта в новое значение ключа, где это произойдет?

4. @aboutaaron если везде, где вы используете эту конечную точку, вы используете ее в одной и той же форме, а затем внутри transformResponse . Если один компонент хочет получить только часть ответа, я бы использовал selectFromResult его, и если в дополнение к этому ему потребуется выполнить преобразование данных, я просто useMemo сделаю это .

5. @phry вы предлагаете использовать useFooQuery(...) over api.endpoints.foo.select('cache-key') with useSelector ?