このガイドでは以下について説明します:
RTK Query は、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 Query を使用します:
本質的に、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 ストア (src/store/store.js): Redux ストアは、アプリケーションの状態を保持する主要な構造です。セットアップでは、Redux 状態の特定の部分をローカルに保存する redux-persist によって拡張され、アプリが再起動しても保持されます。
redux-persist:
エンハンサー: カスタム エンハンサーは、開発モードで Reactotron を統合するために使用されます。これは、Redux アクション、状態、およびネットワーク リクエストをデバッグするための便利なツールです。これは開発時のみ有効となり、運用環境に影響を与えることなくデバッグが容易になります。
ミドルウェア:
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'; 輸入 { アクティビティインジケーター、 ボタン、 フラットリスト、 モーダル、 リフレッシュコントロール、 スタイルシート、 文章、 テキスト入力、 ビュー、 「反応ネイティブ」から; import { SafeAreaView } から 'react-native-safe-area-context'; import { useDispatch, useSelector } から 'react-redux'; import { useLoginMutation } から './api/authApi'; 輸入 { useCreatePostMutation、 useDeletePostMutation、 useGetPostsQuery、 useLazyGetPostsQuery、 UpdatePostMutation を使用する、 './api/postsApi' から; import { useGetUserProfileQuery } から './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 token = useSelector((state) => state.auth.token); // ログインの突然変異 const [login, { isLoading: isLoggingIn }] = useLoginMutation(); // トークンが利用可能な場合にユーザー プロファイルを取得します const { データ: userProfile, refetch: refetchUserProfile } = useGetUserProfileQuery(未定義, { スキップ: !トークン、 }); // ページ分割された投稿を取得します 定数{ データ: 投稿、 読み込み中、 フェッチ中、 エラーです、 再フェッチ、 } = 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, ...posts])); } }, [投稿、ページ]); // ログインハンドラ const handleLogin = async () => { 試す { const credentials = { ユーザー名: 'emilys'、パスワード: 'emilyspass' }; ログインを待ちます(認証情報); console.log('ユーザープロファイル', ユーザープロファイル); refetchUserProfile(); } キャッチ (エラー) { console.error('ログインに失敗しました:', エラー); } }; const handleRefresh = async () => { setRefreshing(true); setPage(1); // 次のスクロールのためにページを 1 にリセットします setPostsData([]); // 重複を避けるためにデータをクリアします // 最初のページのフェッチを明示的にトリガーします const { data } = awaittriggerFetchFirstPage({ ページ: 1, 制限: 10 }); if (データ) { setPostsData(データ); // 投稿データを最初のページの結果に設定します } setRefreshing(false); }; // 新しい投稿を作成して先頭に追加し、リストを再取得します const handleCreatePost = async () => { if (newPostTitle) { const { data: newPost } = await createPost({ title: newPostTitle, body: '新規投稿コンテンツ' }); setNewPostTitle(''); setPostsData((prevData) => [newPost, ...prevData]); 再フェッチ(); } }; // 既存の投稿を更新し、タイトルに「HASAN」を追加します const handleUpdatePost = async (post) => { const { データ: updatedPost } = updatePostを待ちます({ id: post.id、 タイトル: `${post.title} ハサン`, }); 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)); }; // 無限スクロールのためにさらに投稿を読み込みます constloadMorePosts = () => { if (!isFetching) { setPage((前のページ) => 前のページ 1); } }; // モーダルの可視性を切り替えます const toggleModal = () => { setModalVisible(!isModalVisible); }; if (isLoading && page === 1) return <Text>Loading...</Text>; if (isError) return <Text>投稿の取得中にエラーが発生しました。</Text>; 戻る (
React Native アプリは、効率的なデータ管理と API 対話のために Redux Toolkit (RTK) Query を使用します。セットアップには以下が含まれます:
ストア構成: アプリセッション間で特定のデータを保存する redux-persist を備えた Redux ストア、エラーログ用のカスタムミドルウェア、開発モードでのデバッグ用の Reactotron。
RTK クエリを使用した API:
認証スライス: 認証トークンを管理し、ログイン/ログアウト時にトークンを設定またはクリアするためのアクションを提供します。
アプリと MainApp コンポーネント:
完全なコード->
以上がRTK クエリを使用した React Native での効率的なデータ処理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。