祝! Remote functions
、Async SSR
みなさん、先日強化されたRemote functions
は使っていますか?
Remote functions
は以前まで<svelte:boundary>
でラップしないとエラーになり、
SSRされるのはloading
部分だけで、JavaScriptが無効になっている場合そこから先が表示されないという状況でした。
先日のリリースでついに
<script lang="ts">
import { getPosts } from './data.remote';
</script>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
このように書くことができるようになり、今までと大きくコンポーネントやfetchデータ周りの設計が変わることを予感しています。
さてそんなRemote functions
(以下RF
)ですが、最近でたばかりの機能なのでまだ慣れていない人も多いのではないでしょうか?
かくいう私もそうで、RF
に生えているrefresh
が動かず、なかなかミスに気づけず困ったので供養がてら事例を紹介します。
ソース
以下に長々書いていますが、自分で読んでもわかりにくかったのでコードをみてください。
(ごめんなさい!)
https://github.com/shamokit/remote-functions/tree/main/src
動かなかった事例
GetPosts
、posts
、posts2
、NoWorkingGetPosts
はそれぞれRF
のquery
を使ってランダムなidを返すpostsを表示しています。
内部で呼んでいるRF
の中身は
import * as v from 'valibot';
import { query } from '$app/server';
export const getPosts = query(v.string(), async (p: string) => {
const id = crypto.randomUUID();
const posts = [
{
id: id,
title: 'Post 1',
content: 'Content 1',
},
{
id: p,
title: 'Post 2',
content: 'Content 2',
},
];
return posts;
});
こうなっています。
RF
は引数を取ることができ、同じ引数を渡したものは同期されます。
つまり、RF('a')
とRF('b')
は別々に同期され、RF('a').refresh()
を呼んだ場合はawait RF('a')
の部分が全て同期されます。
逆にRF('b')
は更新されません。
ここで、
GetPosts
posts in template
posts in template with derived
posts
NoWorkingGetPosts
に関してはRF('a')
を呼んでおり
posts2
はRF('b')
を呼んでいます。
ボタンを押して一緒に更新が走るか試してみましょう!
( 何個か更新されません。予想しよう!)
先ほどあげたWebページでrefresh posts
ボタンを押すと
GetPosts
posts in template with derived
posts
が同時に更新され、
refresh posts2
ボタンを押すと
posts2
だけが更新されるのがわかります。
posts in template
、NoWorkingGetPosts
は更新されませんが、これが引っかかった部分です。
同じRF('a')
を呼んでいるのになぜでしょうか?
答え:scriptブロックの中身は最初のrender時実行される
posts in template
、NoWorkingGetPosts
はソースをみるとscript
ブロックの中でawait
してしまっています。
これがミスでした。
script
ブロックで書いたものは最初一回しか実行されないのが原因です。
原因というか普通にミスです。
ついでにいうと、posts in template with derived
に関してはRF('a')
の部分を$derived(await getPosts('a'))
のように$derived
で囲っているので再実行されます。
おもしろいですね。
まとめ
Remote functions
はいいぞ!
けどなんとなく使うと引っかかるかも!?という記事でした。
個人的に思っているよさげな方針
個人的には
<script lang="ts">
import { getPosts } from './data.remote';
</script>
<ul>
{#each await getPosts() as { title, slug }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
こういうコンポーネントを作ってしまうと
- テストが書きづらくなったりするかも
- コンポーネントが
RF
に直接依存するのは避けたい
と思ったので、
<GetPosts>
{#snippet children(posts)}
<Posts posts={await posts} />
{/snippet}
</GetPosts>
ざっくりこんな感じでRF
を呼ぶ箇所とそれを使う箇所をまとめて影響範囲がわかるようにした上で、
表示に関する部分はRF
に依存しない形でテストしやすいように作っておくかなぁと思いました。
まぁ好みかなとは思います。
みなさんはどう書いていますか?
また、refetch
query.batch
周りは便利な反面影響範囲がどこまで広がるかを気にしておく必要があって後々辛くなる予感がしたので気をつけたほうがいいかもしれないと思っています。
ただ今までのように+page.server.ts
に依存する設計だとそこがどうしても膨らんだりフォームがそこに依存したりで微妙なところもあったのが解消されたので、かなり嬉しいアップデートだなと思っています。
あとNetworkタブをみるとgetPosts
がボタンの押下一回につき一回しか呼ばれないのですごい…
まとめ
おわり!
みなさんもいろんな使い方を共有してRF
とSvelte界隈を盛り上げ、React界隈を怖がらせましょう!