【SWR】Reactでのデータフェッチを革新する魔法のフック

React React

あなたは、Reactアプリケーションでデータフェッチに苦戦したことはありませんか?ローディング状態の管理、エラーハンドリング、そしてキャッシュの更新...これらの課題に頭を悩ませた経験は、多くの開発者が共有しているものでしょう。

「もっと簡単に、もっと効率的にデータを扱える方法はないのだろうか?」

そんな悩みを抱えているあなたに、朗報があります。SWR(stale-while-revalidate)という魔法のようなReactフックが、データフェッチの世界に革命を起こしているのです。

本記事では、SWRの使い方を徹底解説します。この記事を読めば、あなたのReactアプリケーションは驚くほど高速で信頼性の高いものになるでしょう。データの鮮度を保ちながら、ユーザー体験を向上させる方法を学びましょう。

さあ、SWRの魔法の世界へ飛び込む準備はできていますか?この記事を読み終えるころには、あなたはデータフェッチの達人になっているはずです。Let's SWR!

SWRとは?初心者にもわかりやすく解説

SWR(Stale-While-Revalidate)は、Next.js(ネクストジェーエス)の開発元であるVercel社が開発したReactフックライブラリです。その名前は、HTTP Cache-Control拡張for Stakesからインスピレーションを得ています。

SWRの基本概念

SWRの基本的な考え方は以下の通りです:

  1. まず、キャッシュからデータを返す(Stale)
  2. 次に、フェッチリクエストを送る(Revalidate)
  3. 最後に、最新のデータを持ってくる

この戦略により、アプリケーションはすぐにデータを表示でき(キャッシュから)、その後バックグラウンドで最新のデータに更新されます。

なぜSWRを使うべきか?

SWRには以下のような利点があります:

  • 高速で軽量: キャッシュファーストなので、UIの表示が速くなります。
  • 自動再検証: データの鮮度を自動的に保ちます。
  • フォーカス時の再検証: タブにフォーカスが当たると自動的にデータを更新します。
  • インターバルポーリング: 定期的にデータを更新できます。
  • ページネーション: ページネーションを簡単に実装できます。
  • TypeScript対応: タイプセーフな開発が可能です。

SWRの基本的な使い方

では、実際にSWRを使ってみましょう。

インストール

まず、プロジェクトにSWRをインストールします。

npm install swr

または

yarn add swr

基本的な使用方法

SWRの基本的な使い方は非常にシンプルです。useSWRフックを使用し、キーとフェッチャー関数を渡すだけです。

import useSWR from 'swr'

function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (isLoading) return <div>loading...</div>
  return <div>hello {data.name}!</</div>
}

この例では、/api/userがキーで、fetcherがデータを取得する関数です。

フェッチャー

フェッチャーは、データを取得するための関数です。例えば:

const fetcher = (...args) => fetch(...args).then(res => res.json())

このフェッチャーは、渡されたURLからデータをフェッチし、JSONとしてパースします。

SWRの高度な使い方

SWRの基本を押さえたところで、より高度な使い方を見ていきましょう。

グローバル設定

SWRConfigを使用することで、アプリケーション全体のSWR設定を行うことができます。

import { SWRConfig } from 'swr'

function App() {
  return (
    <SWRConfig 
      value={{
        refreshInterval: 3000,
        fetcher: (resource, init) => fetch(resource, init).then(res => res.json())
      }}
    >
      <div>Your App</div>
    </SWRConfig>
  )
}

この設定により、アプリケーション全体で3秒ごとにデータを再フェッチし、デフォルトのフェッチャーを使用するようになります。

条件付きフェッチ

時には、特定の条件が満たされた場合にのみデータをフェッチしたい場合があります。

const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)

shouldFetchfalseの場合、SWRはフェッチを行いません。

エラー再試行

SWRは、デフォルトでエラーが発生した場合に自動的に再試行を行います。この動作はカスタマイズ可能です。

useSWR('/api/user', fetcher, {
  onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
    // 404エラーの場合は再試行しない
    if (error.status === 404) return

    // 特定のキーの場合は再試行しない
    if (key === '/api/user') return

    // 10回まで再試行
    if (retryCount >= 10) return

    // 5秒後に再試行
    setTimeout(() => revalidate({ retryCount }), 5000)
  }
})

ミューテーション

SWRは、mutate関数を提供しており、これを使用してキャッシュを手動で更新することができます。

import useSWR, { mutate } from 'swr'

