Недействительность конечной точки Redux Toolkit Query (RTKQ) и повторная проверка срабатывают несколько раз

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

Проблема: когда вы используете мутацию RTK и имеете несколько injectEndpoints, повторная проверка GET (запроса) запускается несколько раз. У меня есть несколько injectEndpoints, так как я хочу разделить каждую из моих конечных точек. Я вижу, что для каждого отдельного файла с injectEndpoint в нем RTK извлекает дополнительную информацию для каждого файла.

Ожидается: при запуске мутации RTK повторная проверка любого GET (запроса) должна срабатывать только 1 раз, независимо от того, сколько injectEndpoints находится в API. Именно такое поведение наблюдалось при первом внедрении RTK.

Код: store.js

import { configureStore } from '@reduxjs/toolkit'

// API
import { baseQuery } from 'api/baseQuery'
import { commentsApi } from 'api/commentsApi'
import { postApi } from 'api/postApi'

const reducer = {
  [baseQuery.reducerPath]: baseQuery.reducer,
  [commentsApi.reducerPath]: commentsApi.reducer,
  [postApi.reducerPath]: postApi.reducer
}

const middleware = getDefaultMiddleware =>
  getDefaultMiddleware()
  .concat(baseQuery.middleware)
  .concat(commentsApi.middleware)
  .concat(postApi.middleware)

export const store = configureStore({
  middleware,
  reducer
})

baseQuery.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const baseQuery = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://jsonplaceholder.typicode.com',
    prepareHeaders: headers => {
      headers.set('Accept', 'application/json')
      return headers
    }
  }),
  endpoints: () => ({}),
  reducerPath: 'api',
  tagTypes: [
    'comments',
    'posts'
  ]
})

postApi.js

import { baseQuery } from './baseQuery'

export const postApi = baseQuery.injectEndpoints({
  endpoints: ({ mutation, query }) => ({
    addPost: mutation({
      invalidatesTags: ['posts'],
      query: ({ id }) => ({
        method: 'POST',
        url: `/posts`
      })
    }),
    getPosts: query({
      providesTags: ['posts'],
      query: () => `/posts`
    }),
  })
})

export const {
  useAddPostMutation,
  useGetPostsQuery
} = postApi

комментарииApi.js

import { baseQuery } from './baseQuery'

export const commentsApi = baseQuery.injectEndpoints({
  endpoints: ({ mutation, query }) => ({
    getComments: query({
      providesTags: ['comments'],
      query: () => `/posts/1/comments`
    }),
  })
})

export const {
  useGetCommentsQuery
} = commentsApi

Приложение.js

import { Button, Stack, Typography } from '@mui/material'
import ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded'
import { useAddPostMutation, useGetPostsQuery } from 'api/postApi'
import { useGetCommentsQuery } from 'api/commentsApi'

const App = () => {
  const { data = [] } = useGetPostsQuery()
  const { data: comments = []} = useGetCommentsQuery()
  const [addPost] = useAddPostMutation()

  console.info('comments', comments)

  return (
    <>
      <Button
        onClick = {addPost}
        startIcon = {<ChevronLeftRoundedIcon />}
      >
        Add Post
      </Button>
      <Stack
        spacing = {3}
        sx = {{
          alignItems: 'center',
          justifyContent: 'center',
          px: 3
        }}
      >
        <Typography>
          List of Posts
        </Typography>
        {data.map(({ id, title }) => (
          <Typography key = {id} variant='body2'>
            {title}
          </Typography>
        ))}
      </Stack>
    </>
  )
}

export default App

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux'
import { store } from 'store/store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store = {store}>
    <App />
  </Provider>
);

Единственными зависимостями здесь являются Redux, Redux-Toolkit, Material-UI, Ramda. Я использую jsonplaceholder API в качестве простого примера, чтобы вы могли легко воспроизвести эту проблему.

Что происходит

  1. Посмотрев на вкладку сети, вы получите начальные GET для конечных точек posts и comments.
  2. Нажмите кнопку «Добавить сообщение».
  3. Запускает POST до конечной точки posts
  4. Вкладка «Сеть» срабатывает GET до конечной точки posts 3 раза.
  5. 1 201 POST
  6. 3 200 GET, без введенной конечной точки comments это возвращает 2 200 GET. Это означает, что для каждой введенной конечной точки происходит дополнительное GET событие.

ПРИМЕЧАНИЕ: У меня нет приложения реагирования в строгом режиме, так как это не повлияет на конечные результаты. Похоже, что каждый GET повторно отображает компонент App.js, но Redux Store не должен вызывать эту проблему. В предыдущих приложениях, использующих Redux и RTK, вы могли легко написать такое приложение и никогда не видеть, чтобы для каждой используемой вами мутации запускалось несколько GET.

