이 가이드에서 다룰 내용은 다음과 같습니다.
RTK 쿼리는 Redux Toolkit(RTK)에 내장된 고급 데이터 가져오기 및 캐싱 도구입니다. 데이터 가져오기, 캐싱, 업데이트와 같은 일반적인 작업을 위한 Redux 슬라이스 및 후크를 생성하여 API 상호 작용을 간소화합니다. 주요 기능은 다음과 같습니다:
React 쿼리와 RTK 쿼리는 모두 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 쿼리는 Redux 중심 애플리케이션에 탁월한 반면, React 쿼리는 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 저장소는 애플리케이션의 상태를 유지하는 주요 구조입니다. 설정에서 Redux 상태의 특정 부분을 로컬에 저장하는 redux-persist가 향상되어 앱을 다시 시작해도 지속됩니다.
redux-지속성:
Enhancer: 사용자 정의 Enhancer는 Redux 작업, 상태 및 네트워크 요청을 디버깅하는 데 유용한 도구인 Reactotron을 개발 모드에 통합하는 데 사용됩니다. 이는 개발 중에만 활성화되므로 프로덕션에 영향을 주지 않고 디버깅이 더 쉬워집니다.
미들웨어:
setupListeners: 이 기능을 사용하면 앱이 포커스를 다시 얻거나 백그라운드에서 다시 시작할 때와 같이 특정 이벤트가 발생할 때 데이터를 자동으로 다시 가져올 수 있어 수동으로 새로 고칠 필요 없이 사용자에게 새로운 데이터를 제공할 수 있습니다.
RTK 쿼리는 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 import React, { useEffect, useState } from 'react'; 수입 { 활동표시기, 단추, 플랫리스트, 모달, 새로고침컨트롤, 스타일시트, 텍스트, 텍스트 입력, 보다, } '반응 네이티브'에서; 'react-native-safe-area-context'에서 { SafeAreaView }를 가져옵니다. import { useDispatch, useSelector } from 'react-redux'; import { useLoginMutation } from './api/authApi'; 수입 { useCreatePostMutation, useDeletePostMutation, useGetPostsQuery, useLazyGetPostsQuery, useUpdatePostMutation, } './api/postsApi'에서; import { useGetUserProfileQuery } from './api/usersApi'; 가져오기 { 로그아웃 }'./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 토큰 = useSelector((state) => state.auth.token); // 로그인 돌연변이 const [login, { isLoading: isLoggingIn }] = useLoginMutation(); // 토큰을 사용할 수 있을 때 사용자 프로필을 가져옵니다. const { 데이터: userProfile, 다시 가져오기: refetchUserProfile } = useGetUserProfileQuery(정의되지 않음, { 건너뛰기: !토큰, }); // 페이지가 매겨진 게시물을 가져옵니다. const { 데이터: 게시물, 로드 중, 가져오는 중, isError, 다시 가져오기, } = useGetPostsQuery({ 페이지, 제한: 10 }); // useQuery 후크는 화면 로드 시 데이터를 가져오려는 경우에 사용됩니다. 예를 들어 프로필 화면에서 사용자 프로필을 가져옵니다. // 페이지 1을 직접 가져오기 위해 새로고침 지연 쿼리를 사용합니다. const [triggerFetchFirstPage, { data:lazyData }] = useLazyGetPostsQuery(); // useLazyquery는 버튼 클릭과 같이 API 호출을 제어하려는 경우에 사용됩니다. const [createPost] = useCreatePostMutation(); const [updatePost] = useUpdatePostMutation(); const [deletePost] = useDeletePostMutation(); useEffect(() => { if (게시물) { setPostsData((prevData) => (페이지 === 1 ? 게시물: [...prevData, ...게시물])); } }, [게시물, 페이지]); // 로그인 핸들러 const handlerLogin = async () => { 노력하다 { const 자격 증명 = { 사용자 이름: 'emilys', 비밀번호: 'emilyspass' }; 로그인을 기다립니다(자격 증명); console.log('userProfile', userProfile); refetchUserProfile(); } 잡기(오류) { console.error('로그인 실패:', error); } }; const handlerRefresh = async () => { setRefreshing(true); setPage(1); // 다음 스크롤을 위해 페이지를 1로 재설정합니다. setPostsData([]); // 중복을 피하기 위해 데이터를 지웁니다. // 첫 번째 페이지 가져오기를 명시적으로 트리거합니다. const { data } = TriggerFetchFirstPage({ 페이지: 1, 제한: 10 })를 기다립니다. if (데이터) { setPostsData(데이터); // 게시물 데이터를 첫 번째 페이지의 결과로 설정합니다. } setRefreshing(false); }; // 새 게시물을 작성하여 상단에 추가하고 목록을 다시 가져옵니다. const handlerCreatePost = async () => { if (newPostTitle) { const { data: newPost } = createPost({ title: newPostTitle, body: '새 게시물 콘텐츠' })를 기다리고 있습니다. setNewPostTitle(''); setPostsData((prevData) => [newPost, ...prevData]); 다시 가져오기(); } }; // 기존 게시물을 업데이트하고 제목에 "HASAN"을 추가합니다. const handlerUpdatePost = 비동기(포스트) => { const { data:updatePost } = updatePost를 기다립니다({ id: post.id, 제목: `${post.title} 하산`, }); setPostsData((prevData) => prevData.map((item) => (item?.id ===updatePost?.id ?updatePost : item)) ); }; // 게시물을 삭제하고 UI에서 즉시 제거합니다. const handlerDeletePost = async (id) => { deletePost(id)를 기다립니다; setPostsData((prevData) => prevData.filter((post) => post.id !== id)); }; // 무한 스크롤을 위해 더 많은 게시물 로드 const loadMorePosts = () => { if (!isFetching) { setPage((prevPage) => prevPage 1); } }; // 모달 가시성 전환 const 토글모달 = () => { setModalVisible(!isModalVisible); }; if (isLoading && page === 1) return <Text>Loading...</Text> if (isError) return <Text>게시물 가져오기 오류.</Text> 반품 ( <SafeAreaView> <ul> <li> <strong>MainApp 구성 요소(src/MainApp.js)</strong>: <ul> <li> <strong>상태 및 후크</strong>: 로컬 상태(예: 게시물 페이지 매김) 및 useLoginMutation과 같은 후크를 관리하여 특정 이벤트에 대한 작업을 트리거합니다.</li> <li> <strong>로그인</strong>: <ul> <li>useLoginMutation을 사용하여 사용자를 로그인한 다음 refetchUserProfile을 트리거하여 사용자 프로필 데이터를 로드합니다.</li> <li> <em>조건부 쿼리</em>: 유효한 토큰이 존재할 때만 사용자 프로필을 가져오므로(건너뛰기: !token) 불필요한 API 호출을 줄입니다.</li> </ul> </li> <li> <strong>게시물 가져오는 중</strong>: <ul> <li>useGetPostsQuery를 사용하여 페이지가 매겨진 게시물을 가져오고 사용자가 스크롤할 때 더 많은 데이터를 가져와 무한 스크롤을 지원합니다.</li> <li> <em>새로고침 제어</em>: 사용자가 게시물 목록을 새로 고칠 수 있으며, 모바일에서 당겨서 새로 고침 기능에 유용합니다.</li> </ul> </li> <li> <strong>게시물 생성, 업데이트, 삭제</strong>: <ul> <li> <em>Create</em>: createPost를 호출하여 게시물 목록을 상단에 새 게시물로 즉시 업데이트합니다.</li> <li> <em>업데이트</em>: 업데이트 시 게시물 제목에 "HASAN"을 추가합니다.</li> <li> <em>삭제</em>: deletePost의 캐시 무효화 덕분에 페이지를 다시 로드할 필요 없이 게시물을 제거하고 UI를 업데이트합니다.</li> </ul> </li> <li> <strong>UI 요소</strong>: <ul> <li>모달은 사용자 프로필을 표시합니다. 프로필 버튼은 userProfile 데이터가 로드된 경우에만 나타나므로 사용자 경험이 향상됩니다.</li> </ul> </li> <li> <strong>FlatList</strong>: 게시물을 스크롤 가능하고 페이지가 매겨진 형식으로 표시하여 유용성을 높입니다.</li> </ul> </li> </ul> <hr> <h2> 요약: </h2> <p>React Native 앱은 효율적인 데이터 관리 및 API 상호 작용을 위해 <strong>Redux Toolkit(RTK) 쿼리</strong>를 사용합니다. 설정에는 다음이 포함됩니다.</p> <ol> <li><p><strong>스토어 구성</strong>: 앱 세션 전체에서 특정 데이터를 저장하기 위한 redux-persist, 오류 로깅을 위한 맞춤형 미들웨어, 개발 모드에서 디버깅을 위한 Reactotron을 갖춘 Redux 스토어.</p></li> <li> <p><strong>RTK 쿼리가 포함된 API</strong>:</p><ul> <li> <strong>authApi</strong>는 로그인 변형으로 인증을 처리하고 토큰을 Redux에 저장합니다.</li> <li> <strong>postsApi</strong>는 게시물이 추가, 업데이트 또는 삭제될 때 캐시 태그를 사용하여 자동으로 데이터를 새로 고치는 게시물에 대한 CRUD 작업을 제공합니다.</li> <li> <strong>usersApi</strong>는 동적 토큰 기반 인증 헤더를 사용하여 사용자 프로필을 가져옵니다.</li> </ul> </li> <li><p><strong>Auth Slice</strong>: 인증 토큰을 관리하고 로그인/로그아웃 시 토큰을 설정하거나 삭제하는 작업을 제공합니다.</p></li> <li> <p><strong>앱 및 MainApp 구성요소</strong>:</p> <ul> <li>메인 앱은 Provider 및 PersistGate의 구성 요소를 래핑하여 렌더링 전에 상태가 로드되도록 합니다.</li> <li> MainApp은 게시물 가져오기, 생성, 업데이트 및 삭제를 관리합니다. 조건부로 데이터를 로드하고(예: 토큰이 존재할 때만 사용자 프로필 가져오기) 페이지 매김 및 무한 스크롤을 지원합니다. </li> <li>페이지가 매겨진 게시물 목록에는 FlatList를 사용하고, 프로필에는 모달을 사용하고, 깔끔하고 정리된 레이아웃을 위한 기본 스타일을 사용합니다.</li> </ul> </li> </ol> <blockquote> <p>전체 코드->
위 내용은 RTK 쿼리를 사용한 React Native의 효율적인 데이터 처리의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!