function Profile() {
  const { data } = useSWR('/api/user', fetcher)

  return (
    <div>
      <h1>My name is {data.name}.</h1>
      <button onClick={async () => {
        const newName = data.name.toUpperCase()
        // ローカルデータを即座に更新し、再検証(APIを呼び出し)を行う
        mutate('/api/user', { ...data, name: newName }, false)
        // APIを呼び出してサーバー側のデータを更新
        await fetch('/api/user', {
          method: 'POST',
          body: JSON.stringify({ name: newName })
        })
      }}>Uppercase my name!</button>
    </div>
  )
}

この例では、ボタンをクリックすると即座にUIが更新され(楽観的UI更新)、その後サーバーにデータを送信しています。

SWRの実践的な使用例

ここでは、SWRを使用したより実践的な例を見ていきます。

ページネーション

SWRを使用してページネーションを実装する例を見てみましょう。

import useSWR from 'swr'

function App() {
  const [pageIndex, setPageIndex] = useState(0)
  const { data, error } = useSWR(`/api/posts?page=${pageIndex}`, fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return (
    <div>
      {data.map(post => <Post key={post.id} post={post} />)}
      <button onClick={() => setPageIndex(pageIndex - 1)} disabled={pageIndex === 0}>Previous</button>
      <button onClick={() => setPageIndex(pageIndex + 1)}>Next</button>
    </div>
  )
}

この例では、ページインデックスが変更されるたびに新しいデータがフェッチされます。

無限ローディング

SWRを使用して無限スクロールを実装することも可能です。

import useSWRInfinite from 'swr/infinite'

const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // 最後に到達した
  return `/api/posts?page=${pageIndex}&limit=10` // SWRキー
}

function App() {
  const { data, size, setSize } = useSWRInfinite(getKey, fetcher)

  if (!data) return <div>loading...</div>

  // データを一つの配列にフラット化
  const posts = data ? [].concat(...data) : []

  return (
    <div>
      {posts.map(post => <Post key={post.id} post={post} />)}
      <button onClick={() => setSize(size + 1)}>Load More</button>
    </div>
  )
}

この例では、useSWRInfiniteを使用して無限ローディングを実装しています。「Load More」ボタンをクリックすると、新しいページのデータがフェッチされます。

リアルタイム更新

SWRは、WebSocketやServer-Sent Eventsと組み合わせることで、リアルタイムデータ更新を実現することができます。

import useSWR, { mutate } from 'swr'

function App() {
  const { data } = useSWR('/api/messages', fetcher)

  useEffect(() => {
    const socket = new WebSocket('ws://your-websocket-url')
    socket.onmessage = event => {
      const message = JSON.parse(event.data)
      mutate('/api/messages', [...data, message], false)
    }
    return () => socket.close()
  }, [])

  return (
    <div>
      {data.map(message => <Message key={message.id} message={message} />)}
    </div>
  )
}

この例では、WebSocketからの新しいメッセージを受け取るたびに、mutate関数を使用してローカルのデータを更新しています。

SWRのパフォーマンス最適化

SWRは既に高性能ですが、さらなる最適化の余地があります。

キャッシュプロバイダの使用

デフォルトでSWRはメモリキャッシュを使用しますが、より永続的なストレージを使用することも可能です。

import { cache } from 'swr'
import localStorageProvider from 'swr-local-storage-provider'

cache.setup(localStorageProvider)

この設定により、SWRはブラウザのローカルストレージをキャッシュとして使用するようになります。

デバウンスとスロットリング

頻繁に変更されるデータの場合、デバウンスやスロットリングを適用することで、不必要なリクエストを減らすことができます。

import { useSWR } from 'swr'
import { debounce } from 'lodash'

function App() {
  const [text, setText] = useState('')
  const debouncedText = useDebounce(text, 500)

  const { data } = useSWR(
    () => '/api/search?q=' + debouncedText, 
    fetcher, 
    { revalidateOnFocus: false }
  )

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Search..."
      />
      {data && data.map(item => <Item key={item.id} item={item} />)}
    </div>
  )
}

// カスタムフック: 入力値のdebounce
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value)

  useEffect(() => {
    const handler = debounce(() => setDebouncedValue(value), delay)
    handler()
    return () => handler.cancel()
  }, [value, delay])

  return debouncedValue
}

この例では、入力値が変更されてから500ms後にのみAPIリクエストが送信されます。これにより、ユーザーがタイピングしている間の不必要なリクエストを防ぐことができます。

SWRの落とし穴と注意点

SWRは非常に強力なツールですが、使用する際には以下の点に注意が必要です。