Изображения проблемы:

  • Перед нажатием кнопки

  • После нажатия кнопки

🤔 А знаете ли вы, что...
JavaScript может выполняться как на стороне клиента (в браузере), так и на стороне сервера (с использованием Node.js).


1
171
1

Ответ:

Решено

Несмотря на то, что вы разделяете код, у вас все равно есть только один фрагмент API, который вы «расширяете/улучшаете».

Я почти уверен, что ваша проблема вызвана добавлением того, что фактически является «дублирующими» фрагментами API, каждый из commentsApi и postApi «расширяет/улучшает» базовый baseQuery фрагмент API. Вам нужно добавить только один фрагмент API для каждого базового запроса. Какой бы фрагмент API вы ни импортировали для использования в хранилище, в него уже должны быть внедрены разделенные фрагменты API.

Пример:

baseQuery.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const baseQuery = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://jsonplaceholder.typicode.com',
    prepareHeaders: headers => {
      headers.set('Accept', 'application/json');
      return headers;
    }
  }),
  endpoints: () => ({}),
  reducerPath: 'api',
  tagTypes: [
    'comments',
    'posts'
  ]
});

postApi.js: импортирует фрагмент API baseQuery и экспортирует расширенную версию.

import { baseQuery } from './baseQuery';

const enhancedBaseQuery = baseQuery.injectEndpoints({
  endpoints: ({ mutation, query }) => ({
    addPost: mutation({
      invalidatesTags: ['posts'],
      query: ({ id }) => ({
        method: 'POST',
        url: `/posts`
      })
    }),
    getPosts: query({
      providesTags: ['posts'],
      query: () => `/posts`
    }),
  })
})

export const {
  useAddPostMutation,
  useGetPostsQuery
} = enhancedBaseQuery;

export default enhancedBaseQuery;

commentApi.js: следует импортировать расширенный фрагмент API из postApi.js и экспортировать другую расширенную версию фрагмента API.

import baseQuery from 'api/postApi';

const enhancedBaseQuery = baseQuery.injectEndpoints({
  endpoints: ({ mutation, query }) => ({
    getComments: query({
      providesTags: ['comments'],
      query: () => `/posts/1/comments`
    }),
  })
});

export const {
  useGetCommentsQuery
} = BaseQuery;

export default BaseQuery;
import { configureStore } from '@reduxjs/toolkit';

// API
import baseQuery from 'api/commentsApi';

const reducer = {
  [baseQuery.reducerPath]: baseQuery.reducer,
};

const middleware = getDefaultMiddleware =>
  getDefaultMiddleware()
    .concat(baseQuery.middleware);

export const store = configureStore({
  middleware,
  reducer
});

Альтернатива

Раньше я обычно экспортировал только определения конечных точек из кода разделения и внедрял их в файл/компонент, создавая корневой baseQueryAPI срез.

Пример:

baseQuery.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { commentsApi } from 'api/commentsApi'
import { postApi } from 'api/postApi'

export const baseQuery = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://jsonplaceholder.typicode.com',
    prepareHeaders: headers => {
      headers.set('Accept', 'application/json')
      return headers
    }
  }),
  endpoints: () => ({}),
  reducerPath: 'api',
  tagTypes: [
    'comments',
    'posts'
  ]
});

postApi.js


export const postApi = {
  endpoints: ({ mutation, query }) => ({
    addPost: mutation({
      invalidatesTags: ['posts'],
      query: ({ id }) => ({
        method: 'POST',
        url: `/posts`
      })
    }),
    getPosts: query({
      providesTags: ['posts'],
      query: () => `/posts`
    }),
  })
};

комментарииApi.js

export const commentsApi = {
  endpoints: ({ mutation, query }) => ({
    getComments: query({
      providesTags: ['comments'],
      query: () => `/posts/1/comments`
    }),
  })
};

магазин

import { configureStore } from '@reduxjs/toolkit';

// API
import { baseQuery } from 'api/baseQuery';
import { commentsApi } from 'api/commentsApi';
import { postApi } from 'api/postApi';

const apiSlice = baseQuery
  .injectEndpoints(commentsApi)
  .injectEndpoints(postApi);

const reducer = {
  [apiSlice.reducerPath]: apiSlice.reducer,
}

const middleware = getDefaultMiddleware =>
  getDefaultMiddleware()
    .concat(apiSlice.middleware)

export const store = configureStore({
  middleware,
  reducer
});

export const {
  // ... all the generated hooks ...
} = apiSlice;