Heim >Web-Frontend >js-Tutorial >Effiziente Datenverarbeitung in React Native mit RTK-Abfrage
In diesem Leitfaden behandeln wir Folgendes:
RTK Query ist ein erweitertes Datenabruf- und Caching-Tool, das in Redux Toolkit (RTK) integriert ist. Es optimiert API-Interaktionen durch die Generierung von Redux-Slices und Hooks für häufige Aufgaben wie das Abrufen, Zwischenspeichern und Aktualisieren von Daten. Zu den Hauptmerkmalen gehören:
Sowohl React Query als auch RTK Query bieten Lösungen für das Abrufen und Zwischenspeichern von Daten in React-Anwendungen, haben jedoch unterschiedliche Stärken und Anwendungsfälle:
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. |
Wahl zwischen RTK-Abfrage und Reaktionsabfrage:
RTK-Abfrage verwenden wenn:
React Query verwenden wenn:
Im Wesentlichen eignet sich RTK Query hervorragend für Redux-zentrierte Anwendungen, während React Query Flexibilität und Einfachheit für Projekte ohne Redux oder solche mit einem stärker lokalisierten Fokus auf die Serverstatusverwaltung bietet.
// 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): Der Redux Store ist die Hauptstruktur, die den Status der Anwendung speichert. In Ihrem Setup wird es um redux-persist erweitert, um bestimmte Teile des Redux-Status lokal zu speichern, sodass sie auch beim Neustart der App bestehen bleiben.
redux-persist:
Enhancer: Benutzerdefinierte Enhancer werden verwendet, um Reactotron in den Entwicklungsmodus zu integrieren, ein hilfreiches Tool zum Debuggen von Redux-Aktionen, Status und Netzwerkanforderungen. Dies wird nur in der Entwicklung aktiviert und erleichtert das Debuggen, ohne die Produktion zu beeinträchtigen.
Middleware:
setupListeners: Diese Funktion ermöglicht das automatische erneute Abrufen von Daten, wenn bestimmte Ereignisse auftreten, z. B. wenn die App den Fokus wiedererlangt oder aus dem Hintergrund fortfährt, sodass Benutzer ohne manuelle Aktualisierung mit neuen Daten versorgt werden.
RTK Query vereinfacht API-Aufrufe durch automatische Generierung von Redux-Slices, Hooks und Caching. Hier ist eine Aufschlüsselung der von Ihnen definierten APIs:
// 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 } from 'react'; importieren { Aktivitätsindikator, Taste, FlatList, Modal, RefreshControl, StyleSheet, Text, Texteingabe, Sicht, } von 'react-native'; import { SafeAreaView } aus 'react-native-safe-area-context'; import { useDispatch, useSelector } from 'react-redux'; import { useLoginMutation } from './api/authApi'; importieren { useCreatePostMutation, useDeletePostMutation, useGetPostsQuery, useLazyGetPostsQuery, useUpdatePostMutation, } from './api/postsApi'; import { useGetUserProfileQuery } from './api/usersApi'; importieren {abmelden}von './features/auth/authSlice'; const MainApp = () => { const [newPostTitle, setNewPostTitle] = useState(''); const [page, setPage] = useState(1); const [postsData, setPostsData] = useState([]); const [refreshing, setRefreshing] = useState(false); const [isModalVisible, setModalVisible] = useState(false); const distribution = useDispatch(); const token = useSelector((state) => state.auth.token); // Login-Mutation const [login, { isLoading: isLoggingIn }] = useLoginMutation(); // Benutzerprofil abrufen, wenn Token verfügbar ist const { data: userProfile, refetch: refetchUserProfile } = useGetUserProfileQuery(undefiniert, { überspringen: !token, }); // Paginierte Beiträge abrufen const { Daten: Beiträge, wird geladen, isFetching, isError, erneut abrufen, } = useGetPostsQuery({ page, limit: 10 }); // Der useQuery-Hook wird verwendet, wenn Sie Daten beim Laden des Bildschirms abrufen möchten. Rufen Sie beispielsweise das Benutzerprofil auf dem Profilbildschirm ab. // Verwenden Sie die Lazy-Abfrage für die Aktualisierung, um Seite 1 direkt abzurufen const [triggerFetchFirstPage, { data: lazyData }] = useLazyGetPostsQuery(); // useLazyquery wird verwendet, wenn Sie den API-Aufruf steuern möchten, z. B. beim Klicken auf eine Schaltfläche. const [createPost] = useCreatePostMutation(); const [updatePost] = useUpdatePostMutation(); const [deletePost] = useDeletePostMutation(); useEffect(() => { if (Beiträge) { setPostsData((prevData) => (page === 1 ? posts : [...prevData, ...posts])); } }, [Beiträge, Seite]); // Login-Handler const handleLogin = async () => { versuchen { const credentials = { Benutzername: 'emilys', Passwort: 'emilyspass' }; Warten Sie auf die Anmeldung (Anmeldeinformationen); console.log('userProfile', userProfile); refetchUserProfile(); } Catch (Fehler) { console.error('Anmeldung fehlgeschlagen:', Fehler); } }; const handleRefresh = async () => { setRefreshing(true); setPage(1); // Setze die Seite für die nächsten Scrolls auf 1 zurück setPostsData([]); // Löschen Sie die Daten, um Duplikate zu vermeiden // Den ersten Seitenabruf explizit auslösen const { data } = waiting triggerFetchFirstPage({ page: 1, limit: 10 }); if (Daten) { setPostsData(data); // Setze die Beitragsdaten auf die Ergebnisse der ersten Seite } setRefreshing(false); }; // Einen neuen Beitrag erstellen, ihn oben hinzufügen und die Liste erneut abrufen const handleCreatePost = async () => { if (newPostTitle) { const { data: newPost } = waiting createPost({ title: newPostTitle, body: 'New post content' }); setNewPostTitle(''); setPostsData((prevData) => [newPost, ...prevData]); refetch(); } }; // Einen vorhandenen Beitrag aktualisieren und „HASAN“ zum Titel hinzufügen const handleUpdatePost = async (post) => { const { data: updatePost } = waiting updatePost({ ID: post.id, Titel: `${post.title} HASAN`, }); setPostsData((prevData) => prevData.map((item) => (item?.id === aktualisierterBeitrag?.id ? aktualisierterBeitrag: Artikel)) ); }; // Einen Beitrag löschen und ihn sofort aus der Benutzeroberfläche entfernen const handleDeletePost = async (id) => { Warten auf deletePost(id); setPostsData((prevData) => prevData.filter((post) => post.id !== id)); }; // Laden Sie mehr Beiträge für endloses Scrollen const loadMorePosts = () => { if (!isFetching) { setPage((prevPage) => prevPage 1); } }; // Modale Sichtbarkeit umschalten const toggleModal = () => { setModalVisible(!isModalVisible); }; if (isLoading && page === 1) return <Text>Loading...</Text>; if (isError) return <Text>Fehler beim Abrufen von Beiträgen.</Text>; zurückkehren ( <SafeAreaView> <ul> <li> <strong>MainApp-Komponente (src/MainApp.js)</strong>: <ul> <li> <strong>Status und Hooks</strong>: Verwaltet lokale Status (z. B. für die Paginierung von Beiträgen) und Hooks wie useLoginMutation, um Aktionen bei bestimmten Ereignissen auszulösen.</li> <li> <strong>Anmelden</strong>: <ul> <li>Verwendet useLoginMutation, um den Benutzer anzumelden, und löst dann refetchUserProfile aus, um die Benutzerprofildaten zu laden.</li> <li> <em>Bedingte Abfrage</em>: Ruft das Benutzerprofil nur ab, wenn ein gültiges Token vorhanden ist (überspringen: !token), wodurch unnötige API-Aufrufe reduziert werden.</li> </ul> </li> <li> <strong>Beiträge abrufen</strong>: <ul> <li>Verwendet useGetPostsQuery zum Abrufen paginierter Beiträge und unterstützt das unendliche Scrollen, indem mehr Daten abgerufen werden, während der Benutzer scrollt.</li> <li> <em>Aktualisierungssteuerung</em>: Ermöglicht Benutzern das Aktualisieren der Beitragsliste, nützlich für die Pull-to-Refresh-Funktion auf Mobilgeräten.</li> </ul> </li> <li> <strong>Beiträge erstellen, aktualisieren, löschen</strong>: <ul> <li> <em>Erstellen</em>: Ruft createPost auf und aktualisiert sofort die Beitragsliste mit dem neuen Beitrag ganz oben.</li> <li> <em>Aktualisieren</em>: Hängt beim Aktualisieren „HASAN“ an den Titel eines Beitrags an.</li> <li> <em>Löschen</em>: Entfernt einen Beitrag und aktualisiert die Benutzeroberfläche, ohne dass die Seite neu geladen werden muss, dank der Cache-Ungültigmachung von deletePost.</li> </ul> </li> <li> <strong>UI-Elemente</strong>: <ul> <li>Ein Modal zeigt das Benutzerprofil an. Die Profilschaltfläche wird nur angezeigt, wenn Benutzerprofildaten geladen sind, was das Benutzererlebnis verbessert.</li> </ul> </li> <li> <strong>FlatList</strong>: Zeigt Beiträge in einem scrollbaren, paginierten Format an und verbessert so die Benutzerfreundlichkeit.</li> </ul> </li> </ul> <hr> <h2> Zusammenfassung: </h2> <p>Ihre React Native-App verwendet <strong>Redux Toolkit (RTK) Query</strong> für effiziente Datenverwaltung und API-Interaktionen. Das Setup umfasst:</p> <ol> <li><p><strong>Store-Konfiguration</strong>: Redux-Store mit Redux-Persist zum Speichern spezifischer Daten über App-Sitzungen hinweg, eine benutzerdefinierte Middleware für die Fehlerprotokollierung und Reactotron für das Debuggen im Entwicklungsmodus.</p></li> <li> <p><strong>APIs mit RTK-Abfrage</strong>:</p><ul> <li> <strong>authApi</strong> übernimmt die Authentifizierung mit einer Login-Mutation und speichert das Token in Redux.</li> <li> <strong>postsApi</strong> stellt CRUD-Operationen für Beiträge bereit und verwendet Cache-Tags, um Daten automatisch zu aktualisieren, wenn Beiträge hinzugefügt, aktualisiert oder gelöscht werden.</li> <li> <strong>usersApi</strong> ruft das Benutzerprofil mit dynamischen tokenbasierten Autorisierungsheadern ab.</li> </ul> </li> <li><p><strong>Auth Slice</strong>: Verwaltet das Authentifizierungstoken und stellt Aktionen zum Festlegen oder Löschen des Tokens beim Anmelden/Abmelden bereit.</p></li> <li> <p><strong>App- und MainApp-Komponenten</strong>:</p> <ul> <li>Die Haupt-App verpackt Komponenten in Provider und PersistGate und stellt so sicher, dass der Status vor dem Rendern geladen wird.</li> <li> MainApp verwaltet das Abrufen, Erstellen, Aktualisieren und Löschen von Beiträgen. Es lädt Daten bedingt (z. B. ruft das Benutzerprofil nur ab, wenn ein Token vorhanden ist), unterstützt Paginierung und unendliches Scrollen </li> <li>Verwendet FlatList für eine paginierte Beitragsliste, Modalitäten für das Profil und grundlegende Stile für ein sauberes, organisiertes Layout.</li> </ul> </li> </ol> <blockquote> <p>VOLLSTÄNDIGER CODE->
Das obige ist der detaillierte Inhalt vonEffiziente Datenverarbeitung in React Native mit RTK-Abfrage. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!