はじめに
小さなLTに挑戦してみたので、その時に調べた事をまとめました。
LTの内容をApolloのFragmentの使い方の記事と2つに分割してます。
使用技術
- React
- Apollo Client
- GraphQL Code Generator
- Hasura(GraphQLサーバー側。今回はそこまで関係ないかも)
サンプルアプリ
画面イメージ
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として保存されています。
chrome拡張機能 Apollo Client Devtoolsで確認しています。
キャッシュにデータがあればキャッシュから取得する
デフォルトの挙動では、キャッシュに存在しないデータを取得する時はクエリを発行しますが、キャッシュに存在する場合はキャッシュからデータを取得します。。つまりデータが変更されていても、サーバー側にリクエストせず、キャッシュの古い情報を使ってしまう状況になることもあります。
ここら辺の設定は、FetchPolicyで設定できるようです。(勉強したらまたキータにあげようと思います)
キャッシュが変更された時に、コンポーネントが再レンダリングされる
キャッシュを利用しているコンポーネントが、キャッシュの変更を検知して再レンダリングを行います。
キャッシュを更新することで、更新後のデータを表示することができます。
Todoの更新
mutation updateTodoのレスポンスにidを含んでいる
自動でキャッシュが書きかわり、表示も変更されました
idと__typenameで管理しているため、idを返すことでキャッシュに存在するデータと同じであることが認識されます。
キャッシュを書き換えるためには、変更後の値も必要なため、変更したい値も含む必要があります。
(title, created_at, updated_atのキャッシュが書き換わる。created_atは変化しないが。)
mutation updateTodoのレスポンスにidを含んでいない
idを返さない場合は、表示が更新されませんでした。
Todoの追加
const [addTodo] = useAddTodoMutation()
のみの場合
追加したTodoは画面に表示されませんでした。
users1のtodosが更新されていません。
Listを更新する方法は2つあります。(subscriptionとかは考えてない)
- refetchQueriesを使って、もう1度サーバー側にリクエストをする
- クライアント側でキャッシュを書き換える (今回はこっちです)
refetchQueriesは簡単に実装できますが、クエリの数が増えます
クライアント側でキャッシュを書き換える場合は、不要なリクエストがなくなりますが実装が少し大変です。
クライアント側でキャッシュを書き換える
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しています。
まとめ
- Apolloのキャッシュは、正規化した構造で、idと__typenameをキーとして、管理している。
- キャッシュを更新することで表示を変えられる
おまけ
削除も更新と同じ要領でキャッシュを編集します。こんな感じです。サンプルコードにはないです。
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