RTK Queryクイックスタート
- Redux Toolkitのデータ取得機能「RTK Query」の設定方法と使用方法
- Reduxの用語と概念の理解
はじめに
Redux Toolkit Queryチュートリアルへようこそ!**このチュートリアルでは、Redux Toolkitのデータ取得機能「RTK Query」について簡単に紹介し、正しく使い始める方法を説明します**。
RTK Queryの詳細なチュートリアルについては、Reduxコアドキュメントサイトの完全な「Redux Essentials」チュートリアルを参照してください。
RTK Queryは、Webアプリケーションでデータを読み込む際の一般的なケースを簡素化するために設計された、高度なデータ取得およびキャッシュツールです。RTK Query自体はRedux Toolkitコアの上に構築されており、createSlice
やcreateAsyncThunk
などのRTKのAPIを活用してその機能を実装しています。
RTK Queryは、@reduxjs/toolkit
パッケージに追加アドオンとして含まれています。Redux Toolkitを使用する際にRTK Query APIを使用する必要はありませんが、多くのユーザーがアプリケーションでRTK Queryのデータ取得とキャッシュの恩恵を受けられると考えています。
このチュートリアルの読み方
このチュートリアルでは、ReactでRedux Toolkitを使用していると想定していますが、他のUIレイヤーでも使用できます。例は、すべてのアプリケーションコードがsrc
にある典型的なCreate-React-Appフォルダー構造に基づいていますが、パターンは使用しているプロジェクトまたはフォルダーのセットアップに適応させることができます。
ストアとAPIサービスの設定
RTK Queryの仕組みを確認するために、基本的な使用例を説明します。この例では、Reactを使用しており、RTK Queryの自動生成されたReactフックを利用したいと想定しています。
APIサービスの作成
まず、公開されているPokeAPIにクエリを実行するサービス定義を作成します。
- TypeScript
- JavaScript
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Pokemon } from './types'
// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query<Pokemon, string>({
query: (name) => `pokemon/${name}`,
}),
}),
})
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi
// Need to use the React-specific entry point to import createApi
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query({
query: (name) => `pokemon/${name}`,
}),
}),
})
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi
RTK Queryでは、通常、API定義全体を1か所に定義します。これは、swr
やreact-query
などの他のライブラリで見られるものとは大きく異なる可能性があり、それにはいくつかの理由があります。私たちの視点では、アプリケーション全体に散らばったX個のカスタムフックを持つ場合と比較して、リクエスト、キャッシュの無効化、および一般的なアプリ構成の動作をすべて1つの中央の場所で追跡する方が*はるかに*簡単です。
通常、アプリケーションが通信する必要があるベースURLごとに1つのAPIスライスのみを持つ必要があります。たとえば、サイトが/api/posts
と/api/users
の両方からデータを取得する場合、ベースURLとして/api/
を持つ単一のAPIスライスと、posts
とusers
の個別のエンドポイント定義が必要です。これにより、エンドポイント全体でタグ関係を定義することで、自動再取得を効果的に活用できます。
保守性の目的で、すべてのエンドポイントを含む単一のAPIスライスを維持しながら、エンドポイント定義を複数のファイルに分割することができます。injectEndpoints
プロパティを使用して他のファイルからAPIエンドポイントを単一のAPIスライス定義に挿入する方法については、コード分割を参照してください。
ストアにサービスを追加する
RTKQサービスは、Reduxルートレデューサーに含める必要がある「スライスのレデューサー」と、データの取得を処理するカスタムミドルウェアを生成します。どちらもReduxストアに追加する必要があります。
- TypeScript
- JavaScript
import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { setupListeners } from '@reduxjs/toolkit/query'
import { pokemonApi } from './services/pokemon'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(pokemonApi.middleware),
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { setupListeners } from '@reduxjs/toolkit/query'
import { pokemonApi } from './services/pokemon'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(pokemonApi.middleware),
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
アプリケーションをProvider
でラップする
まだ行っていない場合は、ReduxストアをReactアプリケーションコンポーネントツリーの残りの部分に提供するための標準パターンに従ってください。
- TypeScript
- JavaScript
import * as React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import { store } from './store'
const rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
import * as React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import { store } from './store'
const rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
コンポーネントでクエリを使用する
サービスが定義されたら、フックをインポートしてリクエストを行うことができます。
- TypeScript
- JavaScript
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'
export default function App() {
// Using a query hook automatically fetches data and returns query values
const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
// Individual hooks are also accessible under the generated endpoints:
// const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery('bulbasaur')
return (
<div className="App">
{error ? (
<>Oh no, there was an error</>
) : isLoading ? (
<>Loading...</>
) : data ? (
<>
<h3>{data.species.name}</h3>
<img src={data.sprites.front_shiny} alt={data.species.name} />
</>
) : null}
</div>
)
}
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'
export default function App() {
// Using a query hook automatically fetches data and returns query values
const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
// Individual hooks are also accessible under the generated endpoints:
// const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery('bulbasaur')
return (
<div className="App">
{error ? (
<>Oh no, there was an error</>
) : isLoading ? (
<>Loading...</>
) : data ? (
<>
<h3>{data.species.name}</h3>
<img src={data.sprites.front_shiny} alt={data.species.name} />
</>
) : null}
</div>
)
}
リクエストを行うときは、いくつかの方法で状態を追跡できます。レンダリングする適切なUIを決定するために、常にdata
、status
、およびerror
を確認できます。さらに、useQuery
は、最新のリクエストのためにisLoading
、isFetching
、isSuccess
、およびisError
などのユーティリティブール値も提供します。
基本的な例
なるほど、面白いです...しかし、複数のポケモンを同時に表示したい場合はどうでしょうか?複数のコンポーネントが同じポケモンを読み込むとどうなりますか?
高度な例
RTK Queryは、同じクエリを購読するコンポーネントが常に同じデータを使用することを保証します。RTK Queryはリクエストを自動的に重複排除するため、飛行中のリクエストとパフォーマンスの最適化を自分でチェックする必要はありません。以下のサンドボックスを評価してみましょう。ブラウザの開発ツールの[ネットワーク]パネルを確認してください。購読済みコンポーネントが4つあるにもかかわらず、3つのリクエストが表示されます。bulbasaur
は1つのリクエストのみを行い、読み込み状態は2つのコンポーネント間で同期されます。楽しみのために、ドロップダウンの値を[オフ]から[1秒]に変更して、クエリが再実行されたときにこの動作が続くことを確認してください。