在本指南中,我们将介绍:
RTK 查询 是内置于 Redux Toolkit (RTK) 中的高级数据获取和缓存工具。它通过为获取、缓存和更新数据等常见任务生成 Redux 切片和挂钩来简化 API 交互。主要功能包括:
React Query 和 RTK Query 都提供了 React 应用程序中的数据获取和缓存解决方案,但它们具有不同的优势和用例:
Feature | RTK Query | React Query |
---|---|---|
Purpose | Integrated within Redux for managing server data in Redux state. Best for apps already using Redux or requiring centralized global state. | Dedicated to managing server state with no Redux dependency. Great for apps focused on server state without Redux. |
Caching | Automatic caching with fine-grained cache invalidation through tags. Caches data globally within the Redux store. | Automatic caching with flexible cache control policies. Maintains a separate cache independent of Redux. |
Generated Hooks | Auto-generates hooks for endpoints, allowing mutations and queries using useQuery and useMutation hooks. | Provides hooks (useQuery, useMutation) that work independently from Redux, but require manual configuration of queries and mutations. |
DevTools | Integrated into Redux DevTools, making debugging seamless for Redux users. | Provides its own React Query DevTools, with detailed insight into query states and cache. |
Error Handling | Centralized error handling using Redux middleware. | Error handling within individual queries, with some centralized error-handling options. |
Redux Integration | Built directly into Redux, simplifying usage for Redux-based apps. | Not integrated with Redux by default, although Redux and React Query can be combined if needed. |
在 RTK 查询和 React 查询之间进行选择:
使用 RTK 查询如果:
使用 React 查询 如果:
本质上,RTK Query 非常适合以 Redux 为中心的应用程序,而 React Query 为没有 Redux 的项目或那些注重本地化服务器状态管理的项目提供了灵活性和简单性。
// src/store/store.js import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers, configureStore, isRejectedWithValue } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { FLUSH, PAUSE, PERSIST, persistReducer, PURGE, REGISTER, REHYDRATE } from 'redux-persist'; import { authApi } from '../api/authApi'; import { postsApi } from '../api/postsApi'; import { usersApi } from '../api/usersApi'; import authSlice from '../features/auth/authSlice'; const persistConfig = { key: 'root', version: 1, storage: AsyncStorage, blacklist: ['auth', postsApi.middleware, usersApi.middleware, authApi.middleware], // these reduce will not persist data (NOTE: blacklist rtk api slices so that to use tags) // whitelist: ['users'], //these reduce will persist data }; const getEnhancers = (getDefaultEnhancers) => { if (process.env.NODE_ENV === 'development') { const reactotron = require('../reactotronConfig/ReactotronConfig').default; return getDefaultEnhancers().concat(reactotron.createEnhancer()); } return getDefaultEnhancers(); }; /** * On api error this will be called */ export const rtkQueryErrorLogger = (api) => (next) => (action) => { // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.log('isRejectedWithValue', action.error, action.payload); alert(JSON.stringify(action)); // This is just an example. You can replace it with your preferred method for displaying notifications. } return next(action); }; const reducer = combineReducers({ auth: authSlice, [postsApi.reducerPath]: postsApi.reducer, [usersApi.reducerPath]: usersApi.reducer, [authApi.reducerPath]: authApi.reducer, }); const persistedReducer = persistReducer(persistConfig, reducer); const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware, usersApi.middleware, authApi.middleware, rtkQueryErrorLogger), enhancers: getEnhancers, }); setupListeners(store.dispatch); export default store;
Redux Store (src/store/store.js):Redux store 是保存应用程序状态的主要结构。在您的设置中,它通过 redux-persist 进行了增强,可以在本地保存 Redux 状态的某些部分,因此即使应用程序重新启动,它们也会持续存在。
redux-persist:
增强器:自定义增强器用于在开发模式下集成Reactotron,这是一个调试 Redux 操作、状态和网络请求的有用工具。这只在开发时激活,使调试更容易,而不影响生产。
中间件:
setupListeners:此功能可以在发生某些事件时自动重新获取数据,例如当应用程序重新获得焦点或从后台恢复时,为用户提供新鲜数据,而无需手动刷新。
RTK Query 通过自动生成 Redux 切片、挂钩和缓存来简化 API 调用。以下是您定义的 API 的详细信息:
// src/store/store.js import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers, configureStore, isRejectedWithValue } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { FLUSH, PAUSE, PERSIST, persistReducer, PURGE, REGISTER, REHYDRATE } from 'redux-persist'; import { authApi } from '../api/authApi'; import { postsApi } from '../api/postsApi'; import { usersApi } from '../api/usersApi'; import authSlice from '../features/auth/authSlice'; const persistConfig = { key: 'root', version: 1, storage: AsyncStorage, blacklist: ['auth', postsApi.middleware, usersApi.middleware, authApi.middleware], // these reduce will not persist data (NOTE: blacklist rtk api slices so that to use tags) // whitelist: ['users'], //these reduce will persist data }; const getEnhancers = (getDefaultEnhancers) => { if (process.env.NODE_ENV === 'development') { const reactotron = require('../reactotronConfig/ReactotronConfig').default; return getDefaultEnhancers().concat(reactotron.createEnhancer()); } return getDefaultEnhancers(); }; /** * On api error this will be called */ export const rtkQueryErrorLogger = (api) => (next) => (action) => { // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.log('isRejectedWithValue', action.error, action.payload); alert(JSON.stringify(action)); // This is just an example. You can replace it with your preferred method for displaying notifications. } return next(action); }; const reducer = combineReducers({ auth: authSlice, [postsApi.reducerPath]: postsApi.reducer, [usersApi.reducerPath]: usersApi.reducer, [authApi.reducerPath]: authApi.reducer, }); const persistedReducer = persistReducer(persistConfig, reducer); const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware, usersApi.middleware, authApi.middleware, rtkQueryErrorLogger), enhancers: getEnhancers, }); setupListeners(store.dispatch); export default store;
// src/api/authApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { setToken } from '../features/auth/authSlice'; export const authApi = createApi({ reducerPath: 'authApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/auth/', }), endpoints: (builder) => ({ login: builder.mutation({ query: (credentials) => ({ url: 'login', method: 'POST', body: credentials, }), async onQueryStarted(arg, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled; dispatch(setToken(data.accessToken)); // Store the token in Redux } catch (error) { console.error('Login error:', error); } }, }), }), }); export const { useLoginMutation } = authApi;
// src/store/store.js import AsyncStorage from '@react-native-async-storage/async-storage'; import { combineReducers, configureStore, isRejectedWithValue } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { FLUSH, PAUSE, PERSIST, persistReducer, PURGE, REGISTER, REHYDRATE } from 'redux-persist'; import { authApi } from '../api/authApi'; import { postsApi } from '../api/postsApi'; import { usersApi } from '../api/usersApi'; import authSlice from '../features/auth/authSlice'; const persistConfig = { key: 'root', version: 1, storage: AsyncStorage, blacklist: ['auth', postsApi.middleware, usersApi.middleware, authApi.middleware], // these reduce will not persist data (NOTE: blacklist rtk api slices so that to use tags) // whitelist: ['users'], //these reduce will persist data }; const getEnhancers = (getDefaultEnhancers) => { if (process.env.NODE_ENV === 'development') { const reactotron = require('../reactotronConfig/ReactotronConfig').default; return getDefaultEnhancers().concat(reactotron.createEnhancer()); } return getDefaultEnhancers(); }; /** * On api error this will be called */ export const rtkQueryErrorLogger = (api) => (next) => (action) => { // RTK Query uses `createAsyncThunk` from redux-toolkit under the hood, so we're able to utilize these matchers! if (isRejectedWithValue(action)) { console.log('isRejectedWithValue', action.error, action.payload); alert(JSON.stringify(action)); // This is just an example. You can replace it with your preferred method for displaying notifications. } return next(action); }; const reducer = combineReducers({ auth: authSlice, [postsApi.reducerPath]: postsApi.reducer, [usersApi.reducerPath]: usersApi.reducer, [authApi.reducerPath]: authApi.reducer, }); const persistedReducer = persistReducer(persistConfig, reducer); const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat(postsApi.middleware, usersApi.middleware, authApi.middleware, rtkQueryErrorLogger), enhancers: getEnhancers, }); setupListeners(store.dispatch); export default store;
// src/api/authApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { setToken } from '../features/auth/authSlice'; export const authApi = createApi({ reducerPath: 'authApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/auth/', }), endpoints: (builder) => ({ login: builder.mutation({ query: (credentials) => ({ url: 'login', method: 'POST', body: credentials, }), async onQueryStarted(arg, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled; dispatch(setToken(data.accessToken)); // Store the token in Redux } catch (error) { console.error('Login error:', error); } }, }), }), }); export const { useLoginMutation } = authApi;
// src/api/postsApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; // Define the postsApi slice with RTK Query export const postsApi = createApi({ // Unique key for the API slice in Redux state reducerPath: 'postsApi', // Configure base query settings, including the base URL for all requests baseQuery: fetchBaseQuery({ baseUrl: 'https://jsonplaceholder.typicode.com', }), // Define cache tag types for automatic cache invalidation tagTypes: ['Posts'], // Define API endpoints (queries and mutations) endpoints: (builder) => ({ // Query to fetch a paginated list of posts getPosts: builder.query({ // URL and parameters for paginated posts query: ({ page = 1, limit = 10 }) => `/posts?_page=${page}&_limit=${limit}`, // Tagging posts to automatically refresh this cache when needed providesTags: (result) => result ? [...result.map(({ id }) => ({ type: 'Posts', id })), { type: 'Posts', id: 'LIST' }] : [{ type: 'Posts', id: 'LIST' }], }), // Query to fetch a single post by its ID getPostById: builder.query({ // Define query with post ID in the URL path query: (id) => `/posts/${id}`, // Tag individual post by ID for selective cache invalidation providesTags: (result, error, id) => [{ type: 'Posts', id }], }), // Mutation to create a new post createPost: builder.mutation({ // Configure the POST request details and payload query: (newPost) => ({ url: '/posts', method: 'POST', body: newPost, }), // Invalidate all posts (paginated list) to refresh after creating a post invalidatesTags: [{ type: 'Posts', id: 'LIST' }], }), // Mutation to update an existing post by its ID updatePost: builder.mutation({ // Define the PUT request with post ID and updated data in the payload query: ({ id, ...updatedData }) => ({ url: `/posts/${id}`, method: 'PUT', body: updatedData, }), // Invalidate cache for both the updated post and the paginated list invalidatesTags: (result, error, { id }) => [ { type: 'Posts', id }, { type: 'Posts', id: 'LIST' }, ], }), // Mutation to delete a post by its ID deletePost: builder.mutation({ // Define the DELETE request with post ID in the URL path query: (id) => ({ url: `/posts/${id}`, method: 'DELETE', }), // Invalidate cache for the deleted post and the paginated list invalidatesTags: (result, error, id) => [ { type: 'Posts', id }, { type: 'Posts', id: 'LIST' }, ], }), }), }); // Export generated hooks for each endpoint to use them in components export const { useGetPostsQuery, // Use this when you want data to be fetched automatically as the component mounts or when the query parameters change. useLazyGetPostsQuery, // Use this when you need more control over when the query runs, such as in response to a user action (e.g., clicking a button), conditional fetching, or specific events. useGetPostByIdQuery, useCreatePostMutation, useUpdatePostMutation, useDeletePostMutation, } = postsApi;
// src/api/usersApi.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const usersApi = createApi({ reducerPath: 'usersApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com', prepareHeaders: (headers, { getState }) => { // Get the token from the Redux auth state const { token } = getState().auth; // If the token exists, set it in the Authorization header if (token) { headers.set('Authorization', `Bearer ${token}`); } // Optional: include credentials if needed by the API headers.set('credentials', 'include'); return headers; }, }), endpoints: (builder) => ({ // Fetch user profile with token in Authorization header getUserProfile: builder.query({ query: () => '/auth/me', }), }), }); export const { useGetUserProfileQuery } = usersApi;
// src/MainApp.js 从 'react' 导入 React, { useEffect, useState }; 进口 { 活动指示器, 按钮, 平面列表, 莫代尔, 刷新控制, 样式表, 文本, 文本输入, 看法, 来自 'react-native'; 从 'react-native-safe-area-context' 导入 { SafeAreaView } ; 从 'react-redux' 导入 { useDispatch, useSelector }; 从 './api/authApi' 导入 { useLoginMutation } ; 进口 { 使用CreatePostMutation, 使用DeletePostMutation, 使用GetPosts查询, 使用LazyGetPosts查询, 使用更新后突变, 来自 './api/postsApi'; 从'./api/usersApi'导入{useGetUserProfileQuery}; 导入{注销}来自“./features/auth/authSlice”; const MainApp = () =>; { const [newPostTitle, setNewPostTitle] = useState(''); const [页面,setPage] = useState(1); const [postsData, setPostsData] = useState([]); const [刷新,setRefreshing] = useState(false); const [isModalVisible, setModalVisible] = useState(false); const 调度 = useDispatch(); const token = useSelector((state) => state.auth.token); // 登录突变 const [login, { isLoading: isLoggingIn }] = useLoginMutation(); // 当令牌可用时获取用户配置文件 const { 数据:userProfile,重新获取:refetchUserProfile } = useGetUserProfileQuery(未定义,{ 跳过:!令牌, }); // 获取分页帖子 常量{ 数据:帖子, 正在加载, 正在获取, 是错误, 重新获取, } = useGetPostsQuery({ 页数, 限制: 10 }); // 当你想在屏幕加载时获取数据时,使用 useQuery 钩子。例如,在个人资料屏幕上获取用户个人资料。 // 使用惰性查询刷新直接获取第1页 const [triggerFetchFirstPage,{ 数据:lazyData }] = useLazyGetPostsQuery(); // useLazyquery 当你想要控制 api 调用时使用,比如按钮点击。 const [createPost] = useCreatePostMutation(); const [updatePost] = useUpdatePostMutation(); const [deletePost] = useDeletePostMutation(); useEffect(() => { 如果(帖子){ setPostsData((prevData) => (页 === 1 ? posts : [...prevData, ...posts])); } }, [帖子, 页]); // 登录处理程序 const handleLogin = async () =>; { 尝试 { const 凭证 = { 用户名:'emilys',密码:'emilyspass' }; 等待登录(凭据); console.log('用户配置文件', 用户配置文件); 重新获取用户配置文件(); } 捕获(错误){ console.error('登录失败:', error); } }; const handleRefresh = async () =>; { 设置刷新(真); 设置页面(1); // 将页面重置为 1 以进行下一个滚动 setPostsData([]); // 清除数据以避免重复 // 显式触发第一页获取 const { 数据 } = 等待触发FetchFirstPage({ 页数: 1, 限制: 10 }); 如果(数据){ 设置帖子数据(数据); // 将帖子数据设置为第一页的结果 } 设置刷新(假); }; // 创建一个新帖子,将其添加到顶部,然后重新获取列表 const handleCreatePost = async () =>; { 如果(新帖子标题){ const { data: newPost } = wait createPost({ title: newPostTitle, body: '新帖子内容' }); 设置新帖子标题(''); setPostsData((prevData) => [newPost, ...prevData]); 重新获取(); } }; // 更新现有帖子并将“HASAN”添加到其标题中 const handleUpdatePost = async (post) =>; { const { 数据:updatePost } = 等待 updatePost({ id:帖子id, 标题: `${post.title} HASAN`, }); setPostsData((prevData) =>; prevData.map((item) => (item?.id === UpdatedPost?.id ?updatedPost : item)) ); }; // 删除帖子并立即将其从 UI 中删除 const handleDeletePost = async (id) =>; { 等待deletePost(id); setPostsData((prevData) => prevData.filter((post) => post.id !== id)); }; // 加载更多帖子以实现无限滚动 const loadMorePosts = () =>; { if (!isFetching) { setPage((上一页) => 上一页 1); } }; // 切换模态可见性 consttoggleModal = () =>; { setModalVisible(!isModalVisible); }; if (isLoading && page === 1) return <text>正在加载...</text>; if (isError) return <text> 获取帖子时出错。</text>; 返回 (
您的 React Native 应用程序使用 Redux Toolkit (RTK) 查询 来实现高效的数据管理和 API 交互。设置包括:
存储配置:带有 redux-persist 的 Redux 存储,用于跨应用程序会话保存特定数据,用于错误日志记录的自定义中间件,以及用于在开发模式下进行调试的 Reactotron。
带 RTK 查询的 API:
Auth Slice:管理身份验证令牌并提供在登录/注销时设置或清除令牌的操作。
应用程序和主应用程序组件:
完整代码->
以上是通过 RTK 查询在 React Native 中高效处理数据的详细内容。更多信息请关注PHP中文网其他相关文章!