オーバーフェッチング

SWRは自動的にデータを再検証するため、不必要なデータフェッチが発生する可能性があります。これを防ぐためには、適切なrevalidateOnFocusrefreshIntervalの設定が重要です。

const { data } = useSWR('/api/user', fetcher, { 
  revalidateOnFocus: false,
  refreshInterval: 0 // 自動再検証を無効化
})

メモリリーク

大量のデータをキャッシュする場合、メモリリークに注意が必要です。必要に応じて、キャッシュのクリアやガベージコレクションを行うことが重要です。

import { cache } from 'swr'

// 特定のキーのキャッシュをクリア
cache.delete('/api/user')

// すべてのキャッシュをクリア
cache.clear()

依存関係の管理

SWRは依存関係の管理が重要です。特に、フェッチャー関数内で使用される変数や状態に注意が必要です。

function Profile({ userId }) {
  const { data } = useSWR(
    () => userId ? `/api/user/${userId}` : null,
    fetcher
  )

  // ...
}

この例では、userIdが変更されるたびに新しいリクエストが発生します。

SWRと他のデータフェッチライブラリとの比較

SWRは優れたライブラリですが、他のデータフェッチライブラリと比較してみましょう。

React Query vs SWR

React QueryはSWRと似た機能を持つライブラリです。主な違いは以下の通りです:

  1. APIの豊富さ: React QueryはSWRよりも多くのAPIと機能を提供しています。
  2. バンドルサイズ: SWRの方がバンドルサイズが小さいです。
  3. 学習曲線: SWRの方が学習曲線が緩やかです。
// SWRの例
const { data, error } = useSWR('/api/user', fetcher)

// React Queryの例
const { data, error, isLoading } = useQuery('user', () => 
  fetch('/api/user').then(res => res.json())
)

Apollo Client vs SWR

Apollo ClientはGraphQL特化のライブラリですが、SWRはRESTful APIに適しています。

  1. GraphQL対応: Apollo ClientはGraphQLに最適化されています。
  2. 機能の豊富さ: Apollo Clientはより多くの機能を提供しますが、複雑さも増します。
  3. セットアップの簡易さ: SWRの方がセットアップが簡単です。
// SWRの例(RESTful API)
const { data } = useSWR('/api/user', fetcher)

// Apollo Clientの例(GraphQL)
const { data } = useQuery(gql`
  query GetUser {
    user {
      id
      name
    }
  }
`)

SWRの実際の使用例:プロジェクトでの活用

実際のプロジェクトでSWRがどのように使用されているか、いくつかの例を見てみましょう。

例1:Eコマースサイトの商品リスト

Eコマースサイトで、商品リストを表示する際にSWRを使用する例を見てみましょう。

import useSWR from 'swr'

function ProductList({ category }) {
  const { data, error } = useSWR(`/api/products?category=${category}`, fetcher)

  if (error) return <div>商品の読み込みに失敗しました</div>
  if (!data) return <div>商品を読み込んでいます...</div>

  return (
    <div>
      {data.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  )
}

この例では、カテゴリーが変更されるたびに新しい商品リストがフェッチされます。SWRのキャッシュ機能により、同じカテゴリーに戻った際にはキャッシュからデータが即座に表示されます。

例2:SNSアプリのリアルタイム更新

SNSアプリでリアルタイムにフィードを更新する例を見てみましょう。

import useSWR, { mutate } from 'swr'

function Feed() {
  const { data } = useSWR('/api/feed', fetcher, { refreshInterval: 5000 })

  useEffect(() => {
    const socket = new WebSocket('ws://your-websocket-url')
    socket.onmessage = event => {
      const newPost = JSON.parse(event.data)
      mutate('/api/feed', currentData => [newPost, ...currentData], false)
    }
    return () => socket.close()
  }, [])

  if (!data) return <div>フィードを読み込んでいます...</div>

  return (
    <div>
      {data.map(post => (
        <Post key={post.id} post={post} />
      ))}
    </div>
  )
}

この例では、SWRのrefreshIntervalオプションを使用して5秒ごとにフィードを更新しています。さらに、WebSocketを使用してリアルタイムの更新も受け取り、mutate関数でローカルのデータを即座に更新しています。

例3:ダッシュボードアプリケーション

複数のデータソースを持つダッシュボードアプリケーションでSWRを使用する例を見てみましょう。

import useSWR from 'swr'

function Dashboard() {
  const { data: userData } = useSWR('/api/user', fetcher)
  const { data: salesData } = useSWR('/api/sales', fetcher)
  const { data: inventoryData } = useSWR('/api/inventory', fetcher)

  if (!userData || !salesData || !inventoryData) return <div>データを読み込んでいます...</div>

  return (
    <div>
      <UserInfo user={userData} />
      <SalesChart data={salesData} />
      <InventoryStatus data={inventoryData} />
    </div>
  )
}

この例では、複数の独立したデータソースを並行してフェッチしています。SWRは各リクエストを個別に管理し、必要に応じて再検証を行います。

SWRのベストプラクティス

SWRを効果的に使用するためのベストプラクティスをいくつか紹介します。

グローバル設定の活用

アプリケーション全体で共通の設定を使用する場合は、SWRConfigを活用しましょう。

import { SWRConfig } from 'swr'

function MyApp({ Component, pageProps }) {
  return (
    <SWRConfig 
      value={{
        fetcher: (resource, init) => fetch(resource, init).then(res => res.json()),
        revalidateOnFocus: false,
        revalidateOnReconnect: false
      }}
    >
      <Component {...pageProps} />
    </SWRConfig>
  )
}

エラーハンドリングの実装

エラーが発生した場合の処理を適切に実装することが重要です。

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) {
    if (error.status === 404) return <div>ユーザーが見つかりません</div>
    return <div>エラーが発生しました: {error.message}</div>
  }
  if (!data) return <div>読み込み中...</div>

  return <div>こんにちは、{data.name}さん</div>
}

