宮崎からお送りします。yoshinoです。
私が参加するプロジェクトでは、フロントエンドにNext.jsを採用していて、バックエンドサーバへのリクエストの際には、ApolloクライアントとGraphQLを利用しています。 先日、同じページから、同一のクエリを複数回リクエストする問題が発生していて、修正する機会がありましたので、その時のことを書こうと思います。
問題
QUERY_A
です。
const QUERY_A = graphql(` query query_a { user { profile { name email } address { id prefecture } } } `)
特別なことをするわけでもなく、普通にデータをフェッチして使います。
const { data, loading, error } = useQuery(QUERY_A)
Chromeのデベロッパーツールを利用してNetworkを見ると、QUERY_Aよりも前にQUERY_Bが呼ばれていることもわかりました。
const QUERY_B = graphql(` query query_b { user { id isAuthenticated } } `)
QUERY_B -> QUERY_A -> QUERY_B -> QUERY_A -> QUERY_B のように、QUERY_Bが3回、QUERY_Aが2回呼ばれていました。
解決方法
今回のケースでは、idがない箇所にid
を足すことで、複数回クエリする問題は解決しました。
const QUERY_A = graphql(` user { + id profile { + id name email } address { id prefecture } } `)
考えられる理由
useQueryのfetch-policyのデフォルト値はcache-first
になります。
cache-first
を指定することで、クエリを投げた時に、キャッシュ上にデータがあるかを確認し、データがある場合は実際にリクエストすることなく、レスポンスを返します。
今回のケースでも、fetch-policyは明示していないので、cache-first
です(QUERY_A, QUERY_Bどちらも)。
Apolloクライアントでは、キャッシュに保存されているデータを問い合わせるたびに、各クエリのオブジェクトにユニークな識別子をつけるための「正規化」と呼ばれるプロセスを経ます。
通常、オブジェクトの __typename
フィールドにidフィールドを追加することで、各オブジェクトに、ユニークなキャッシュ識別子を割り当てを行います(例えば、userだと__typename
がUser
で取得したidが1であれば、識別子はUser:1
となる)。そして、この識別子を見て「キャッシュ上にデータがあるかを確認し、データがある場合は実際にリクエストする」かを決定します。
Chromeの拡張機能であるApollo Client Devtools
を利用することで、正規化後の__typename
とidに実際に何が格納されているかを確認することができます。
QUERY_Aでidを指定する前は、idを指定しているはずの、QUERY_BのUserオブジェクトのidも、cacheで空の状態であることが確認できました。 QUERY_Bでidを指定するようにすると、どちらのUserオブジェクトのidにも取得してきたidが入るようになります。
このような結果から、Apolloクライアントは、QUERY_Aではidを指定しなかったことにより、正規化プロセスが上手くいかず、複数回のリクエストをしてしまったと考えられます(どうして、QUERY_Bが3回、QUERY_Aが2回呼ばれるのかは、わかっていませんが....)。
終わりに
Apolloクライアントと正規化の時の識別子から生じる問題についての紹介でした。 それでは、良きApolloクライアント + GraphQL開発を!