Maison >interface Web >js tutoriel >Gestion efficace des données dans React Native avec requête RTK
Dans ce guide, nous aborderons :
RTK Query est un outil avancé de récupération de données et de mise en cache intégré à Redux Toolkit (RTK). Il rationalise les interactions API en générant des tranches et des hooks Redux pour les tâches courantes telles que la récupération, la mise en cache et la mise à jour des données. Les principales fonctionnalités incluent :
React Query et RTK Query fournissent tous deux des solutions pour la récupération et la mise en cache des données dans les applications React, mais ils ont des atouts et des cas d'utilisation différents :
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. |
Choisir entre la requête RTK et la requête React :
Utiliser la requête RTK si :
Utilisez React Query si :
Essentiellement, RTK Query excelle pour les applications centrées sur Redux, tandis que React Query offre flexibilité et simplicité pour les projets sans Redux ou ceux avec une gestion plus localisée de l'état du serveur.
// 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) : Le magasin Redux est la structure principale détenant l'état de l'application. Dans votre configuration, il est amélioré avec redux-persist pour enregistrer localement certaines parties de l'état Redux, afin qu'elles persistent même lorsque l'application redémarre.
redux-persist :
Enhancers : des Enhancers personnalisés sont utilisés pour intégrer Reactotron en mode développement, un outil utile pour déboguer les actions Redux, l'état et les requêtes réseau. Cela ne s'active qu'en développement, ce qui facilite le débogage sans affecter la production.
Middleware :
setupListeners : Cette fonction permet la récupération automatique des données lorsque certains événements se produisent, comme lorsque l'application retrouve le focus ou reprend en arrière-plan, fournissant aux utilisateurs de nouvelles données sans actualisation manuelle.
RTK Query simplifie les appels d'API en générant automatiquement des tranches Redux, des hooks et une mise en cache. Voici une répartition des API que vous avez définies :
// 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 importer React, { useEffect, useState } depuis 'react' ; importer { Indicateur d'activité, Bouton, Liste plate, Modal, ActualiserContrôle, Feuille de style, Texte, Entrée de texte, Voir, } de « réagir-natif » ; importer { SafeAreaView } depuis 'react-native-safe-area-context' ; importer { useDispatch, useSelector } depuis 'react-redux' ; importer { useLoginMutation } depuis './api/authApi' ; importer { utilisezCreatePostMutation, useDeletePostMutation, utilisezGetPostsQuery, utilisezLazyGetPostsQuery, useUpdatePostMutation, } depuis './api/postsApi'; importer { useGetUserProfileQuery } depuis './api/usersApi' ; importer { déconnexion }de './features/auth/authSlice'; const MainApp = () => { const [newPostTitle, setNewPostTitle] = useState(''); const [page, setPage] = useState(1); const [postsData, setPostsData] = useState([]); const [rafraîchissant, setRefreshing] = useState(false); const [isModalVisible, setModalVisible] = useState(false); const dispatch = useDispatch(); const token = useSelector((state) => state.auth.token); // Mutation de connexion const [login, { isLoading : isLoggingIn }] = useLoginMutation(); // Récupère le profil utilisateur lorsque le jeton est disponible const { données : userProfile, refetch : refetchUserProfile } = useGetUserProfileQuery (non défini, { sauter : !jeton, }); // Récupère les messages paginés const { données : postes, est en cours de chargement, estRécupération, estErreur, récupérer, } = useGetPostsQuery({ page, limite : 10 }); // Le hook useQuery est utilisé lorsque vous souhaitez récupérer des données lors du chargement de l'écran. Par exemple, récupérez le profil utilisateur sur l'écran de profil. // Utilisez la requête paresseuse pour actualiser pour récupérer directement la page 1 const [triggerFetchFirstPage, { data: lazyData }] = useLazyGetPostsQuery(); // useLazyquery est utilisé lorsque vous souhaitez contrôler l'appel de l'API, comme lors d'un clic sur un bouton. const [createPost] = useCreatePostMutation(); const [updatePost] = useUpdatePostMutation(); const [deletePost] = useDeletePostMutation(); useEffect(() => { si (messages) { setPostsData((prevData) => (page === 1 ? posts : [...prevData, ...posts])); } }, [messages, page]); // Gestionnaire de connexion const handleLogin = async () => { essayer { const identifiants = { nom d'utilisateur : 'emilys', mot de passe : 'emilyspass' } ; attendre la connexion (informations d'identification) ; console.log('userProfile', userProfile); refetchUserProfile(); } attraper (erreur) { console.error('Échec de la connexion :', erreur); } } ; const handleRefresh = async () => { setRefreshing(true); setPage(1); // Remet la page à 1 pour les prochains scrolls setPostsData([]); // Efface les données pour éviter les duplications // Déclenche explicitement la récupération de la première page const { data } = wait triggerFetchFirstPage({ page : 1, limite : 10 }); si (données) { setPostsData(données); // Définit les données des publications sur les résultats de la première page } setRefreshing(faux); } ; // Crée un nouveau message, l'ajoute en haut et récupère la liste const handleCreatePost = async () => { si (nouveau titre de poste) { const { data : newPost } = wait createPost ({ title : newPostTitle, body : 'Nouveau contenu du message' }); setNewPostTitle(''); setPostsData((prevData) => [newPost, ...prevData]); récupérer(); } } ; // Mettre à jour un article existant et ajouter "HASAN" à son titre const handleUpdatePost = async (post) => { const { data : updatePost } = wait updatePost ({ identifiant : post.id, titre : `${post.title} HASAN`, }); setPostsData((prevData) => prevData.map((item) => (item?.id === updatePost?.id ? updatePost : item)) ); } ; // Supprime une publication et la supprime immédiatement de l'interface utilisateur const handleDeletePost = async (id) => { attendre deletePost(id); setPostsData((prevData) => prevData.filter((post) => post.id !== id)); } ; // Charge plus de messages pour un défilement infini const loadMorePosts = () => { si (!isFetching) { setPage((prevPage) => prevPage 1); } } ; // Basculer la visibilité modale const toggleModal = () => { setModalVisible(!isModalVisible); } ; if (isLoading && page === 1) return <Text>Loading...</Text>; if (isError) return <Text>Erreur lors de la récupération des publications.</Text>; retour ( <SafeAreaView> <ul> <li> <strong>Composant MainApp (src/MainApp.js)</strong> : <ul> <li> <strong>State and Hooks</strong> : gère les états locaux (par exemple, pour la pagination des publications) et les hooks comme useLoginMutation pour déclencher des actions sur des événements spécifiques.</li> <li> <strong>Connexion</strong> : <ul> <li>Utilise useLoginMutation pour connecter l'utilisateur, puis déclenche refetchUserProfile pour charger les données du profil utilisateur.</li> <li> <em>Requête conditionnelle</em> : récupère le profil utilisateur uniquement lorsqu'un jeton valide existe (ignorer : !token), réduisant ainsi les appels d'API inutiles.</li> </ul> </li> <li> <strong>Récupération des messages</strong> : <ul> <li>Utilise useGetPostsQuery pour récupérer les publications paginées, prenant en charge le défilement infini en récupérant plus de données à mesure que l'utilisateur fait défiler.</li> <li> <em>Contrôle d'actualisation</em> : permet aux utilisateurs d'actualiser la liste des publications, utile pour la fonctionnalité d'extraction pour actualiser sur mobile.</li> </ul> </li> <li> <strong>Créer, mettre à jour, supprimer des publications</strong> : <ul> <li> <em>Create</em> : appelle createPost, mettant immédiatement à jour la liste des publications avec la nouvelle publication en haut.</li> <li> <em>Mise à jour</em> : ajoute « HASAN » au titre d'un article lors de la mise à jour.</li> <li> <em>Supprimer</em> : supprime une publication et met à jour l'interface utilisateur sans avoir besoin de recharger la page, grâce à l'invalidation du cache de deletePost.</li> </ul> </li> <li> <strong>Éléments de l'interface utilisateur</strong> : <ul> <li>Un modal affiche le profil utilisateur. Le bouton de profil n'apparaît que si les données userProfile sont chargées, améliorant ainsi l'expérience utilisateur.</li> </ul> </li> <li> <strong>FlatList</strong> : affiche les publications dans un format défilant et paginé, améliorant ainsi la convivialité.</li> </ul> </li> </ul> <hr> <h2> Résumé: </h2> <p>Votre application React Native utilise <strong>Redux Toolkit (RTK) Query</strong> pour une gestion efficace des données et des interactions API. La configuration comprend :</p> <ol> <li><p><strong>Configuration du magasin</strong> : magasin Redux avec redux-persist pour enregistrer des données spécifiques sur les sessions d'application, un middleware personnalisé pour la journalisation des erreurs et Reactotron pour le débogage en mode développement.</p></li> <li> <p><strong>API avec requête RTK</strong> :</p><ul> <li> <strong>authApi</strong> gère l'authentification avec une mutation de connexion, stockant le jeton dans Redux.</li> <li> <strong>postsApi</strong> fournit des opérations CRUD pour les publications, en utilisant des balises de cache pour actualiser automatiquement les données lorsque les publications sont ajoutées, mises à jour ou supprimées.</li> <li> <strong>usersApi</strong> récupère le profil utilisateur avec des en-têtes d'autorisation dynamiques basés sur des jetons.</li> </ul> </li> <li><p><strong>Auth Slice</strong> : gère le jeton d'authentification et fournit des actions pour définir ou effacer le jeton lors de la connexion/déconnexion.</p></li> <li> <p><strong>Composants App et MainApp</strong> :</p> <ul> <li>L'application principale enveloppe les composants dans Provider et PersistGate, garantissant que l'état est chargé avant le rendu.</li> <li> MainApp gère la récupération, la création, la mise à jour et la suppression des publications. Il charge les données de manière conditionnelle (par exemple, récupère le profil utilisateur uniquement lorsqu'un jeton existe), prend en charge la pagination et le défilement infini </li> <li>Utilise FlatList pour une liste de publications paginée, des modaux pour le profil et des styles de base pour une mise en page propre et organisée.</li> </ul> </li> </ol> <blockquote> <p>CODE COMPLET->
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!