コンテンツにスキップ
SWR 2.0 の発表

SWR 2.0 の発表

コンポーネントがデータのフェッチ、キャッシュ、変更を可能にし、データの変更に合わせてUIを最新の状態に保つことができる人気のReactデータフェッチライブラリであるSWR 2.0のリリースを発表できることを嬉しく思います。

この新しいバージョンには、新しいミューテーションAPI、改善された楽観的UI機能、新しい開発ツール、同時レンダリングのサポートの強化など、多くの改善と新機能が搭載されています。このリリースを可能にしたすべての貢献者とメンテナーに、心から感謝を申し上げます。

ミューテーションと楽観的UI

useSWRMutation

ミューテーションは、データフェッチプロセスの重要な部分です。これにより、ローカルとリモートの両方でデータに変更を加えることができます。既存のmutate APIを使用すると、リソースを手動で再検証および変更できます。SWR 2.0では、新しいフックuseSWRMutationを使用すると、宣言型APIを使用してリモートでデータを変更するのがさらに簡単になります。フックを使用してミューテーションを設定し、後でアクティブ化できます。

import useSWRMutation from 'swr/mutation'
 
async function sendRequest(url, { arg }) {
  return fetch(url, {
    method: 'POST',
    body: JSON.stringify(arg)
  })
}
 
function App() {
  const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest)
 
  return (
    <button
      disabled={isMutating}
      onClick={() => trigger({ username: 'johndoe' })}
    >{
      isMutating ? 'Creating...' : 'Create User'
    }</button>
  )
}

上記の例では、'/api/user'リソースに影響を与えるsendRequestミューテーションを定義します。useSWRとは異なり、useSWRMutationはレンダリング時にすぐにリクエストを開始しません。代わりに、後で手動でミューテーションを開始するために呼び出すことができるtrigger関数を返します。

ボタンをクリックすると、追加の引数{ username: 'johndoe' }とともに、sendRequest関数が呼び出されます。ミューテーションが完了するまで、isMutatingの値はtrueに設定されます。

さらに、この新しいフックは、ミューテーションで発生する可能性のあるその他の問題に対処します。

  • データが変更されている間、UIを楽観的に更新します
  • ミューテーションが失敗した場合に自動的に元に戻します
  • useSWRと、同じリソースの他のミューテーションの間で発生する可能性のある競合状態を回避します。
  • ミューテーションの完了後にuseSWRキャッシュを読み込みます。
  • ...

詳細なAPIリファレンスと例については、ドキュメントを読むか、次のいくつかのセクションをスクロールしてください。

楽観的UI

楽観的UIは、高速でレスポンシブなWebサイトを作成するための優れたモデルですが、正しく実装するのは難しい場合があります。SWR 2.0では、それを簡単にするための強力な新しいオプションがいくつか追加されました。

Todoリストに新しいtodoを追加してサーバーに送信するAPIがあるとしましょう

await addNewTodo('New Item')

UIでは、useSWRフックを使用してTodoリストを表示し、このリクエストをトリガーし、mutate()を介してSWRにデータを再フェッチさせる「新しいアイテムを追加」ボタンを使用します。

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={async () => {
    await addNewTodo('New Item')
    mutate()
  }}>
    Add New Item
  </button>
</>

ただし、await addNewTodo(...)リクエストは非常に遅くなる可能性があります。処理中であっても、新しいリストがどのようになるかをすでに知っている場合でも、ユーザーには古いリストが表示されたままになります。新しいoptimisticDataオプションを使用すると、サーバーが応答する前に、新しいリストを楽観的に表示できます。

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
    })
  }}>
    Add New Item
  </button>
</>

SWRは、dataoptimisticData の値で即座に更新し、その後サーバーにリクエストを送信します。リクエストが完了すると、SWRはリソースを再検証して、最新の状態であることを確認します。

多くのAPIと同様に、addNewTodo(...) リクエストがサーバーから最新のデータを返した場合、その結果を直接表示することもできます(新しい再検証を開始する代わりに)!SWRにmutateのレスポンスでローカルデータを更新するように指示する新しい populateCache オプションがあります。

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
    })
  }}>
    Add New Item
  </button>
</>

同時に、レスポンスデータは真実の情報源からのものであるため、その後に追加の再検証は必要ありません。revalidate オプションを使用して無効にすることができます。

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      revalidate: false,
    })
  }}>
    Add New Item
  </button>
</>

最後に、addNewTodo(...) が例外で失敗した場合、[...data, 'New Item'] のように設定した楽観的なデータを取り消すことができます。rollbackOnErrortrue (デフォルトオプション)に設定することで、それができます。その場合、SWRは data を前の値に戻します。

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Display data */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('New Item'), {
      optimisticData: [...data, 'New Item'],
      populateCache: true,
      revalidate: false,
      rollbackOnError: true,
    })
  }}>
    Add New Item
  </button>