条件付きフェッチの活用

必要な時にのみデータをフェッチするように設計することで、不要なリクエストを減らすことができます。

function UserProfile({ userId }) {
  const { data } = useSWR(userId ? `/api/user/${userId}` : null, fetcher)

  if (!userId) return <div>ユーザーIDが必要です</div>
  if (!data) return <div>読み込み中...</div>

  return <div>プロフィール: {data.name}</div>
}

デバウンスとスロットリングの適用

検索機能などで頻繁にリクエストが発生する場合は、デバウンスやスロットリングを適用しましょう。

import { useSWR } from 'swr'
import { useDebounce } from 'use-debounce'

function Search() {
  const [searchTerm, setSearchTerm] = useState('')
  const [debouncedSearchTerm] = useDebounce(searchTerm, 500)

  const { data } = useSWR(
    () => '/api/search?q=' + debouncedSearchTerm, 
    fetcher
  )

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="検索..."
      />
      {data && data.map(item => <SearchResult key={item.id} item={item} />)}
    </div>
  )
}

プリフェッチの活用

ユーザーの行動を予測してデータをプリフェッチすることで、体験を向上させることができます。

import { useSWR, preload } from 'swr'

// プリフェッチ関数
const prefetchUser = (id) => {
  preload(`/api/user/${id}`, fetcher)
}

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li 
          key={user.id} 
          onMouseEnter={() => prefetchUser(user.id)}
        >
          {user.name}
        </li>
      ))}
    </ul>
  )
}

function UserProfile({ userId }) {
  const { data } = useSWR(`/api/user/${userId}`, fetcher)

  if (!data) return <div>読み込み中...</div>

  return <div>プロフィール: {data.name}</div>
}

この例では、ユーザーがリスト項目にマウスを合わせた時点でそのユーザーの詳細データをプリフェッチします。

まとめ

SWRは、Reactアプリケーションでのデータフェッチを革新的に簡素化する強力なツールです。その特徴は以下の通りです:

  1. シンプルな API: 使いやすく、学習曲線が緩やかです。
  2. 高速なページロード: キャッシュファーストアプローチにより、ユーザー体験が向上します。
  3. リアルタイム更新: 自動再検証機能により、データの鮮度が保たれます。
  4. ページネーションと無限ローディング: 複雑なデータ取得パターンを簡単に実装できます。
  5. TypeScript対応: 型安全な開発が可能です。

SWRを使用することで、開発者はデータ取得のロジックに煩わされることなく、アプリケーションのビジネスロジックに集中することができます。ただし、オーバーフェッチングやメモリリークには注意が必要です。

適切に使用すれば、SWRはあなたのReactアプリケーションのパフォーマンスと開発効率を大幅に向上させる強力な武器となるでしょう。

さあ、あなたも今すぐSWRを使って、データフェッチの魔法を体験してみませんか?

参考リンク

  1. SWR公式ドキュメント
  2. Vercel社のブログ:SWRの紹介
  3. ReactのData Fetchingの歴史とSWR

コメント

タイトルとURLをコピーしました