🦍

両方使ってわかった Vercel/SWR と Kong/swrv の違い

2022/02/14に公開約7,700字

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 の違い

swrv では data, error, isValidating が Ref でラップされている

swrv では useSWRV() から返却される data, error, isValidating が Ref でラップされています。例えば isValidating の TypeScript における型は Ref<boolean> となっています。
そのため、これら 3 つは Vue Composition API の ref()computed() の戻り値と同じように setup 内では .value で値にアクセスする必要があり、テンプレート上では Ref のアンラップが施されます:

swrvでのコード例(.vue)
<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] を利用する必要があることに注意が必要です。

swrv には revalidateOnMount オプションがない

SWR には revalidateOnMount オプション(コンポーネントのマウント時に行われる自動再検証を有効または無効にします)がありますが、 swrv にはまだありません

swrv のバウンドミューテート[3]では第 1 引数に関数のみを指定できる

useSWRV() から返却される mutate を用いる際に、第 1 引数を指定する場合は関数(Promise を返却する非同期関数でもよい)でなければなりません。

swrvでのコード例(.vue)
<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() も実行する必要があります:

swrvでのコード例(.vue)
<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() でメモ化された関数が古いデータを参照してしまうため必要なものです:

SWRでのコード例(.jsx)
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() から返却される datadata.value を参照する必要があります:

swrvでのコード例(.vue)
<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 人のユーザーデータを取得する例です:

SWRでのコード例(.jsx)
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>
  );
}
swrvでのコード例(.vue)
<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 ではミドルウェアの仕組みを活用することで、以前のキーでのキャッシュを返す挙動に変更できます(参照: 以前の結果を保持する)。
また、将来的に以前のキーでのキャッシュを返す挙動とするオプションを追加する可能性があります

一方、swrv では「SWR と挙動を合わせるべきではないか?」という Issue がありましたが、対応されずにクローズされています。
以下のように useSWRV と同じ型の関数 useNonStickySWRV を定義することで以前のキーでのキャッシュを返さない挙動を実現できます:

TypeScriptでの実装例(.ts)
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 で教えていただけますと幸いです。

脚注
  1. 記事公開時点の npm における swrv の Weekly Downloads は約 5,000 に対して SWR の Weekly Downloads は約 598,000 と大きな差があり、利用者の多さもあり SWR の情報の方が多いようです。また、SWR には日本語に翻訳されたドキュメントがあります ↩︎

  2. XMLHttpRequest は直接扱いづらいので axios などのライブラリを利用することが多いです ↩︎

  3. useSWR() または useSWRV() から返却される、キーが事前にバインドされている mutate 関数でミューテートすること。参照: ミューテーション – SWR ↩︎

Discussion

ログインするとコメントできます