両方使ってわかった Vercel/SWR と Kong/swrv の違い
Vercel/SWR (以下 SWR)はデータフェッチのための React Hooks ライブラリで、データ取得のロジックを単純化したり様々な機能の恩恵を受けられます。
Kong/swrv (以下 swrv)は Vue において SWR と同じようなことを実現している Vue Composition API 向けライブラリです。
swrv については情報が少ない[1]ものの、SWR のドキュメントや事例を活用できるほど両者はよく似ています。とはいえ、両者には細かな違いが見受けられるため本記事にまとめました。両者の違いと特性を知っていただければ幸いです。
動作確認したバージョン
- React 17
react@17.0.2
swr@1.2.1
- Vue 3
vue@3.2.30
swrv@1.0.0-beta.8
- Vue 2
vue@2.6.14
@vue/composition-api@1.4.5
unplugin-vue2-script-setup@0.9.2
swrv@0.9.6
SWR と swrv の違い
data
, error
, isValidating
が Ref でラップされている
swrv では swrv では useSWRV()
から返却される data
, error
, isValidating
が Ref でラップされています。例えば isValidating
の TypeScript における型は Ref<boolean>
となっています。
そのため、これら 3 つは Vue Composition API の ref()
や computed()
の戻り値と同じように setup
内では .value
で値にアクセスする必要があり、テンプレート上では Ref のアンラップが施されます:
<script setup>
import useSWRV from "swrv";
const { data } = useSWRV("/api/user", fetcher);
const greete = () => {
if (!data.value) {
return;
}
alert(`Hello, my name is ${data.value.name}.`);
};
</script>
<template>
<div v-if="!data">Loading...</div>
<div v-else>
<h1>My name is {{ data.name }}.</h1>
<button @click="greete">greete</button>
</div>
</template>
swrv にはデフォルトのフェッチャーがある
SWR では v1.0.0 からデフォルトのフェッチャーがなくなりましたが、swrv ではデフォルトのフェッチャーがあります。
swrv のデフォルトのフェッチャーは Fetch API を利用しているため、IE11 等の古いブラウザに対応する必要がある場合は XMLHttpRequest[2] を利用する必要があることに注意が必要です。
revalidateOnMount
オプションがない
swrv には SWR には revalidateOnMount
オプション(コンポーネントのマウント時に行われる自動再検証を有効または無効にします)がありますが、 swrv にはまだありません。
[3]では第 1 引数に関数のみを指定できる
swrv のバウンドミューテートuseSWRV()
から返却される mutate
を用いる際に、第 1 引数を指定する場合は関数(Promise
を返却する非同期関数でもよい)でなければなりません。
<script setup>
import useSWRV from "swrv";
const { data, mutate } = useSWRV("/api/user", fetcher);
const handleClick = () => {
// 誤り: mutate({ name: "Bob" })
mutate(() => ({ name: "Bob" }));
};
</script>
swrv のミューテートではローカルデータの更新と同時に再検証する指定ができない
SWR のミューテートでは、ローカルデータの更新後にフェッチャーで再検証する動作がデフォルトとなっています。
useSWR()
から返却される mutate
であれば mutate(func, false)
のように false
を明示的に指定することでローカルデータの更新後に再検証しないようにできます。
swrv のミューテートにはこれに対応するオプションが存在せず、ローカルデータの更新後に再検証しません。
もし SWR のようにローカルデータの更新後に再検証する必要があれば、以下のように mutate()
も実行する必要があります:
<script setup>
import useSWRV from "swrv";
const { data, mutate } = useSWRV("/api/user", fetcher);
const handleClick = async () => {
await mutate(() => ({ name: "Bob" }));
await mutate();
};
</script>
現在のデータにもとづいたバウンドミューテートの方法が異なる
useSWR()
から返却される mutate
では、データを更新する関数に現在キャッシュされているデータが渡されます。これは useSWR()
から返却される data
を参照すると useCallback()
でメモ化された関数が古いデータを参照してしまうため必要なものです:
import { useCallback } from "react";
import useSWR from "swr";
function Profile() {
const { data, mutate } = useSWR("/api/user", fetcher);
const handleClick = useCallback(() => {
mutate((prevData) => ({ ...prevData, name: "Bob" }), false);
}, []);
if (!data) return <></>;
return (
<div>
<h1>My name is {data.name}.</h1>
<button onClick={handleClick}>Make my name Bob!</button>
</div>
);
}
一方、useSWRV()
から返却される mutate
では、データを更新する関数に現在キャッシュされているデータが渡されません。現在のデータにもとづいたバウンドミューテートをするには useSWRV()
から返却される data
の data.value
を参照する必要があります:
<script setup>
import useSWRV from "swrv";
const { data, mutate } = useSWRV("/api/user", fetcher);
const handleClick = () => {
mutate(() => ({ ...data.value, name: "Bob" }));
};
</script>
<template>
<div v-if="data">
<h1>My name is {{ data.name }}.</h1>
<button @click="handleClick">Make my name Bob!</button>
</div>
</template>
キーが変化する際、以前のキーでのキャッシュを返すか・返さないかが異なる
SWR では以前のキーでのキャッシュを返さない挙動となっていて、swrv では以前のキーでのキャッシュを返す挙動となっています。
以下のコードと動画は、API から 3 人のユーザーデータを取得する例です:
import { useState } from "react";
import useSWR from "swr";
function App() {
const [userId, setUserId] = useState(1);
const { data } = useSWR(`/api/users/${userId}`, fetcher);
return (
<div>
<h1>SWR</h1>
<div>User ID: {userId}</div>
<div>User Name: {!data ? "Loading..." : data.name}</div>
<button onClick={() => setUserId(1)}>setUserId(1)</button>
<button onClick={() => setUserId(2)}>setUserId(2)</button>
<button onClick={() => setUserId(3)}>setUserId(3)</button>
</div>
);
}
<script setup>
import { ref } from "@vue/composition-api"; // Vue 2
import useSWRV from "swrv";
const userId = ref(1);
const { data } = useSWRV(() => `/api/users/${userId.value}`, fetcher);
</script>
<template>
<div>
<h1>swrv</h1>
<div>User ID: {{ userId }}</div>
<div>User Name: {{ !data ? "Loading..." : data.name }}</div>
<button @click="userId = 1">userId = 1</button>
<button @click="userId = 2">userId = 2</button>
<button @click="userId = 3">userId = 3</button>
</div>
</template>
Vue ではコンポーネントが作成されたタイミングで setup
が一度だけ実行されるため、キーが変化する場合は useSWRV()
にキーを返す関数を渡す必要があります。
SWR の動作例 | swrv の動作例 |
---|---|
SWR ではミドルウェアの仕組みを活用することで、以前のキーでのキャッシュを返す挙動に変更できます(参照: 以前の結果を保持する)。
また、将来的に以前のキーでのキャッシュを返す挙動とするオプションを追加する可能性があります。
一方、swrv では「SWR と挙動を合わせるべきではないか?」という Issue がありましたが、対応されずにクローズされています。
以下のように useSWRV
と同じ型の関数 useNonStickySWRV
を定義することで以前のキーでのキャッシュを返さない挙動を実現できます:
import { computed } from "@vue/composition-api"; // Vue 2
import useSWRV, { SWRVCache, IConfig } from "swrv";
const cache = new SWRVCache();
export const useNonStickySWRV: typeof useSWRV = (
keyFn,
fn?: any,
config?: IConfig
) => {
const { data, error, isValidating, mutate } = useSWRV(keyFn, fn, {
...config,
cache,
});
const patchedData =
typeof keyFn === "function"
? computed(() => {
const value = data.value;
const key = keyFn();
if (!key || (typeof key === "string" && !cache.get(key))) {
return undefined;
}
return value;
})
: data;
return {
data: patchedData,
error,
isValidating,
mutate,
};
};
所感
本記事は情報が少ない swrv の記事として書こうと思っていましたが、調査しているうちに SWR の特性も知ることができたため比較形式の記事にしました。似ているフレームワークやライブラリを両方触ることで得られる知識があることを改めて実感しました。
もし誤りなどがありましたら、コメントや Twitter で教えていただけますと幸いです。
-
記事公開時点の npm における swrv の Weekly Downloads は約 5,000 に対して SWR の Weekly Downloads は約 598,000 と大きな差があり、利用者の多さもあり SWR の情報の方が多いようです。また、SWR には日本語に翻訳されたドキュメントがあります ↩︎
-
useSWR()
またはuseSWRV()
から返却される、キーが事前にバインドされているmutate
関数でミューテートすること。参照: ミューテーション – SWR ↩︎
Discussion