自動再フェッチ
デフォルトのキャッシュ動作で説明されているように、クエリのエンドポイントに対してサブスクリプションが追加された場合、キャッシュデータがまだ存在しない場合にのみリクエストが送信されます。存在する場合は、既存のデータが代わりに提供されます。
RTK Queryは、ミューテーションエンドポイントによってデータが影響を受けるクエリエンドポイントの再フェッチを自動化するために、「キャッシュタグ」システムを使用します。これにより、特定のミューテーションを発生させると、特定のクエリエンドポイントでキャッシュされたデータが無効と見なされ、アクティブなサブスクリプションがある場合はデータを再フェッチするようにAPIを設計できます。
各エンドポイント+パラメーターの組み合わせは、独自のqueryCacheKey
を提供します。キャッシュタグシステムにより、特定のクエリキャッシュが特定のタグを提供したことをRTK Queryに通知することができます。クエリキャッシュが提供したタグをinvalidate
するミューテーションが実行された場合、キャッシュされたデータは無効化されたと見なされ、キャッシュされたデータへのアクティブなサブスクリプションがある場合は再フェッチされます。
他の手段で再フェッチをトリガーする方法については、キャッシュ動作の操作を参照してください。
定義
タグ
RTK Queryの場合、タグは、再フェッチの目的でキャッシュと無効化の動作を制御するために、特定のデータコレクションに付けることができる単なる名前です。これは、ミューテーション後に読み取られる、キャッシュされたデータにアタッチされた「ラベル」と考えることができ、データがミューテーションの影響を受けるかどうかを決定します。
タグは、APIを定義するときにtagTypes
引数で定義されます。たとえば、Posts
とUsers
の両方を持つアプリケーションでは、createApi
を呼び出すときにtagTypes: ['Post', 'User']
を定義する場合があります。
個々のtag
には、string
名で表されるtype
と、オプションのstring
またはnumber
で表されるid
があります。プレーンな文字列('Post'
など)、または{type: string, id?: string|number}
([{type: 'Post', id: 1}]
など)の形式のオブジェクトで表すことができます。
タグの提供
クエリは、キャッシュされたデータにタグを提供させることができます。これにより、クエリによって返されるキャッシュされたデータにアタッチされる「タグ」が決まります。
providesTags
引数は、string
の配列(['Post']
など)、{type: string, id?: string|number}
([{type: 'Post', id: 1}]
など)、またはそのような配列を返すコールバックのいずれかです。この関数には、最初引数として結果、2番目の引数として応答エラー、3番目の引数として最初にquery
メソッドに渡された引数が渡されます。クエリが成功したかどうかに応じて、結果またはエラー引数が未定義になる場合があることに注意してください。
タグの無効化
ミューテーションは、タグに基づいて特定のキャッシュされたデータを無効化できます。これにより、どのキャッシュデータが再フェッチされるか、またはキャッシュから削除されるかが決定されます。
invalidatesTags
引数は、string
の配列(['Post']
など)、{type: string, id?: string|number}
([{type: 'Post', id: 1}]
など)、またはそのような配列を返すコールバックのいずれかです。この関数には、最初引数として結果、2番目の引数として応答エラー、3番目の引数として最初にquery
メソッドに渡された引数が渡されます。ミューテーションが成功したかどうかに応じて、結果またはエラー引数が未定義になる場合があることに注意してください。
キャッシュタグ
RTK Queryは、あるエンドポイントのミューテーションが、別のエンドポイントからのクエリによって提供されたデータを無効化することを意図しているかどうかを判断するために、「タグ」の概念を使用します。
キャッシュデータが無効化されている場合、提供元のクエリを再フェッチするか(コンポーネントがまだそのデータを使用している場合)、データをキャッシュから削除します。
APIスライスを定義するとき、createApi
はtagTypes
プロパティのタグタイプ名の配列を受け入れます。これは、APIスライスのクエリが提供できる可能性のあるタグ名オプションのリストです。
以下の例では、エンドポイントがキャッシュに「Posts」および/または「Users」を提供する可能性があることを宣言しています。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
}),
getUsers: build.query<User[], void>({
query: () => '/users',
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
}),
getUsers: build.query({
query: () => '/users',
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
これらのタグをキャッシュに提供できるものとして宣言することにより、個々のミューテーションエンドポイントが、個々のエンドポイントのprovidesTags
およびinvalidatesTags
と組み合わせて、キャッシュの特定の部分に影響を与えるかどうかを主張するための制御が可能になります。
キャッシュデータの提供
個々のquery
エンドポイントは、キャッシュされたデータに特定のタグを提供させることができます。これにより、1つ以上のクエリエンドポイントからのキャッシュされたデータと、1つ以上のミューテーションエンドポイントの動作との関係が可能になります。
query
エンドポイントのprovidesTags
プロパティがこの目的に使用されます。
提供されるタグは、個別のquery
エンドポイント間で固有の関係を持ちません。提供されるタグは、エンドポイントによって返されるキャッシュされたデータがinvalidated
されるべきかどうか、および再フェッチされるかキャッシュから削除されるかを判断するために使用されます。2つの別々のエンドポイントが同じタグを提供する場合でも、それらは独自の別個のキャッシュされたデータを提供し、後でミューテーションから宣言された単一のタグによって両方を無効化することができます。
以下の例では、getPosts
query
エンドポイントが、query
エンドポイントのprovidesTags
プロパティを使用して、キャッシュに'Post'
タグをprovides
することを宣言しています。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'posts',
method: 'POST',
body,
}),
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'posts',
method: 'POST',
body,
}),
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
提供されたデータをより細かく制御するために、提供されたtags
は関連付けられたid
を持つことができます。これにより、「特定のタグタイプのいずれか」と「特定のタグタイプの特定のインスタンス」を区別できます。
以下の例では、提供された投稿は、エンドポイントによって返された結果によって決定される特定のIDに関連付けられていることを宣言しています
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), 'Post']
: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
}),
}),
})
上記の例では、成功した結果では可能な限りid
が使用されていることに注意してください。エラーが発生した場合、結果は提供されず、特定のタグのインスタンスではなく、一般的な'Post'
タグタイプを提供したと見なされます。
適切なデータの無効化をより強力に制御するために、特定のタグに対して'LIST'
などの任意のIDを使用できます。詳細については、抽象タグIDによる高度な無効化を参照してください。
キャッシュデータの無効化
個々のミューテーションエンドポイントは、既存のキャッシュデータの特定のタグをinvalidate
できます。これにより、1つ以上のクエリエンドポイントからのキャッシュされたデータと、1つ以上のミューテーションエンドポイントの動作との関係が可能になります。
ミューテーションエンドポイントのinvalidatesTags
プロパティがこの目的に使用されます。
以下の例では、addPost
およびeditPost
ミューテーションエンドポイントが、ミューテーションエンドポイントのinvalidatesTags
プロパティを使用して、'Post'
タグを持つキャッシュされたデータをinvalidate
することを宣言しています。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), 'Post']
: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
}),
})
上記の例では、addPost
および/またはeditPost
ミューテーションが呼び出されて完了した後、'Post'
タグが付けられたキャッシュデータは有効ではなくなることをRTK Queryに通知します。上記のミューテーションが呼び出されて完了した後、コンポーネントが現在'Post'
タグのキャッシュされたデータにサブスクライブしている場合、サーバーから最新のデータを取得するために自動的に再フェッチされます。
シナリオの例は次のようになります
useGetPostsQuery()
フックを使用して、そのエンドポイントのキャッシュされたデータにサブスクライブするコンポーネントがレンダリングされます/posts
リクエストが開始され、サーバーがID 1、2、および3の投稿で応答しますgetPosts
エンドポイントは、受信したデータをキャッシュに保存し、次のタグが提供されたことを内部的に登録します[
{ type: 'Post', id: 1 },
{ type: 'Post', id: 2 },
{ type: 'Post', id: 3 },
]- 特定の投稿を変更するために
editPost
ミューテーションが開始されます - 完了すると、RTK Queryは内部的に
'Post'
タグが無効になったことを登録し、以前に提供された'Post'
タグをキャッシュから削除します。 getPosts
エンドポイントが'Post'
型のタグを提供しており、そのキャッシュデータが無効になったため、コンポーネントがまだデータにサブスクライブしている場合、/posts
リクエストが自動的に再度発行され、新しいデータを取得し、更新されたキャッシュデータのために新しいタグを登録します。
無効化されたデータをより細かく制御するために、無効化されたtags
はprovidesTags
と同じように関連付けられたid
を持つことができます。これにより、「特定のタグタイプのいずれか」と「特定のタグタイプの特定のインスタンス」を区別できます。
以下の例では、editPost
ミューテーションがPost
タグの特定のインスタンスを無効にすることを宣言しています。ミューテーション関数を呼び出すときに渡されるIDを使用します。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post' as const, id })), 'Post']
: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => '/posts',
providesTags: (result, error, arg) =>
result
? [...result.map(({ id }) => ({ type: 'Post', id })), 'Post']
: ['Post'],
}),
getUsers: build.query({
query: () => '/users',
providesTags: ['User'],
}),
addPost: build.mutation({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }],
}),
}),
})
上記の例では、タイプが'Post'
のタグを無効化するのではなく、editPost
ミューテーション関数を呼び出すと、提供されたid
のタグのみが無効になります。つまり、エンドポイントからのキャッシュデータが同じid
の'Post'
を提供しない場合、それは「有効」とみなされ、自動的に再フェッチされることはありません。
適切なデータの無効化をより強力に制御するために、特定のタグに対して'LIST'
などの任意のIDを使用できます。詳細については、抽象タグIDによる高度な無効化を参照してください。
タグの無効化動作
以下のマトリックスは、無効化されたタグがどの提供されたタグに影響を与え、無効化するかの例を示しています。
提供済み 無効化済み | 一般的なタグ A ['Post'] / [{ type: 'Post' }] | 一般的なタグ B ['User'] / [{ type: 'User' }] | 特定のタグ A1 [{ type: 'Post', id: 1 }] | 特定のタグ A2 [{ type: 'Post', id: 'LIST' }] | 特定のタグ B1 [{ type: 'User', id: 1 }] | 特定のタグ B2 [{ type: 'User', id: 2 }] |
---|---|---|---|---|---|---|
一般的なタグ A ['Post'] / [{ type: 'Post' }] | ✔️ | ✔️ | ✔️ | |||
一般的なタグ B ['User'] / [{ type: 'User' }] | ✔️ | ✔️ | ✔️ | |||
特定のタグ A1 [{ type: 'Post', id: 1 }] | ✔️ | |||||
特定のタグ A2 [{ type: 'Post', id: 'LIST' }] | ✔️ | |||||
特定のタグ B1 [{ type: 'User', id: 1 }] | ✔️ | |||||
特定のタグ B2 [{ type: 'User', id: 2 }] | ✔️ |
無効化の動作は、以下のセクションのタグの具体性に基づいて要約されます。
一般的なタグ
例:['Post'] / [{ type: 'Post' }]
一致するタイプを持つ、一般的および特定のタグを含む、すべてのprovided
タグをinvalidate
します。
例
Post
の一般的なタグが無効化された場合、次のタグをprovided
したデータを持つエンドポイントはすべて、そのデータが無効化されます。
['Post']
[{ type: 'Post' }]
[{ type: 'Post' }, { type: 'Post', id: 1 }]
[{ type: 'Post', id: 1 }]
[{ type: 'Post', id: 1 }, { type: 'User' }]
[{ type: 'Post', id: 'LIST' }]
[{ type: 'Post', id: 1 }, { type: 'Post', id: 'LIST' }]
次のタグをprovided
したデータを持つエンドポイントは、データが無効化されません。
['User']
[{ type: 'User' }]
[{ type: 'User', id: 1 }]
[{ type: 'User', id: 'LIST' }]
[{ type: 'User', id: 1 }, { type: 'User', id: 'LIST' }]
特定のタグ
例:[{ type: 'Post', id: 1 }]
一致するタイプと一致するIDの両方を持つprovided
タグをinvalidate
します。general
タグを直接無効にすることはありませんが、general
タグも提供する場合、一致するspecific
タグを提供するエンドポイントのデータを無効にする可能性があります。
例1:{ type: 'Post', id: 1 }
の特定のタグが無効化された場合、次のタグをprovided
したデータを持つエンドポイントはすべて、そのデータが無効化されます。
[{ type: 'Post' }, { type: 'Post', id: 1 }]
[{ type: 'Post', id: 1 }]
[{ type: 'Post', id: 1 }, { type: 'User' }]
[{ type: 'Post', id: 1 }, { type: 'Post', id: 'LIST' }]
次のタグをprovided
したデータを持つエンドポイントは、データが無効化されません。
['Post']
[{ type: 'Post' }]
[{ type: 'Post', id: 'LIST' }]
['User']
[{ type: 'User' }]
[{ type: 'User', id: 1 }]
[{ type: 'User', id: 'LIST' }]
[{ type: 'User', id: 1 }, { type: 'User', id: 'LIST' }]
例2:{ type: 'Post', id: 'LIST' }
の特定のタグが無効化された場合、次のタグをprovided
したデータを持つエンドポイントはすべて、そのデータが無効化されます。
[{ type: 'Post', id: 'LIST' }]
[{ type: 'Post', id: 1 }, { type: 'Post', id: 'LIST' }]
次のタグをprovided
したデータを持つエンドポイントは、データが無効化されません。
['Post']
[{ type: 'Post' }]
[{ type: 'Post' }, { type: 'Post', id: 1 }]
[{ type: 'Post', id: 1 }]
[{ type: 'Post', id: 1 }, { type: 'User' }]
['User']
[{ type: 'User' }]
[{ type: 'User', id: 1 }]
[{ type: 'User', id: 'LIST' }]
[{ type: 'User', id: 1 }, { type: 'User', id: 'LIST' }]
レシピ
抽象的なタグIDを使用した高度な無効化
タグのid
に「エンティティID」を使用するのが一般的なユースケースですが、id
プロパティはデータベースIDのみに限定されるものではありません。id
は、特定のtag type
の特定のデータコレクションのサブセットにラベルを付けるための単なる方法です。
強力なユースケースは、バルククエリによって提供されるデータのラベルとして'LIST'
のようなIDを使用することです。また、個々のアイテムにエンティティIDを使用します。そうすることで、将来のmutations
は、特定のアイテムが含まれる場合にのみデータを無効にするか(例:{ type: 'Post', id: 5 }
)、'LIST'
の場合にデータを無効にするか(例:{ type: 'Post', id: 'LIST' }
)を宣言できます。
LIST
は任意の文字列です。厳密に言えば、ALL
や*
など、ここで好きなものを使用できます。カスタムIDを選択する際に重要なことは、クエリ結果によって返されるIDと衝突する可能性がないことを確認することです。クエリ結果に不明なIDがあり、リスクを冒したくない場合は、以下の3番目の点を使用できます。- さらに多くの制御のために、多数のタグタイプを追加できます。
[{ type: 'Posts', id: 'LIST' }, { type: 'Posts', id: 'SVELTE_POSTS' }, { type: 'Posts', id: 'REACT_POSTS' }]
'LIST'
のようなid
の使用の概念が奇妙に思える場合は、常に別のtagType
を追加してそのルートを無効にできますが、示されているようにid
アプローチを使用することをお勧めします。
以下のシナリオを比較して、'LIST'
IDを使用して動作を最適化する方法を確認できます。
あるタイプのすべてを無効化する
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post, User } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result ? result.map(({ id }) => ({ type: 'Posts', id })) : ['Posts'],
}),
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({
url: `post`,
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery, useAddPostMutation } = api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query({
query: () => 'posts',
providesTags: (result) =>
result ? result.map(({ id }) => ({ type: 'Posts', id })) : ['Posts'],
}),
addPost: build.mutation({
query: (body) => ({
url: `post`,
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery, useAddPostMutation } = api
function App() {
const { data: posts } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
return (
<div>
<AddPost onAdd={addPost} />
<PostsList />
{/* Assume each PostDetail is subscribed via `const {data} = useGetPostQuery(id)` */}
<PostDetail id={1} />
<PostDetail id={2} />
<PostDetail id={3} />
</div>
)
}
予想されること
addPost
がトリガーされると、addPost
がルートタグを無効化するため、各PostDetail
コンポーネントはisFetching
状態に戻り、これにより、'Posts'を提供するすべてのクエリが再実行されます。ほとんどの場合、これはやりたいことではないかもしれません。画面に100件の投稿があり、すべてがgetPost
クエリにサブスクライブしていると想像してください。この場合、100件のリクエストを作成し、サーバーに大量の不要なトラフィックを送信することになります。これは、そもそも回避しようとしていることです!ユーザーは最後に良好なキャッシュ結果を確認し、ブラウザの不具合以外には何も気づかない可能性がありますが、それでもこれを回避する必要があります。
リストを選択的に無効化する
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Post, User } from './types'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts' as const, id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useAddPostMutation, useGetPostQuery } = api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query({
query: () => 'posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts', id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})
export const { useGetPostsQuery, useAddPostMutation, useGetPostQuery } = api
function App() {
const { data: posts } = useGetPostsQuery()
const [addPost] = useAddPostMutation()
return (
<div>
<AddPost onAdd={addPost} />
<PostsList />
{/* Assume each PostDetail is subscribed via `const {data} = useGetPostQuery(id)` */}
<PostDetail id={1} />
<PostDetail id={2} />
<PostDetail id={3} />
</div>
)
}
予想されること
addPost
が発行されると、addPost
が'LIST'
IDのみを無効化するため、PostsList
のみがisFetching
状態になります。これにより、getPosts
が再実行されます(特定のIDを提供するため)。したがって、ネットワークタブでは、GET /posts
に対して1つの新しいリクエストが発行されるだけです。単数形のgetPost
クエリは無効化されていないため、addPost
の結果として再実行されません。
addPost
ミューテーションが個々のPostDetail
コンポーネントを含むすべての投稿を更新することを意図しており、同時に新しいGET /posts
リクエストを1つだけ作成する場合は、selectFromResult
を使用してデータの一部を選択することで実行できます。
エラーをキャッシュに提供する
キャッシュに提供される情報は、正常なデータフェッチに限定されません。この概念を使用して、特定のエラーが発生した場合、その失敗したキャッシュデータに特定のtag
をprovide
するようにRTK Queryに通知できます。別のエンドポイントは、そのtag
のデータをinvalidate
することができ、コンポーネントがまだ失敗したデータにサブスクライブしている場合、RTK Queryに以前に失敗したエンドポイントを再試行するように指示します。
以下の例は、次の動作の例を示しています。
- クエリが
401 UNAUTHORIZED
のエラーコードで失敗した場合、UNAUTHORIZED
キャッシュタグを提供します。 - クエリが異なるエラーで失敗した場合、
UNKNOWN_ERROR
キャッシュタグを提供します。 - 成功した場合、
UNAUTHORIZED
タグを使用してデータをinvalidate
する「ログイン」ミューテーションを有効にします。
これは、postById
エンドポイントが次の場合に再発行されることをトリガーします。postById
の最後の呼び出しで、未承認のエラーが発生した場合、および- コンポーネントがまだキャッシュデータにサブスクライブしている場合
- 呼び出されたときに、
UNKNOWN_ERROR
タグを使用してデータをinvalidate
する「refetchErroredQueries」ミューテーションを有効にします。
これは、postById
エンドポイントが次の場合に再発行されることをトリガーします。postById
の最後の呼び出しで、不明なエラーが発生した場合、および- コンポーネントがまだキャッシュデータにサブスクライブしている場合
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { Post, LoginResponse } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'UNAUTHORIZED', 'UNKNOWN_ERROR'],
endpoints: (build) => ({
postById: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) =>
result
? [{ type: 'Post', id }]
: error?.status === 401
? ['UNAUTHORIZED']
: ['UNKNOWN_ERROR'],
}),
login: build.mutation<LoginResponse, void>({
query: () => '/login',
// on successful login, will refetch all currently
// 'UNAUTHORIZED' queries
invalidatesTags: (result) => (result ? ['UNAUTHORIZED'] : []),
}),
refetchErroredQueries: build.mutation<null, void>({
queryFn: () => ({ data: null }),
invalidatesTags: ['UNKNOWN_ERROR'],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'UNAUTHORIZED', 'UNKNOWN_ERROR'],
endpoints: (build) => ({
postById: build.query({
query: (id) => `post/${id}`,
providesTags: (result, error, id) =>
result
? [{ type: 'Post', id }]
: error?.status === 401
? ['UNAUTHORIZED']
: ['UNKNOWN_ERROR'],
}),
login: build.mutation({
query: () => '/login',
// on successful login, will refetch all currently
// 'UNAUTHORIZED' queries
invalidatesTags: (result) => (result ? ['UNAUTHORIZED'] : []),
}),
refetchErroredQueries: build.mutation({
queryFn: () => ({ data: null }),
invalidatesTags: ['UNKNOWN_ERROR'],
}),
}),
})
一般的なprovides/invalidatesの使用を抽象化する
特定のAPIスライスのタグをprovide
およびinvalidate
するために記述されたコードは、次のようないくつかの要因に依存します。
- バックエンドによって返されるデータの形状
- 特定のクエリエンドポイントが提供すると予想されるタグ
- 特定のミューテーションエンドポイントが無効化すると予想されるタグ
- 無効化機能を使用したい範囲
APIスライスを宣言するとき、コードが重複していると感じるかもしれません。たとえば、特定のエントリのリストを提供する2つの別々のエンドポイントの場合、providesTags
宣言は提供されるtagType
のみが異なる場合があります。
例:
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => `posts`,
providesTags: (result) =>
result
? [
{ type: 'Post', id: 'LIST' },
...result.map(({ id }) => ({ type: 'Post' as const, id })),
]
: [{ type: 'Post', id: 'LIST' }],
}),
getUsers: build.query<User[], void>({
query: () => `users`,
providesTags: (result) =>
result
? [
{ type: 'User', id: 'LIST' },
...result.map(({ id }) => ({ type: 'User' as const, id })),
]
: [{ type: 'User', id: 'LIST' }],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) =>
result
? [
{ type: 'Post', id: 'LIST' },
...result.map(({ id }) => ({ type: 'Post', id })),
]
: [{ type: 'Post', id: 'LIST' }],
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) =>
result
? [
{ type: 'User', id: 'LIST' },
...result.map(({ id }) => ({ type: 'User', id })),
]
: [{ type: 'User', id: 'LIST' }],
}),
}),
})
特定のAPI用に設計されたヘルパー関数を定義して、エンドポイント定義全体でこのボイラープレートを削減すると便利かもしれません。例:
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
function providesList<R extends { id: string | number }[], T extends string>(
resultsWithIds: R | undefined,
tagType: T
) {
return resultsWithIds
? [
{ type: tagType, id: 'LIST' },
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
]
: [{ type: tagType, id: 'LIST' }]
}
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) => providesList(result, 'Post'),
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) => providesList(result, 'User'),
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
function providesList(resultsWithIds, tagType) {
return resultsWithIds
? [
{ type: tagType, id: 'LIST' },
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
]
: [{ type: tagType, id: 'LIST' }]
}
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) => providesList(result, 'Post'),
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) => providesList(result, 'User'),
}),
}),
})
一般的なrestデータ形式用に設計された、タグの提供/無効化のためのさまざまな抽象化の例を、次のgistで見ることができます。これには、typescriptのサポート、および'LIST'スタイルの高度なタグ無効化と'エラー'スタイルのタグ無効化の両方が含まれています:RTK Queryキャッシュユーティリティ。