В качестве предисловия я просмотрел документацию 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
в качестве простого примера, чтобы вы могли легко воспроизвести эту проблему.
Что происходит
GET
для конечных точек posts
и comments
.POST
до конечной точки posts
GET
до конечной точки posts
3 раза.POST
GET
, без введенной конечной точки comments
это возвращает 2 200 GET
. Это означает, что для каждой введенной конечной точки происходит дополнительное GET
событие.ПРИМЕЧАНИЕ:
У меня нет приложения реагирования в строгом режиме, так как это не повлияет на конечные результаты. Похоже, что каждый GET
повторно отображает компонент App.js, но Redux Store не должен вызывать эту проблему. В предыдущих приложениях, использующих Redux и RTK, вы могли легко написать такое приложение и никогда не видеть, чтобы для каждой используемой вами мутации запускалось несколько GET
.
Изображения проблемы:
Перед нажатием кнопки
После нажатия кнопки
🤔 А знаете ли вы, что...
JavaScript может выполняться как на стороне клиента (в браузере), так и на стороне сервера (с использованием Node.js).
Несмотря на то, что вы разделяете код, у вас все равно есть только один фрагмент 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;