Apollo キャッシュ操作チートシート:`cache.modify`/`writeQuery`/`readFragment` 早見表

Apollo Client でキャッシュを操作する際、「どの API を使えばいいのか」「それぞれ何が違うのか」で迷ったことはありませんか?
この記事では、Apollo Client が提供する主要なキャッシュ操作 API である cache.modify
、writeQuery
、readFragment
の 使い分け方 と 実践的なコード例 をチートシート形式でお届けします。各 API の特徴を理解することで、適切な場面で適切な方法を選べるようになり、キャッシュ操作がぐっと楽になりますよ。
Apollo Client キャッシュ操作 API 早見表
# | API | 主な用途 | 操作対象 | 戻り値 | 注意点 |
---|---|---|---|---|---|
1 | cache.modify | フィールド単位の更新・削除 | 特定オブジェクトのフィールド | 真偽値 | INVALIDATE で削除可能 |
2 | cache.writeQuery | クエリ結果全体の書き込み | クエリ全体 | Reference | 既存データを完全上書き |
3 | cache.readQuery | クエリ結果の読み取り | クエリ全体 | データまたは null | キャッシュミス時は null |
4 | cache.writeFragment | フラグメント単位の書き込み | 特定オブジェクトの一部 | Reference | id 必須、部分更新向き |
5 | cache.readFragment | フラグメント単位の読み取り | 特定オブジェクトの一部 | データまたは null | id 必須、軽量読み取り |
各 API の特徴比較表
# | 項目 | cache.modify | writeQuery / readQuery | writeFragment / readFragment |
---|---|---|---|---|
1 | 粒度 | フィールド単位 | クエリ全体 | フラグメント単位 |
2 | 型安全性 | 低(関数ベース) | 高(GraphQL 型) | 高(GraphQL 型) |
3 | 部分更新 | ★★★ | ★☆☆ | ★★☆ |
4 | 削除操作 | ★★★ | ★☆☆ | ★★☆ |
5 | 学習コスト | 中 | 低 | 中 |
6 | パフォーマンス | 高速 | 中速 | 高速 |
背景
Apollo Client のキャッシュ機構
Apollo Client は、GraphQL のクエリ結果を 正規化されたキャッシュ として保存します。この仕組みにより、同じデータへの複数回のアクセスを効率化し、UI の再レンダリングを最小限に抑えられます。
mermaidflowchart LR
query["GraphQL クエリ"] -->|実行| apollo["Apollo Client"]
apollo -->|結果を保存| cache[("正規化<br/>キャッシュ")]
cache -->|再利用| ui["React コンポーネント"]
ui -->|更新要求| modify["キャッシュ<br/>操作 API"]
modify -->|直接変更| cache
図で理解できる要点:
- Apollo Client はクエリ結果を正規化してキャッシュに保存
- UI コンポーネントはキャッシュを参照して表示
- キャッシュ操作 API で直接変更が可能
正規化キャッシュとは
Apollo Client は、各オブジェクトを __typename
と id
(または _id
)の組み合わせで一意に識別します。たとえば、以下のようなクエリ結果があったとします。
typescript// クエリ例
const GET_USER = gql`
query GetUser {
user(id: "1") {
id
name
posts {
id
title
}
}
}
`;
このクエリ結果は、キャッシュ内で以下のように正規化されます。
typescript// 正規化後のキャッシュイメージ
{
"User:1": {
__typename: "User",
id: "1",
name: "太郎",
posts: [{ __ref: "Post:101" }, { __ref: "Post:102" }]
},
"Post:101": {
__typename: "Post",
id: "101",
title: "Apollo 入門"
},
"Post:102": {
__typename: "Post",
id: "102",
title: "GraphQL 基礎"
}
}
この構造により、User
と Post
は独立して管理され、同じ Post
が複数のクエリで参照されても、キャッシュ内では 1 つのオブジェクトとして保持されます。
キャッシュ操作が必要になる場面
正規化キャッシュは強力ですが、以下のような場面では 手動でキャッシュを操作 する必要があります。
- Mutation 後のキャッシュ更新:
createPost
などの Mutation 実行後、新しいデータをキャッシュに追加したい - 楽観的 UI 更新: サーバーレスポンスを待たずに UI を先に更新したい
- ローカルデータの管理: サーバーに保存しないクライアント専用のデータを管理したい
- リスト操作の最適化: アイテムの追加・削除時に、リスト全体を再取得せずキャッシュを直接変更したい
これらのケースに対応するため、Apollo Client は複数のキャッシュ操作 API を提供しています。
課題
API が多くて使い分けが難しい
Apollo Client のキャッシュ操作 API は、用途に応じて以下のように分類されます。
# | API グループ | 代表的な API | 主な用途 |
---|---|---|---|
1 | modify 系 | cache.modify | フィールド単位の更新・削除 |
2 | Query 系 | cache.writeQuery 、cache.readQuery | クエリ全体の読み書き |
3 | Fragment 系 | cache.writeFragment 、cache.readFragment | 特定オブジェクトの部分的な読み書き |
4 | 削除系 | cache.evict 、cache.gc | キャッシュからオブジェクトを削除 |
初心者の方にとっては、「どの API をどの場面で使うべきか」が分かりにくいという課題がありますね。
各 API の違いがわからない
たとえば、「ユーザーの名前を更新する」という操作 1 つを取っても、以下のように複数の方法が存在します。
typescript// 方法 1: cache.modify を使う
cache.modify({
id: cache.identify({ __typename: 'User', id: '1' }),
fields: {
name() {
return '新しい名前';
},
},
});
typescript// 方法 2: cache.writeFragment を使う
cache.writeFragment({
id: cache.identify({ __typename: 'User', id: '1' }),
fragment: gql`
fragment UpdateName on User {
name
}
`,
data: { name: '新しい名前' },
});
typescript// 方法 3: cache.writeQuery を使う(非推奨)
cache.writeQuery({
query: GET_USER,
data: {
user: {
__typename: 'User',
id: '1',
name: '新しい名前',
posts: [
/* 既存の posts データ */
],
},
},
});
このように、複数の選択肢があるため、「どれを使うのがベストプラクティスなのか」が見えにくいですね。
実践的なコード例が少ない
公式ドキュメントには API のリファレンスが記載されていますが、「実際のプロジェクトでどう使うか」という実践例が不足しているケースがあります。特に以下のようなケースでは、コード例が欲しいところです。
- リストへのアイテム追加・削除
- ネストされたオブジェクトの更新
- 複数フィールドの同時更新
- エラーハンドリング
解決策
用途別に API を使い分ける
Apollo Client のキャッシュ操作 API は、以下の基準で使い分けるのが効果的です。
mermaidflowchart TD
start["キャッシュ操作<br/>したい"] --> q1{操作対象は?}
q1 -->|特定フィールドのみ| modify["cache.modify<br/>を使う"]
q1 -->|オブジェクト全体| q2{既存データは?}
q2 -->|クエリ結果| query["writeQuery /<br/>readQuery"]
q2 -->|特定オブジェクト| fragment["writeFragment /<br/>readFragment"]
q1 -->|削除したい| evict["cache.evict<br/>を使う"]
図で理解できる要点:
- 操作対象によって最適な API が異なる
- フィールド単位なら
modify
、オブジェクト全体ならFragment
やQuery
系を使う - 削除操作には専用の
evict
がある
cache.modify の特徴と使い所
cache.modify
は、特定のフィールドを関数で変更する API です。以下のような場面で威力を発揮します。
# | 用途 | 適している理由 |
---|---|---|
1 | リストへのアイテム追加 | 既存配列に新要素を追加できる |
2 | フィールドの削除 | INVALIDATE で削除可能 |
3 | 条件付き更新 | 関数内で条件分岐できる |
4 | 複数フィールドの同時更新 | fields オブジェクトで複数指定可能 |
基本構文
typescriptimport { InMemoryCache } from '@apollo/client';
// InMemoryCache インスタンス
const cache = new InMemoryCache();
typescript// cache.modify の基本形
cache.modify({
id: cache.identify({ __typename: 'User', id: '1' }), // 対象オブジェクトの ID
fields: {
// 更新したいフィールド名: 更新関数
name(existingName) {
return '新しい名前'; // 新しい値を返す
},
},
});
id の指定方法
cache.identify
は、__typename
と id
からキャッシュキーを生成します。
typescript// オブジェクトから ID を生成
const userId = cache.identify({
__typename: 'User',
id: '1',
});
console.log(userId); // "User:1"
typescript// ROOT_QUERY を操作する場合(クエリのルート)
cache.modify({
id: 'ROOT_QUERY',
fields: {
users(existingUsers = []) {
// ルートクエリの users フィールドを操作
return [...existingUsers];
},
},
});
fields の更新関数
fields
オブジェクト内の各関数は、以下の引数を受け取ります。
typescript// 更新関数のシグネチャ
cache.modify({
id: 'User:1',
fields: {
name(
existingValue, // 既存の値
{
readField,
DELETE,
INVALIDATE,
isReference,
toReference,
}
) {
// readField: 他のフィールドを読み取る
const currentName = readField('name');
// DELETE: フィールドを削除(非推奨)
// return DELETE;
// INVALIDATE: フィールドを無効化(推奨)
// return INVALIDATE;
return '新しい値';
},
},
});
writeQuery / readQuery の特徴と使い所
writeQuery
と readQuery
は、クエリ全体の結果を読み書き する API です。
# | API | 用途 | 戻り値 |
---|---|---|---|
1 | cache.writeQuery | クエリ結果をキャッシュに書き込む | Reference |
2 | cache.readQuery | クエリ結果をキャッシュから読み取る | データまたは null |
基本構文
typescriptimport { gql } from '@apollo/client';
// クエリの定義
const GET_USERS = gql`
query GetUsers {
users {
id
name
}
}
`;
typescript// writeQuery: クエリ結果を書き込む
cache.writeQuery({
query: GET_USERS,
data: {
users: [
{ __typename: 'User', id: '1', name: '太郎' },
{ __typename: 'User', id: '2', name: '花子' },
],
},
});
typescript// readQuery: クエリ結果を読み取る
const data = cache.readQuery({ query: GET_USERS });
console.log(data?.users); // [{ id: "1", name: "太郎" }, ...]
変数付きクエリの扱い
GraphQL クエリに変数がある場合、variables
も指定する必要があります。
typescript// 変数付きクエリ
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`;
typescript// 書き込み時に変数を指定
cache.writeQuery({
query: GET_USER,
variables: { id: '1' },
data: {
user: { __typename: 'User', id: '1', name: '太郎' },
},
});
typescript// 読み取り時にも同じ変数を指定
const data = cache.readQuery({
query: GET_USER,
variables: { id: '1' },
});
console.log(data?.user); // { id: "1", name: "太郎" }
注意点:既存データの上書き
writeQuery
は 既存のクエリ結果を完全に上書き します。部分的な更新には向いていません。
typescript// 既存データ
cache.writeQuery({
query: GET_USER,
data: {
user: {
__typename: 'User',
id: '1',
name: '太郎',
email: 'taro@example.com',
},
},
});
typescript// 部分的に更新しようとすると、他のフィールドが消える
cache.writeQuery({
query: gql`
query GetUser {
user(id: "1") {
id
name
}
}
`,
data: {
user: { __typename: 'User', id: '1', name: '次郎' },
// email フィールドが失われる!
},
});
writeFragment / readFragment の特徴と使い所
writeFragment
と readFragment
は、特定のオブジェクトの一部を読み書き する API です。
# | API | 用途 | 戻り値 |
---|---|---|---|
1 | cache.writeFragment | フラグメント単位でキャッシュに書き込む | Reference |
2 | cache.readFragment | フラグメント単位でキャッシュから読み取る | データまたは null |
基本構文
typescriptimport { gql } from '@apollo/client';
// フラグメントの定義
const USER_FRAGMENT = gql`
fragment UserInfo on User {
id
name
email
}
`;
typescript// writeFragment: フラグメントを書き込む
cache.writeFragment({
id: cache.identify({ __typename: 'User', id: '1' }),
fragment: USER_FRAGMENT,
data: {
__typename: 'User',
id: '1',
name: '太郎',
email: 'taro@example.com',
},
});
typescript// readFragment: フラグメントを読み取る
const user = cache.readFragment({
id: cache.identify({ __typename: 'User', id: '1' }),
fragment: USER_FRAGMENT,
});
console.log(user); // { id: "1", name: "太郎", email: "taro@example.com" }
部分更新が可能
writeFragment
は、指定したフィールドのみを更新 します。他のフィールドは保持されます。
typescript// 既存データ
cache.writeFragment({
id: 'User:1',
fragment: gql`
fragment UserFull on User {
id
name
email
age
}
`,
data: {
__typename: 'User',
id: '1',
name: '太郎',
email: 'taro@example.com',
age: 25,
},
});
typescript// name だけを更新
cache.writeFragment({
id: 'User:1',
fragment: gql`
fragment UserName on User {
name
}
`,
data: {
name: '次郎',
},
});
typescript// 他のフィールドは保持される
const user = cache.readFragment({
id: 'User:1',
fragment: gql`
fragment UserFull on User {
id
name
email
age
}
`,
});
console.log(user); // { id: "1", name: "次郎", email: "taro@example.com", age: 25 }
フラグメントの再利用
フラグメントは、クエリや Mutation と共有できます。
typescript// フラグメント定義
const USER_FRAGMENT = gql`
fragment UserInfo on User {
id
name
email
}
`;
typescript// クエリで使用
const GET_USER = gql`
${USER_FRAGMENT}
query GetUser($id: ID!) {
user(id: $id) {
...UserInfo
}
}
`;
typescript// キャッシュ操作でも同じフラグメントを使用
const user = cache.readFragment({
id: 'User:1',
fragment: USER_FRAGMENT,
});
具体例
例 1: リストへのアイテム追加(cache.modify)
Mutation で新しい投稿を作成した後、投稿リストに追加する例です。
typescriptimport { gql, useMutation } from '@apollo/client';
// Mutation の定義
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
content
}
}
`;
typescript// カスタムフック内での使用
function useCreatePost() {
const [createPost] = useMutation(CREATE_POST, {
update(cache, { data }) {
// 新しく作成された投稿
const newPost = data?.createPost;
if (!newPost) return;
// キャッシュを変更してリストに追加
cache.modify({
id: 'ROOT_QUERY', // ルートクエリを操作
fields: {
posts(existingPosts = []) {
// 新しい投稿への参照を作成
const newPostRef = cache.writeFragment({
data: newPost,
fragment: gql`
fragment NewPost on Post {
id
title
content
}
`,
});
// 既存のリストに追加
return [...existingPosts, newPostRef];
},
},
});
},
});
return createPost;
}
typescript// コンポーネントでの使用例
function CreatePostForm() {
const createPost = useCreatePost();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await createPost({
variables: {
title: '新しい投稿',
content: '内容です',
},
});
};
return (
<form onSubmit={handleSubmit}>
{/* フォーム要素 */}
</form>
);
}
ポイント:
ROOT_QUERY
を操作してルートレベルのリストを更新writeFragment
で新しいオブジェクトへの参照を作成existingPosts
にデフォルト値[]
を設定してエラー回避
例 2: リストからのアイテム削除(cache.evict + cache.gc)
投稿を削除する際、キャッシュからも完全に削除する例です。
typescriptimport { gql, useMutation } from '@apollo/client';
// Mutation の定義
const DELETE_POST = gql`
mutation DeletePost($id: ID!) {
deletePost(id: $id)
}
`;
typescript// カスタムフック内での使用
function useDeletePost() {
const [deletePost] = useMutation(DELETE_POST, {
update(cache, { data }, { variables }) {
if (!data?.deletePost) return;
const postId = variables?.id;
if (!postId) return;
// キャッシュから投稿を削除
cache.evict({
id: cache.identify({
__typename: 'Post',
id: postId,
}),
});
// ガベージコレクションで孤立した参照を削除
cache.gc();
},
});
return deletePost;
}
typescript// コンポーネントでの使用例
function PostItem({
post,
}: {
post: { id: string; title: string };
}) {
const deletePost = useDeletePost();
const handleDelete = async () => {
await deletePost({
variables: { id: post.id },
});
};
return (
<div>
<h3>{post.title}</h3>
<button onClick={handleDelete}>削除</button>
</div>
);
}
ポイント:
cache.evict
で特定のオブジェクトをキャッシュから削除cache.gc()
で参照されなくなったオブジェクトを削除- リストからも自動的に削除される
例 3: ネストされたオブジェクトの更新(readFragment + writeFragment)
ユーザーの投稿の 1 つを更新する例です。
typescriptimport { gql, useMutation } from '@apollo/client';
// Mutation の定義
const UPDATE_POST = gql`
mutation UpdatePost($id: ID!, $title: String!) {
updatePost(id: $id, title: $title) {
id
title
}
}
`;
typescript// カスタムフック内での使用
function useUpdatePost() {
const [updatePost] = useMutation(UPDATE_POST, {
update(cache, { data }) {
const updatedPost = data?.updatePost;
if (!updatedPost) return;
// 既存の投稿データを読み取る
const existingPost = cache.readFragment({
id: cache.identify({
__typename: 'Post',
id: updatedPost.id,
}),
fragment: gql`
fragment ExistingPost on Post {
id
title
content
}
`,
});
// 新しいデータで更新
if (existingPost) {
cache.writeFragment({
id: cache.identify({
__typename: 'Post',
id: updatedPost.id,
}),
fragment: gql`
fragment UpdatedPost on Post {
title
}
`,
data: {
title: updatedPost.title,
},
});
}
},
});
return updatePost;
}
typescript// コンポーネントでの使用例
function EditPostForm({ postId }: { postId: string }) {
const updatePost = useUpdatePost();
const [title, setTitle] = React.useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await updatePost({
variables: { id: postId, title },
});
};
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button type='submit'>更新</button>
</form>
);
}
ポイント:
readFragment
で既存データを確認writeFragment
で必要なフィールドのみを更新- 他のフィールド(
content
など)は保持される
例 4: 複数フィールドの同時更新(cache.modify)
ユーザーの複数のフィールドを一度に更新する例です。
typescriptimport { gql, useMutation } from '@apollo/client';
// Mutation の定義
const UPDATE_USER = gql`
mutation UpdateUser(
$id: ID!
$name: String!
$email: String!
) {
updateUser(id: $id, name: $name, email: $email) {
id
name
email
updatedAt
}
}
`;
typescript// カスタムフック内での使用
function useUpdateUser() {
const [updateUser] = useMutation(UPDATE_USER, {
update(cache, { data }) {
const updatedUser = data?.updateUser;
if (!updatedUser) return;
// 複数フィールドを同時に更新
cache.modify({
id: cache.identify({
__typename: 'User',
id: updatedUser.id,
}),
fields: {
name() {
return updatedUser.name;
},
email() {
return updatedUser.email;
},
updatedAt() {
return updatedUser.updatedAt;
},
},
});
},
});
return updateUser;
}
typescript// コンポーネントでの使用例
function UserProfileForm({ userId }: { userId: string }) {
const updateUser = useUpdateUser();
const [name, setName] = React.useState('');
const [email, setEmail] = React.useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await updateUser({
variables: { id: userId, name, email },
});
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='名前'
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
placeholder='メール'
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type='submit'>更新</button>
</form>
);
}
ポイント:
fields
オブジェクトで複数フィールドを一度に指定- 各フィールドに対して関数を定義
- 他のフィールドに影響を与えずに更新可能
例 5: 条件付き更新(cache.modify + readField)
いいね数を増やす際、既存の値を読み取って条件付きで更新する例です。
typescriptimport { gql, useMutation } from '@apollo/client';
// Mutation の定義
const LIKE_POST = gql`
mutation LikePost($id: ID!) {
likePost(id: $id) {
id
likes
isLiked
}
}
`;
typescript// カスタムフック内での使用
function useLikePost() {
const [likePost] = useMutation(LIKE_POST, {
update(cache, { data }) {
const likedPost = data?.likePost;
if (!likedPost) return;
cache.modify({
id: cache.identify({
__typename: 'Post',
id: likedPost.id,
}),
fields: {
likes(existingLikes = 0, { readField }) {
// 既に「いいね」済みかチェック
const isLiked = readField('isLiked');
if (isLiked) {
// すでに「いいね」済みなら変更しない
return existingLikes;
}
// 「いいね」数を増やす
return existingLikes + 1;
},
isLiked() {
return true;
},
},
});
},
});
return likePost;
}
typescript// コンポーネントでの使用例
function LikeButton({ postId }: { postId: string }) {
const likePost = useLikePost();
const handleLike = async () => {
await likePost({
variables: { id: postId },
});
};
return <button onClick={handleLike}>いいね</button>;
}
ポイント:
readField
で他のフィールドの値を参照- 条件分岐で重複操作を防止
- 複数フィールドを連動して更新
例 6: 楽観的 UI 更新(optimisticResponse + cache.modify)
サーバーレスポンスを待たずに UI を先に更新する例です。
typescriptimport { gql, useMutation } from '@apollo/client';
// Mutation の定義
const CREATE_COMMENT = gql`
mutation CreateComment($postId: ID!, $content: String!) {
createComment(postId: $postId, content: $content) {
id
content
createdAt
author {
id
name
}
}
}
`;
typescript// カスタムフック内での使用
function useCreateComment(currentUser: {
id: string;
name: string;
}) {
const [createComment] = useMutation(CREATE_COMMENT, {
// 楽観的レスポンス:サーバーからの応答を待たずに UI を更新
optimisticResponse: (variables) => ({
__typename: 'Mutation',
createComment: {
__typename: 'Comment',
id: `temp-${Date.now()}`, // 一時的な ID
content: variables.content,
createdAt: new Date().toISOString(),
author: {
__typename: 'User',
id: currentUser.id,
name: currentUser.name,
},
},
}),
update(cache, { data }, { variables }) {
const newComment = data?.createComment;
if (!newComment) return;
cache.modify({
id: cache.identify({
__typename: 'Post',
id: variables.postId,
}),
fields: {
comments(existingComments = []) {
const newCommentRef = cache.writeFragment({
data: newComment,
fragment: gql`
fragment NewComment on Comment {
id
content
createdAt
author {
id
name
}
}
`,
});
return [...existingComments, newCommentRef];
},
},
});
},
});
return createComment;
}
typescript// コンポーネントでの使用例
function CommentForm({ postId }: { postId: string }) {
const currentUser = { id: '1', name: '太郎' }; // 実際はログインユーザーを取得
const createComment = useCreateComment(currentUser);
const [content, setContent] = React.useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await createComment({
variables: { postId, content },
});
setContent('');
};
return (
<form onSubmit={handleSubmit}>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button type='submit'>コメント</button>
</form>
);
}
ポイント:
optimisticResponse
で即座に UI を更新- サーバーからの応答後、一時的な ID が正式な ID に置き換わる
- ネットワーク遅延があっても、ユーザーはすぐに結果を確認できる
例 7: エラーハンドリング(readQuery のエラー処理)
キャッシュにデータが存在しない場合のエラーハンドリング例です。
typescriptimport {
gql,
useMutation,
ApolloCache,
} from '@apollo/client';
const GET_USERS = gql`
query GetUsers {
users {
id
name
}
}
`;
typescriptconst ADD_USER = gql`
mutation AddUser($name: String!) {
addUser(name: $name) {
id
name
}
}
`;
typescriptfunction useAddUser() {
const [addUser] = useMutation(ADD_USER, {
update(cache: ApolloCache<any>, { data }) {
const newUser = data?.addUser;
if (!newUser) return;
try {
// キャッシュからクエリ結果を読み取る
const existingData = cache.readQuery({
query: GET_USERS,
});
if (existingData?.users) {
// キャッシュが存在する場合、新しいユーザーを追加
cache.writeQuery({
query: GET_USERS,
data: {
users: [...existingData.users, newUser],
},
});
}
} catch (error) {
// キャッシュが存在しない場合、modify で直接追加
console.warn(
'キャッシュにクエリ結果がありません。modify で追加します。'
);
cache.modify({
id: 'ROOT_QUERY',
fields: {
users(existingUsers = []) {
const newUserRef = cache.writeFragment({
data: newUser,
fragment: gql`
fragment NewUser on User {
id
name
}
`,
});
return [...existingUsers, newUserRef];
},
},
});
}
},
});
return addUser;
}
typescript// コンポーネントでの使用例
function AddUserForm() {
const addUser = useAddUser();
const [name, setName] = React.useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await addUser({
variables: { name },
});
setName('');
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='名前'
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type='submit'>追加</button>
</form>
);
}
ポイント:
readQuery
がnull
を返す場合があるためtry-catch
で囲む- キャッシュが存在しない場合は
modify
にフォールバック - エラーハンドリングで堅牢なコードを実現
まとめ
Apollo Client のキャッシュ操作 API は、用途に応じて適切に使い分けることで、効率的かつ堅牢なキャッシュ管理が実現できます。
各 API の特徴をおさらいしましょう。
# | API | 最適な用途 | 注意点 |
---|---|---|---|
1 | cache.modify | フィールド単位の更新、リスト操作 | readField で他フィールド参照可能 |
2 | cache.writeQuery | クエリ全体の書き込み | 既存データを完全上書き |
3 | cache.readQuery | クエリ結果の読み取り | キャッシュミス時は null が返る |
4 | cache.writeFragment | 特定オブジェクトの部分更新 | id が必須、部分更新向き |
5 | cache.readFragment | 特定オブジェクトの読み取り | 軽量で効率的 |
6 | cache.evict | オブジェクトの削除 | gc() と併用推奨 |
この記事で紹介したコード例を参考に、皆さんのプロジェクトでもキャッシュ操作を実践してみてください。適切な API を選ぶことで、パフォーマンスが向上し、コードの可読性も高まりますよ。
Apollo Client のキャッシュ機構を活用して、快適な GraphQL 開発を楽しんでくださいね。
関連リンク
- article
Apollo キャッシュ操作チートシート:`cache.modify`/`writeQuery`/`readFragment` 早見表
- article
Apollo スキーマの進化設計:非破壊変更・ディプレケーション・ロールアウト戦略
- article
Apollo でインクリメンタル配信:@defer/@stream を実データで動かす手順
- article
Apollo を最短導入:Vite/Next.js/Remix での初期配線テンプレ集
- article
Apollo の全体像を 1 枚で理解する:Client/Server/Router/GraphOS の役割と関係
- article
useQuery から useLazyQuery まで - Apollo Hooks 活用パターン集
- article
Apollo キャッシュ操作チートシート:`cache.modify`/`writeQuery`/`readFragment` 早見表
- article
GitHub Actions 条件式チートシート:if/contains/startsWith/always/success/failure
- article
Zod で CSV/TSV インポートを安全に処理:パース → 検証 → 差分レポート
- article
Yarn のインストール完全ガイド:Corepack 有効化からバージョン固定まで
- article
Git を macOS に最適導入:Homebrew・初期設定テンプレ・credential 管理まで
- article
Web Components で作るモーダルダイアログ:フォーカス管理・閉じる動線まで実装
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来