</>

これらのすべてのAPIは、新しい useSWRMutation フックでもサポートされています。詳細については、ドキュメントをご覧ください。そして、ここにその動作を示すデモがあります。

自動エラーロールバックを備えた楽観的UI

複数のキーのミューテート

グローバルの mutate APIは、特定のキーをミューテートまたは再検証できるフィルター関数を受け入れるようになりました。これは、キャッシュされたすべてのデータを無効にするなどのユースケースに役立ちます。詳細については、ドキュメントの 複数のキーのミューテートをご覧ください。

import { mutate } from 'swr'
// Or from the hook if you have customized your cache provider:
// { mutate } = useSWRConfig()
 
// Mutate single resource
mutate(key)
 
// Mutate multiple resources and clear the cache (set to undefined)
mutate(
  key => typeof key === 'string' && key.startsWith('/api/item?id='),
  undefined,
  { revalidate: false }
)

SWR DevTools

SWRDevTools (新しいタブで開きます) は、SWRキャッシュとフェッチ結果をデバッグするのに役立つブラウザ拡張機能です。アプリケーションでDevToolsを使用する方法については、devtools のセクションをご覧ください。

データのプリロード

データのプリロードは、ユーザーエクスペリエンスを大幅に向上させることができます。リソースがアプリケーションで後で使用されることがわかっている場合は、新しい preload APIを使用して、早期にフェッチを開始できます。

import useSWR, { preload } from 'swr'
 
const fetcher = (url) => fetch(url).then((res) => res.json())
 
// You can call the preload function in anywhere
preload('/api/user', fetcher)
 
function Profile() {
  // The component that actually uses the data:
  const { data, error } = useSWR('/api/user', fetcher)
  // ...
}
 
export function Page () {
  return <Profile/>
}

この例では、preload APIはグローバルスコープで呼び出されます。これは、Reactが何かをレンダリングし始める前に、リソースのプリロードを開始することを意味します。Profile コンポーネントがレンダリングされるときには、データはおそらくすでに利用可能になっているでしょう。まだ進行中の場合は、useSWR フックは、新しいリクエストを開始する代わりに、その進行中のプリロードリクエストを再利用します。

preload APIは、レンダリングされる可能性のある別のページのデータをプリロードするなどの場合にも使用できます。SWRを使用したデータのプリフェッチの詳細については、こちらをご覧ください。

isLoading

isLoading は、useSWR によって返される新しい状態であり、**リクエストがまだ進行中で、まだデータがロードされていない**かどうかを示します。以前は、isValidating 状態は、初期ロード状態と再検証状態の両方を表していたため、初期ロード状態であるかどうかを判断するには、dataerror の両方が undefined であるかどうかを確認する必要がありました。

これで、isLoading 値を直接使用して、ローディングメッセージをレンダリングすることができます。

import useSWR from 'swr'
 
