在本指南中,我們將介紹:
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中文網其他相關文章!