10
4

More than 1 year has passed since last update.

Apolloのキャッシュとリストの更新

Last updated at Posted at 2021-09-25

はじめに

小さなLTに挑戦してみたので、その時に調べた事をまとめました。
LTの内容をApolloのFragmentの使い方の記事と2つに分割してます。

使用技術

  • React
  • Apollo Client
  • GraphQL Code Generator
  • Hasura(GraphQLサーバー側。今回はそこまで関係ないかも)

サンプルアプリ

画面イメージ

スクリーンショット 2021-09-24 13.16.11.png

DB

usersテーブル:id, name
todosテーブル:id, user_id, title, created_at, updated_at

userがtodoのリストを持っている

ソースコード

https://github.com/tokio-k/LT-sample-apollo-fragment-and-modify-cache
ApolloのFragmentの使い方の記事の内容も入っています

Apolloのキャッシュについて

クエリの結果をインメモリのキャッシュに自動で保存してくれる

フラットな構造に正規化してキャッシュに保存してくれます。
idと__typenameをキーとしてキャッシュを管理しています。
users:1のtodosには、「todos:id」のキーのみが__refとして保存されています。

スクリーンショット 2021-09-24 23.47.00.png
chrome拡張機能 Apollo Client Devtoolsで確認しています。

キャッシュにデータがあればキャッシュから取得する
デフォルトの挙動では、キャッシュに存在しないデータを取得する時はクエリを発行しますが、キャッシュに存在する場合はキャッシュからデータを取得します。。つまりデータが変更されていても、サーバー側にリクエストせず、キャッシュの古い情報を使ってしまう状況になることもあります。
ここら辺の設定は、FetchPolicyで設定できるようです。(勉強したらまたキータにあげようと思います)

キャッシュが変更された時に、コンポーネントが再レンダリングされる

キャッシュを利用しているコンポーネントが、キャッシュの変更を検知して再レンダリングを行います。
キャッシュを更新することで、更新後のデータを表示することができます。

Todoの更新

mutation updateTodoのレスポンスにidを含んでいる

demo-id.gif

自動でキャッシュが書きかわり、表示も変更されました
idと__typenameで管理しているため、idを返すことでキャッシュに存在するデータと同じであることが認識されます。

キャッシュを書き換えるためには、変更後の値も必要なため、変更したい値も含む必要があります。
(title, created_at, updated_atのキャッシュが書き換わる。created_atは変化しないが。)

mutation updateTodoのレスポンスにidを含んでいない

demo-noid.gif

idを返さない場合は、表示が更新されませんでした。

Todoの追加

.ts
const [addTodo] = useAddTodoMutation()

のみの場合

demo-add-no.gif

追加したTodoは画面に表示されませんでした。
users1のtodosが更新されていません。

Listを更新する方法は2つあります。(subscriptionとかは考えてない)

  • refetchQueriesを使って、もう1度サーバー側にリクエストをする
  • クライアント側でキャッシュを書き換える (今回はこっちです)

refetchQueriesは簡単に実装できますが、クエリの数が増えます
クライアント側でキャッシュを書き換える場合は、不要なリクエストがなくなりますが実装が少し大変です。

クライアント側でキャッシュを書き換える

.ts
const [addTodo] = useAddTodoMutation({
  update(cache, { data }) {
    cache.modify({
      id: cache.identify({ id: 1, __typename: "users" }), //※1
      fields: {
        todos(existing = []) { // ※2
          const newTodoRef = cache.writeFragment({ //※3
            data: data?.insert_todos_one,
            fragment: TodoFragmentDoc,
          });
          return [...existing, newTodoRef]; //※4
        },
      },
    });
  },
});

※1 サンプルのため、userを1で固定しています。実際はuserのidを入れてください。
※2 existingには、todosの中の「__ref: "todos:{id}"」の配列が入っています。
※3 newTodoRefも「__ref: "todos:{id}"」の形式です。
※4 それらを1つの配列としてreturnしています。

demo-add.gif
無地に追加したTodoを表示することができました。

まとめ

  • Apolloのキャッシュは、正規化した構造で、idと__typenameをキーとして、管理している。
  • キャッシュを更新することで表示を変えられる

おまけ

削除も更新と同じ要領でキャッシュを編集します。こんな感じです。サンプルコードにはないです。

.ts
cache.modify({
  id: cache.identify({ id: 1, __typename: "users" }),
  fields: {
    todos(existingTodoRefs, { readField }) {
      return existingTodoRefs.filter((todoRef: any) => {
        return todo.id !== readField("id", todoRef);
      }
    },
  },
});

キャッシュの中から、削除したいTodoと同じidを持つTodoを探して消しているだけです

参考記事

https://www.apollographql.com/docs/react/caching/overview/
https://www.apollographql.com/docs/react/caching/cache-interaction/
https://www.apollographql.com/docs/react/caching/cache-configuration/
https://zenn.dev/kazu777/articles/b64935ea7d6fee
https://caddi.tech/archives/2195
https://tech.wasd-inc.com/entry/2020/12/02/222922

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4