function Profile() {
  const { data, isLoading } = useSWR('/api/user', fetcher)
 
  if (isLoading) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

isValidating はまだ存在するため、再検証のローディングインジケーターを表示するために使用できます。

📝

SWRがどのように値を返すかを説明する新しいSWRの理解ページを追加しました。これには、isValidatingisLoading の違いや、ユーザーエクスペリエンスを向上させるためにそれらを組み合わせる方法が含まれています。

以前の状態の保持

keepPreviousData オプションは、以前にフェッチしたデータを保持できる新しい機能です。これは、ライブ検索機能のように、リソースの key が変更され続けるような、リアルタイムで発生するユーザーアクションに基づいてデータをフェッチする場合、UXを大幅に向上させます。

function Search() {
  const [search, setSearch] = React.useState('');
 
  const { data, isLoading } = useSWR(`/search?q=${search}`, fetcher, {
    keepPreviousData: true
  })
 
  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Search..."
      />
 
      <div className={isLoading ? "loading" : ""}>
        {data?.products.map(item => <Product key={item.id} name={item.name} />)
      </div>
    </div>
  );
}
keepPreviousDataが有効になっている場合は、前の検索結果を保持します。

CodeSandbox (新しいタブで開きます) でコードを確認し、詳細についてはこちらをご覧ください。

構成の拡張

SWRConfig は、関数値を受け入れることができるようになりました。<SWRConfig> が複数レベルある場合、内側のコンポーネントは親の構成を受け取り、新しい構成を返します。この変更により、大規模なコードベースでSWRをより柔軟に構成できます。詳細については、こちらをご覧ください。

<SWRConfig
  value={parentConfig => ({
    dedupingInterval: parentConfig.dedupingInterval * 5,
    refreshInterval: 100,
  })}
>
  <Page />
</SWRConfig>

React 18のサポートの改善

SWRは、React 18で useSyncExternalStorestartTransition APIを使用するように内部コードを更新しました。これにより、UIを同時レンダリングする際の整合性が強化されます。この変更にはユーザーコードの変更は必要なく、すべての開発者が直接恩恵を受けることができます。React 17以下にはShimが含まれています。

SWR 2.0とすべての新機能は、React 16および17と引き続き互換性があります。

移行ガイド

Fetcherが複数の引数を受け付けなくなる

key は単一の引数として渡されるようになりました。

- useSWR([1, 2, 3], (a, b, c) => {
+ useSWR([1, 2, 3], ([a, b, c]) => {
  assert(a === 1)
  assert(b === 2)
  assert(c === 3)
})

グローバルなmutateがgetKey関数を受け付けなくなる

グローバルなmutateに関数を渡すと、それはフィルターとして使用されるようになりました。以前は、キーを返す関数をグローバルなmutateに渡すことができました。

- mutate(() => '/api/item') // a function to return a key
+ mutate('/api/item')       // to mutate the key, directly pass it

キャッシュインターフェースの新しい必須プロパティkeys()

独自のキャッシュ実装を使用する場合、キャッシュインターフェースには、JavaScriptのMapインスタンスと同様に、キャッシュオブジェクト内のすべてのキーを返すkeys()メソッドが必要になりました。

interface Cache<Data> {
  get(key: string): Data | undefined
  set(key: string, value: Data): void
  delete(key: string): void
+ keys(): IterableIterator<string>
}

キャッシュの内部構造の変更

キャッシュデータの内部構造は、現在のすべての状態を保持するオブジェクトになります。

- assert(cache.get(key) === data)
+ assert(cache.get(key) === { data, error, isValidating })
 
// getter
- cache.get(key)
+ cache.get(key)?.data
 
// setter
- cache.set(key, data)
+ cache.set(key, { ...cache.get(key), data })
🚨

キャッシュに直接書き込むべきではありません。予期しない動作を引き起こす可能性があります。

SWRConfig.defaultSWRConfig.defaultValue にリネーム

SWRConfig.defaultValue は、デフォルトのSWR構成にアクセスするためのプロパティです。

- SWRConfig.default
+ SWRConfig.defaultValue

InfiniteFetcherSWRInfiniteFetcher にリネーム

- import type { InfiniteFetcher } from 'swr/infinite'
+ import type { SWRInfiniteFetcher } from 'swr/infinite'

サーバーでのSuspenseの回避

Next.jsでのプリレンダリングを含め、サーバー側でSWRでsuspense: trueを使用したい場合は、fallbackData または fallback を介して初期データを提供する必要があります。現在、これは、サーバー側でSuspenseを使用してデータをフェッチできないことを意味します。他の2つのオプションは、完全にクライアント側のデータフェッチを実行するか、フレームワークにデータフェッチをさせる(Next.jsのgetStaticPropsのように)ことです。

ビルドターゲットとしてのES2018

IE 11をサポートする場合は、フレームワークまたはバンドラーでES5をターゲットにする必要があります。この変更により、SSRのパフォーマンスが向上し、バンドルサイズが小さく保たれています。

変更履歴

GitHubで完全な変更履歴をお読みください(新しいタブで開きます)

今後の展望と感謝!

Next.js 13(新しいタブで開きます)の新しいリリースに伴い、Reactエコシステムには、多くのエキサイティングな新機能とパラダイムシフトが見られます。React Server Components(新しいタブで開きます)、ストリーミングSSR、非同期コンポーネント(新しいタブで開きます)、およびuseフック(新しいタブで開きます)です。これらの多くはデータフェッチに関連しており、一部はSWRと重複するユースケースを持っています。

ただし、SWRプロジェクトの目標は変わりません。軽量で、フレームワークに依存せず、少し独断的(フォーカス時に再検証するなど)なドロップインライブラリにしたいと考えています。標準的なソリューションになろうとするのではなく、UXを向上させるイノベーションに焦点を当てたいと考えています。同時に、Reactのこれらの新しい機能を使用してSWRを改善する方法についても研究を行っています。

143人(新しいタブで開きます)の貢献者(+ 106人(新しいタブで開きます)のドキュメント貢献者)の皆様、そして私たちを支援してくれたり、フィードバックをくれたりした皆様に感謝します。DevToolsとドキュメントのすべての作業に対して、小林 徹(新しいタブで開きます)に特別な感謝を捧げます。彼なしでは実現できませんでした!