Comment utiliser les requêtes RTK avec des sélecteurs
P粉729198207
P粉729198207 2023-12-28 11:46:38
0
1
679

Je construis un projet en utilisant Redux Toolkit et RTK Query et j'essaie d'obtenir des entrées de l'API. J'utilise la méthode de normalisation des données de createEntityAdapter car dans un certain composant, j'ai besoin des données sous forme de tableau, j'ai donc fini par utiliser un sélecteur. Maintenant, mon problème est que depuis que j'ai ajouté le filtre en tant que paramètre de la requête, mon sélecteur a cessé de fonctionner.

J'ai examiné des questions similaires ici comme : "Comment utiliser le sélecteur de requête RTK avec des paramètres ?", mais je suis trop stupide pour comprendre ce que je dois modifier. J'essaie de comprendre la documentation des requêtes RTK, mais je n'y parviens pas.

D'après la question ci-dessus, je sais que mon sélecteur doit également avoir des paramètres afin de savoir exactement quoi sélectionner, et que ce n'est pas un modèle recommandé, mais je n'arrive pas à comprendre comment le faire fonctionner.

Ma tranche d'entrée :

import { createSelector, createEntityAdapter } from '@reduxjs/toolkit'
import { apiSlice } from './apiSlice'

const entryAdapter = createEntityAdapter()

const initialState = entryAdapter.getInitialState({
  ids: [],
  entities: {},
})

export const entryApiSlice = apiSlice.injectEndpoints({
  endpoints: (builder) => ({
    initialState,
    getEntry: builder.query({
      query: (filters) => ({
        url: '/history',
        params: filters,
      }),
      transformResponse: (responseData) => {
        return entryAdapter.setAll(initialState, responseData)
      },
      providesTags: (result, error, arg) => [
        { type: 'Entry', id: 'LIST' },
        ...result.ids.map((id) => ({ type: 'Entry', id })),
      ],
    }),
    addEntry: builder.mutation({
      query: (data) => ({
        url: '/history/new',
        method: 'POST',
        body: data,
      }),
      invalidatesTags: [{ type: 'Entry', id: 'LIST' }],
    }),
    updateEntry: builder.mutation({
      query: (initialEntry) => ({
        url: `/history/${initialEntry.Id}`,
        method: 'PUT',
        body: {
          ...initialEntry,
          date: new Date().toISOString(),
        },
      }),
      invalidatesTags: (result, error, arg) => [{ type: 'Entry', id: arg.id }],
    }),
    deleteEntry: builder.mutation({
      query: ({ id }) => ({
        url: `/history/${id}`,
        method: 'DELETE',
        body: { id },
      }),
      invalidatesTags: (result, error, arg) => [{ type: 'Entry', id: arg.id }],
    }),
  }),
})

export const {
  useGetEntryQuery,
  useAddEntryMutation,
  useUpdateEntryMutation,
  useDeleteEntryMutation,
} = entryApiSlice

export const selectEntryResult = (state, params) =>
  entryApiSlice.endpoints.getEntry.select(params)(state).data

const entrySelectors = entryAdapter.getSelectors(
  (state) => selectEntryResult(state) ?? initialState
)
export const selectEntry = entrySelectors.selectAll

Je l'utilise dans le composant Entries comme celui-ci

const {
    data: entriesData = [],
    refetch,
    isLoading,
    isSuccess,
    isError,
    error,
  } = useGetEntryQuery(filters)

  const entries = useSelector(selectEntry)

Remarque : si je supprime le "filtre" de la requête get, tout fonctionne comme avant (comme prévu, bien sûr).

Avertissement : je n'ai aucune idée de ce que je fais, j'ai lu la documentation et j'essaie de comprendre, donc tout commentaire est grandement apprécié. Merci!

P粉729198207
P粉729198207

répondre à tous(1)
P粉779565855

Oui, c'est un sujet un peu sensible, car la documentation de RTKQ a tendance à montrer les exemples les plus simples, c'est-à-dire des requêtes qui n'utilisent aucun paramètre. J'ai moi-même eu beaucoup de problèmes.

Quoi qu'il en soit, vous avez déclaré selectEntryResult en fonction de deux paramètres : state et params. Ensuite, lorsque vous créez le sélecteur d'adaptateur en dessous, vous l'appelez avec un seul paramètre : state. De plus, lorsque vous utilisez le sélecteur final dans votre composant comme ceci :

const entries = useSelector(selectEntry);
Les paramètres

sont introuvables, par défaut ils 未定义 et aucune donnée associée à ces paramètres de requête ne peut être trouvée.

L'élément clé à comprendre ici est que vous devez en fait transmettre les paramètres de requête à travers chaque niveau du sélecteur (chaque wrapper) d'une manière ou d'une autre.

Une façon ici est de "transférer" le paramètre via le sélecteur :

export const selectEntryResult = createSelector([state => state, (_, params) => params], (state, params) =>
  entryApiSlice.endpoints.getEntry.select(params)(state)?.data ?? initialState)

Ici, nous utilisons la fonction createSelector exportée depuis RTK. Ensuite, dans votre composant, vous feriez quelque chose comme ceci :

  const {...} = useGetEntryQuery(filters);

  const entries = useSelector(state => selectEntry(state, filters));

Ceci est créé à l'aide de l'adaptateur d'entité selectAll 选择器时有效,但在使用 selectById 时会导致问题,因为该选择器也是参数化的。简而言之,selectById Le sélecteur est défini en interne pour prendre un deuxième argument de l'identifiant d'entité que vous souhaitez récupérer, alors que la méthode que j'ai montrée utilise le deuxième argument pour transmettre les paramètres de requête (le filtre de votre appareil). cas).

Pour autant que je sache, il n'existe jusqu'à présent aucune solution qui fonctionne parfaitement et couvre tous les éléments suivants :

  • Utiliser la normalisation d'entité
  • Récupérer des données à l'aide de sélecteurs
  • Utiliser des requêtes paramétrées

Une autre approche pourrait consister à créer des fabriques de sélecteurs qui créent dynamiquement des sélecteurs de base pour des combinaisons spécifiques de paramètres de requête.

J'ai déjà fabriqué un tel emballage qui peut être utilisé dans toutes les situations. Malheureusement, je ne peux pas le partager car il s'agit d'un package privé, mais l'idée de base est de modifier les paramètres pour que selectByIdselectAll (et tous les autres sélecteurs) fonctionnent correctement, en transmettant les paramètres de requête au sélecteur comme troisième argument, puis plus loin réemballer chaque sélecteur d'adaptateur d'entité : 

export const createBaseSelector = (endpoint) =>
  createSelector(
    [(state) => state, (_, other) => other, (_, _other, params) => params],
    (state, _other, params) => endpoint.select(params)(state)
  );

const selectors = adapter.getSelectors(baseSelector);

// And then rewrap selectAll and selectById from 'selectors' above

Je sais que cela semble compliqué et je l'ai à peine fait fonctionner alors essayez d'éviter d'aller dans cette direction :)

Un article utile que j'ai trouvé en cours de route peut être trouvé ici, ils décrivent également quelques façons de créer des sélecteurs au niveau des composants et de les mémoriser, mais je ne les ai pas encore tous essayés. Jetez un œil, vous trouverez peut-être un moyen plus simple de résoudre votre problème spécifique.

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal