Dalam panduan ini, kami akan membincangkan:
Pertanyaan RTK ialah alat pengambilan data dan caching termaju yang terbina dalam Redux Toolkit (RTK). Ia menyelaraskan interaksi API dengan menjana hirisan dan cangkuk Redux untuk tugas biasa seperti mengambil, menyimpan cache dan mengemas kini data. Ciri utama termasuk:
Kedua-dua React Query dan RTK Query menyediakan penyelesaian untuk pengambilan data dan caching dalam aplikasi React, tetapi mereka mempunyai kekuatan dan kes penggunaan yang berbeza:
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. |
Memilih Antara Pertanyaan RTK dan Pertanyaan Reaksi:
Gunakan Pertanyaan RTK jika:
Gunakan React Query jika:
Pada dasarnya, RTK Query cemerlang untuk aplikasi Redux-centric, manakala React Query memberikan fleksibiliti dan kesederhanaan untuk projek tanpa Redux atau yang mempunyai fokus pengurusan keadaan pelayan yang lebih setempat.
// 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;
Stor Redux (src/store/store.js): Stor Redux ialah struktur utama yang memegang keadaan aplikasi. Dalam persediaan anda, ia dipertingkatkan dengan redux-persist untuk menyimpan bahagian tertentu keadaan Redux secara setempat, supaya ia berterusan walaupun apabila apl dimulakan semula.
redux-berterusan:
Peningkatan: Penambah tersuai digunakan untuk menyepadukan Reactotron dalam mod pembangunan, alat yang berguna untuk menyahpepijat tindakan Redux, keadaan dan permintaan rangkaian. Ini hanya diaktifkan dalam pembangunan, menjadikan penyahpepijatan lebih mudah tanpa menjejaskan pengeluaran.
Perisian Tengah:
setupListeners: Fungsi ini mendayakan pengambilan semula data secara automatik apabila peristiwa tertentu berlaku, seperti apabila apl mendapat semula fokus atau menyambung semula dari latar belakang, memberikan pengguna data baharu tanpa muat semula manual.
Pertanyaan RTK memudahkan panggilan API dengan menjana hirisan, cangkuk dan caching Redux secara automatik. Berikut ialah pecahan API yang anda tetapkan:
// 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 import React, { useEffect, useState } daripada 'react'; import { Penunjuk Aktiviti, butang, Senarai Rata, modal, RefreshControl, Stylesheet, teks, TextInput, Lihat, } daripada 'react-native'; import { SafeAreaView } daripada 'react-native-safe-area-context'; import { useDispatch, useSelector } daripada 'react-redux'; import { useLoginMutation } daripada './api/authApi'; import { gunakanCreatePostMutation, gunakanDeletePostMutation, gunakanGetPostsQuery, gunakanLazyGetPostsQuery, useUpdatePostMutation, } daripada './api/postsApi'; import { useGetUserProfileQuery } daripada './api/usersApi'; import { logout }daripada './features/auth/authSlice'; const MainApp = () => { const [newPostTitle, setNewPostTitle] = useState(''); const [halaman, setPage] = useState(1); const [postsData, setPostsData] = useState([]); const [refreshing, setRefreshing] = useState(false); const [isModalVisible, setModalVisible] = useState(false); const dispatch = useDispatch(); token const = useSelector((state) => state.auth.token); // Mutasi log masuk const [log masuk, { isLoading: isLoggingIn }] = useLoginMutation(); // Ambil profil pengguna apabila token tersedia const { data: userProfile, refetch: refetchUserProfile } = useGetUserProfileQuery(undefined, { langkau: !token, }); // Ambil catatan bernombor const { data: siaran, sedang memuatkan, isFetching, isError, ulang semula, } = useGetPostsQuery({halaman, had: 10}); // Cangkuk useQuery digunakan apabila anda ingin mengambil data pada beban skrin. Contohnya ambil profil pengguna pada skrin profil. // Gunakan pertanyaan malas untuk muat semula untuk terus mengambil halaman 1 const [triggerFetchFirstPage, { data: lazyData }] = useLazyGetPostsQuery(); // useLazyquery digunakan apabila anda ingin mengawal panggilan api, seperti pada klik butang. const [createPost] = useCreatePostMutation(); const [updatePost] = useUpdatePostMutation(); const [deletePost] = useDeletePostMutation(); useEffect(() => { jika (siaran) { setPostsData((prevData) => (halaman === 1 ? siaran : [...prevData, ...posts])); } }, [siaran, halaman]); // Pengendali log masuk const handleLogin = async () => { cuba { kelayakan const = { nama pengguna: 'emilys', kata laluan: 'emilyspass' }; tunggu log masuk(kredential); console.log('userProfile', userProfile); refetchUserProfile(); } tangkap (ralat) { console.error('Log masuk gagal:', ralat); } }; const handleRefresh = tak segerak () => { setRefreshing(true); setPage(1); // Tetapkan semula halaman kepada 1 untuk skrol seterusnya setPostsData([]); // Kosongkan data untuk mengelakkan pertindihan // Cetuskan pengambilan halaman pertama secara eksplisit const { data } = menunggu triggerFetchFirstPage({halaman: 1, had: 10}); jika (data) { setPostsData(data); // Tetapkan data siaran kepada hasil halaman pertama } setRefreshing(false); }; // Cipta siaran baharu, tambahkannya ke bahagian atas dan dapatkan semula senarai const handleCreatePost = tak segerak () => { if (newPostTitle) { const { data: newPost } = tunggu createPost({ title: newPostTitle, body: 'New post content' }); setNewPostTitle(''); setPostsData((prevData) => [newPost, ...prevData]); ulang (); } }; // Kemas kini siaran sedia ada dan tambah "HASAN" pada tajuknya const handleUpdatePost = async (siaran) => { const { data: updatedPost } = tunggu updatePost({ id: post.id, tajuk: `${post.title} HASAN`, }); setPostsData((prevData) => prevData.map((item) => (item?.id === updatedPost?.id ? updatedPost : item)) ); }; // Padamkan siaran dan alih keluarnya daripada UI dengan serta-merta const handleDeletePost = async (id) => { tunggu deletePost(id); setPostsData((prevData) => prevData.filter((post) => post.id !== id)); }; // Muatkan lebih banyak siaran untuk menatal tanpa had const loadMorePosts = () => { jika (!isFetching) { setPage((prevPage) => prevPage 1); } }; // Togol keterlihatan modal const toggleModal = () => { setModalVisible(!isModalVisible); }; jika (sedangMemuatkan && halaman === 1) kembalikan <Teks>Memuatkan...</Teks>; jika (isError) mengembalikan <Teks>Ralat semasa mengambil siaran.</Teks>; kembali ( <SafeAreaView> <ul> <li> <strong>Komponen Aplikasi Utama (src/MainApp.js)</strong>: <ul> <li> <strong>Keadaan dan Cangkuk</strong>: Menguruskan keadaan setempat (cth., untuk penomboran catatan) dan cangkuk seperti useLoginMutation untuk mencetuskan tindakan pada acara tertentu.</li> <li> <strong>Log Masuk</strong>: <ul> <li>Menggunakan useLoginMutation untuk log masuk pengguna dan kemudian mencetuskan refetchUserProfile untuk memuatkan data profil pengguna.</li> <li> <em>Pertanyaan Bersyarat</em>: Hanya mengambil profil pengguna apabila token yang sah wujud (langkau: !token), mengurangkan panggilan API yang tidak diperlukan.</li> </ul> </li> <li> <strong>Mengambil Siaran</strong>: <ul> <li>Menggunakan useGetPostsQuery untuk mengambil siaran bernombor, menyokong penatalan tidak terhingga dengan mengambil lebih banyak data semasa pengguna menatal.</li> <li> <em>Kawalan Segar Semula</em>: Membenarkan pengguna memuat semula senarai siaran, berguna untuk fungsi tarik-untuk-segar semula pada mudah alih.</li> </ul> </li> <li> <strong>Buat, Kemas Kini, Padam Catatan</strong>: <ul> <li> <em>Buat</em>: Panggil createPost, serta-merta mengemas kini senarai siaran dengan siaran baharu di bahagian atas.</li> <li> <em>Kemas kini</em>: Menambahkan "HASAN" pada tajuk siaran apabila dikemas kini.</li> <li> <em>Padam</em>: Mengalih keluar siaran dan mengemas kini UI tanpa memerlukan muat semula halaman, terima kasih kepada ketidaksahihan cache daripada deletePost.</li> </ul> </li> <li> <strong>Elemen UI</strong>: <ul> <li>Satu modal memaparkan profil pengguna. Butang profil hanya muncul jika data Profil pengguna dimuatkan, meningkatkan pengalaman pengguna.</li> </ul> </li> <li> <strong>FlatList</strong>: Memaparkan siaran dalam format yang boleh ditatal, dinomborkan, meningkatkan kebolehgunaan.</li> </ul> </li> </ul> <hr> <h2> Ringkasan: </h2> <p>Apl React Native anda menggunakan <strong>Pertanyaan Redux Toolkit (RTK)</strong> untuk pengurusan data yang cekap dan interaksi API. Persediaan termasuk:</p> <ol> <li><p><strong>Konfigurasi Kedai</strong>: Simpan Redux dengan redux-berterusan untuk menyimpan data khusus merentas sesi apl, perisian tengah tersuai untuk pengelogan ralat dan Reactotron untuk nyahpepijat dalam mod pembangunan.</p></li> <li> <p><strong>API dengan RTK Query</strong>:</p><ul> <li> <strong>authApi</strong> mengendalikan pengesahan dengan mutasi log masuk, menyimpan token dalam Redux.</li> <li> <strong>postsApi</strong> menyediakan operasi CRUD untuk siaran, menggunakan teg cache untuk memuat semula data secara automatik apabila siaran ditambah, dikemas kini atau dipadamkan.</li> <li> <strong>usersApi</strong> mengambil profil pengguna dengan pengepala kebenaran berasaskan token dinamik.</li> </ul> </li> <li><p><strong>Auth Slice</strong>: Menguruskan token pengesahan dan menyediakan tindakan untuk menetapkan atau mengosongkan token semasa log masuk/log keluar.</p></li> <li> <p><strong>Komponen Apl dan Apl Utama</strong>:</p> <ul> <li>Apl utama membungkus komponen dalam Provider dan PersistGate, memastikan keadaan dimuatkan sebelum dipaparkan.</li> <li> MainApp mengurus pengambilan, mencipta, mengemas kini dan memadamkan siaran. Ia memuatkan data secara bersyarat (cth., mengambil profil pengguna hanya apabila token wujud), menyokong penomboran dan penatalan tak terhingga </li> <li>Menggunakan FlatList untuk senarai siaran bernombor, modal untuk profil dan gaya asas untuk susun atur yang bersih dan teratur.</li> </ul> </li> </ol> <blockquote> <p>KOD PENUH->
Atas ialah kandungan terperinci Pengendalian Data Cekap dalam React Native dengan Pertanyaan RTK. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!