俺でもわかるGraphQLで基本のTodoアプリをSvelteとApolloでやってみる(5)
はじめに
以前につくったREST-APIベースのTodoアプリをGraphQLで作ってみるとどんな感じかやってみるという回です。
Apollo、Svelte、svelte-apolloで進めています。この構成では日本語の情報少なめなのでこういうのもあってもいいのではと思っています。
前回で、svelte-apollo 完全に理解した 気分になっていたところ、実際に簡単なCRUDアプリ作ってみると意外と考えることがあったので、その辺りを書いておこうかと思ってます。
できあがり
以前のやつとほとんど同じUIですが、中身はGraphQLに入れ替わっています。
コード
Apollo, Svelteともにコードはこちら。いけてないところ教えてくれると嬉しいっす。
考えたこと
簡単なアプリなんですぐできると思ってたんですが、ちょいちょいハマったところを残しておきます。ほとんどは今回使ったsvelte-apollo
の話[1]。
QueryをEventHandlerでコールしない
タスクを取りに行くところ。
const getTask = async (tid) => {
const reply = await query(get_task, { variables: { id: tid } });
reply.subscribe(data => console.log('get', data));
};
getTask(1);
このまま実行したら動きます。
<i class="fas fa-info" on:click={ () => getTask(task.id) }></i>`
でも、こんな風にAjax的にクリックからgetTask()
を実行したら、怒られます。
Uncaught (in promise) Error: Function called outside component initialization
どうやら、svelte-apollo
のsetClient()
はコンポーネントの初期化の時にしか有効ではなく、EventHandlerからは扱えないみたいです[2]。
となると、クエリの引数や値が変わった時の再取得とかはどうすんの?とおもったらsvelte-apollo
のREADMEにもデータのBindingsでrefresh()
使う方法が書いてあった。なるほど以下のような方法を想定しているのか。
let tid = 1;
const getTask = (taskId) => { tid = taskId; }
const first_task = query(get_task);
$: selected_task = first_task.refetch({ id: tid })
としておいて
<i class="fas fa-info" on:click={ () => getTask(task.id) }></i>
で、tid
を変更して、reactiveに$:
を動かしてクエリで値を再取得する。
Storesの入ったPromiseが返ってくる
query()
の返り値はstore
が入ったPromise
です。一方、そのQueryの再実行であるrefetch()
の返り値は普通のPromise
で、mutaion()
の返り値も普通のPromise
です[3]。
え?...ということはquery()
とrefetch()
では返り値をHTMLにマップするときにやり方が変わるってことか...
例えば、以下のテンプレはstore
が入ったPromise
であるbook
の中身を$
、つまりsubscribe
で参照してるので、refetch()
した時には使えないということです。今回のような同じテーブルに対して値を状態に合わせて入れ替えるケースにはquery()
かrefetch()
に揃えておかないとちょっと書きにくいです。今回は全部refetch()
で揃えることにしてみました。
<ul>
{#if $books.loading}
<li>Loading...</li>
{:else if $books.error}
<li>ERROR: {$books.error.message}</li>
{:else}
{#each $books.data.books as book (book.id)}
<li>{book.title} by {book.author.name}</li>
{/each}
{/if}
</ul>
キャッシュがようわからん
Apolloは賢いのでクエリの結果をクライアント側でキャッシュしてくれます。例えば、こんなクエリでactiveなTodoだけ出すかどうかをリアクティブにスイッチできます。でもこれだけだとキャッシュをみてるので、Mutation
、つまりTodoの作成や削除、変更があったときに反映できません。なんとかしないと。
$: current_task = query(list_tasks, {
variables: {
activeOnly: activeOnly
}
});
Apolloのキャッシュ戦略については、ここに素晴らしいまとめがありますので書くことはあまりないです。このうちのどれかを適用すればいいのです。
キャッシュを使わないようにする
まずは一番簡単そうなキャッシュを一切使わないようにして逃げようと思います。fetchPolicy
のパラメータをいじれば良さそうです。
$: current_task = query(list_tasks, {
variables: {
activeOnly: activeOnly,
fetchPolicy: "network-only"
}
});
...やってみたんですが、fetchPolicy: "network-only"
fetchPolicy: "no-cache"
ともにquery()
の結果は引き続きキャッシュを見ているようです。うーん、よくわからない。残念。
Mutationをトリガーにサーバから再取得する
キャッシュの設定で逃げることができなかったので、次はMutationが発生するたびにrefetch()
することにします。これが一番確実な気がする。問題は、どうやってMutationが発生したことをSvelteに伝えるか...ここはひとまず安易にカウンターを作ります。
let mcnt = 0; // mutation counter to trigger reactivity
const initial_tasks = query(list_tasks);
$: {
current_tasks = initial_tasks.refetch({ activeOnly: activeOnly });
console.log(mcnt); // refer mcnt in order to trigger this whole $:
}
これで、on:click()
などでmcnt
をカウントアップしておけば$:
が発火するはずなので、あとは引数を適切に設定すればいいでしょう。こんなんでいいんだろうか?
次回
よくわからないところもありますが、とりあえず動くようにはなりました。ああ、そういう風に使うことを想定しているのかって感じで、初心者には学びも多かった。一方で、普通にapolloクライアント使った方がいいかも...とも思い始めたな...
いずれにしても、GraphQLは面白い技術。次はもう少し実用的なアプリをつくることにします。
シリーズ
- 俺でもわかるGraphQLでリスト表示(1)
- 俺でもわかるGraphQLでサーチ(2)
- 俺でもわかるGraphQLでデータ更新(3)
- 俺でもわかるGraphQLでsvelte-apolloクライアント(4)
- 俺でもわかるGraphQLで基本のTodoアプリをSvelteとApolloでやってみる(5)
Discussion