useSWRについての学習 useEffectよりも優れたデータ取得法（useSWRInfinite、useSWRSubscription...）

# はじめに

Reactアプリケーションでデータ取得を効率的に行うことは、パフォーマンスとユーザー体験を向上させる上で非常に重要です。その中で、Vercel社が開発したuseSWRは、データフェッチングを簡単かつ強力に行えるReact Hooksライブラリとして注目を集めています。

### useSWRとは何か

useSWRは「stale-while-revalidate」というHTTPキャッシュ無効化戦略に基づいたデータフェッチングライブラリです。このライブラリを使用すると、コンポーネント内でデータの取得、キャッシング、そして自動的な再検証を簡単に実装できます。

主な特徴は以下の通りです:

- キャッシュからのデータ即時返却
- バックグラウンドでの自動再検証
- インターバルによる定期的なポーリング
- フォーカス時の再検証
- ネットワーク回復時の自動再試行

### なぜuseSWRを使うべきか

useSWRを使用する主な利点は以下のとおりです:

1. **高速で軽量**: キャッシュを活用することで、アプリケーションの応答性が向上します

2. **自動更新**: フォーカスやネットワーク回復時に自動的にデータを再検証するため、常に最新のデータを表示できます

3. **簡潔なコード**: 複雑なデータフェッチングロジックを数行のコードで実装できます

4. **エラーハンドリングの簡素化**: ネットワークエラーや読み込み状態の管理が容易になります

5. **Suspenseとの互換性**: React Suspenseと組み合わせて使用することができます

useSWRを使用することで、開発者はデータフェッチングの複雑さを抽象化し、ビジネスロジックやUIの構築に集中できます

# useSWRの基本的な使い方

### インストール方法

https://swr.vercel.app/ja/docs/getting-started

```
pnpm add swr
```
```
npm i swr
```

### 基本的な構文
1. データを取得する非同期関数
    ```js
     const fetcher = (...args) => fetch(...args).then(res => res.json())
    
    // fetchを使う場合
     const fetcher = url => fetch(url).then(r => r.json())
    
    // axios を使う場合
     const fetcher = url => axios.get(url).then(res => res.data)
     
    ```
1. useSWRをインポートして任意の関数コンポーネントで使用
    ```js
    const { data, error, isLoading } = useSWR(key, fetcher)
    ```
key: データの一意な識別子（通常は API の URL）
**基本的にkeyで記述したapiのurlがfetcherの引数に渡されます**
### シンプルな例（APIからデータを取得する）
```react
// データ取得部分をカスタムフック化
function useUser (id) {
  const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
 
  return {
    user: data,
    isLoading,
    isError: error
  }
}

function User (id) {
  const { data, error, isLoading } = useUser(id)
  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading user data.</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

```
* 使いたい関数コンポーネント上で、カスタムフックの呼び出しのみでデータの取得ができる！
##### useSWRを使わず、useEffectでデータ取得を行うと
```react
function User (id) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    fetch('api/user/${id}')
      .then(res => res.json())
      .then(data => setUser(data))
  }, [])

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
```
コードが複雑化します。またuseSWRを使った方が簡単にデータの再取得を定義できます。
# mutateとは
mutateとはキャッシュされたデータを更新するためのメソッドです
サーバーとの通信を待たずにクライアント側でデータを更新し、ユーザーに反映させることが可能です。
* グローバルミューテート
どんなキーに対してもミューテートできます
    ```react
    import { useSWRConfig } from "swr"
     
    function App() {
      const { mutate } = useSWRConfig()
      mutate(key, data, options)
    }
    ```
* バウンドミューテート
  useSWRのkeyに対応しており、フックの返り値から値を取得することができます。
    ``` react
    import useSWR from 'swr'
     
    function Profile () {
      const { data, mutate } = useSWR('/api/user', fetcher)
     
      return (
        <div>
          <h1>My name is {data.name}.</h1>
          <button onClick={async () => {
            const newName = data.name.toUpperCase()
            // データを更新するために API にリクエストを送信します
            await requestUpdateUsername(newName)
            // ローカルのデータを即座に更新して再検証（再フェッチ）します
            // 注意: useSWR の mutate は key が対応付けられているため、key の指定は必要ありません
            mutate({ ...data, name: newName })
          }}>Uppercase my name!</button>
        </div>
      )
    }
* mutateの第二引数にfalseを指定しないと再検証が実行されるので、不要なリクエスト防止に注意！！
##### mutateの使用例
ツイートの投稿:
新しいツイートを投稿した後、タイムラインのデータをmutateを使って即座に更新します。これにより、ユーザーは投稿したツイートをすぐに自分のタイムラインで確認できます。

# 自動再検証
##### フォーカス時の再検証
タブの切り替え or ページのフォーカスによって最新のデータを取得するオプション。デフォルトでtrueになっている
```react
const { data, error, isLoading } = useSWR('/api/user', fetcher, {
    revalidateOnFocus: true, // デフォルト値
  });
```
##### 定期的な再検証
一定時間（設定可能）ごとにデータを自動的に再フェッチするオプション
リアルタイムデータを表示する場合に非常に便利
デフォルトでは無効になっています。ので使用の際は指定します。
```react
const { tweet, error, isLoading } = useSWR('api/tweets', fetcher, {refreshInterval: 5000})
// 5秒ごとにデータを自動再fetch
```
##### 自動再検証の無効化
```react
useSWR(key, fetcher, {
  revalidateIfStale: false,
  revalidateOnFocus: false,
  revalidateOnReconnect: false
})
```
これを指定するとデータがキャッシュされると二度とリクエストされなくなるそうです。

mutateはこちらを参考にさせていただきました。

https://zenn.dev/uttk/articles/b3bcbedbc1fd00#mutation

# ユースケース
### リアルタイムデータ更新
refreshIntervalより更にリアルタイム性が必要で、効率的なデータの取得を行うには
**useSWRSubscription**というフックを使うといいです
##### サブスクリプションとは
サブスクリプションは、クライアント（ブラウザ）がサーバーからリアルタイムで更新を受け取る仕組みです。従来のポーリング（定期的にデータを取得する方法）と異なり、データが変更されたときにすぐに通知を受け取ることができます

これはHTTPリクエストと違い、サバーからプッシュされてくるデータを扱います。

株価やライブチャットなどに有効とされています
1. import
    ```js
    import useSWRSubscription from 'swr/subscription'
    ```
* 型定義
    ```react
    useSWRSubscription<Data, Error>(key: Key, subscribe: (key: Key, options: { next: (error?: Error | null, data: Data) => void }) => () => void): { data?: Data, error?: Error }
    ```
* 引数
  `key` :useSWRと同じでデータソースの指定やキャッシュ管理に使われます
  `suscribe関数` :サブスクリプションを設定する関数
  `next関数` :errorが起きたらエラーを渡して、第二引数に新しいデータを渡す
* フックの戻り値
  `Data` :最新のデータ
  `error` :エラーオブジェクト

##### WebSocketのデータソースを購読する
Webソケットについては

https://zenn.dev/nameless_sn/articles/websocket_tutorial

https://ginga-sys.jp/blog/2024/02/websocket-http/

簡単に言ったら、HTTPがリクエストに対してレスポンスを返すよに対してWebソケットはよっ！て挨拶したらデータを投げ合える感じですかねぇ......
```react
import useSWRSubscription from 'swr/subscription'
 
function App() {
// keyが自動的にsubscribeに渡され、next関数が実施されます
  const { data, error } = useSWRSubscription('ws://localhost:8000/xxx', subscribe)
 
  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data}!</div>
}


function subscribe(key, { next }) {
  // WebSocket接続を作成
  const socket = new WebSocket(key)
  // 接続が開いて、メッセージを受信したときの処理
  socket.addEventListener('message', (event) => next(null, event.data))
  // エラーが発生したときの処理
  socket.addEventListener('error', (event) => next(event.error))
  // クリーンアップ関数
  return () => socket.close()
}
```

### useSWRInfinite

##### useSWRInfinite（twitterのタイムラインのような機能）

1. 現在のページインデックスと前のページのデータをgetKeyに渡す
```react
const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // 最後に到達した
  return `/users?page=${pageIndex}&limit=10`                    // SWR キー
}
```
公式より
> getKey 関数は、useSWRInfinite と useSWR とで大きな違いがあります。 現在のページのインデックスに加えて、前のページのデータも受け入れます。 したがって、インデックスベースとカーソルベースの両方のページネーション API を適切にサポートできます。
* `pageIndex`: 現在のページインデックス（0から始まる）
* `previousPageData`: 前のページのデータ

```react
import useSWRInfinite from 'swr/infinite'
 
// ...

const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)
```
* useSWRと引数は基本同じ

* `data`: 取得したデータの配列（各要素が1ページ分のデータ）
これはつまり

```
[
  [ 1ページ目のデータ ],
  [ 2ページ目のデータ ],
  [ 3ページ目のデータ ],
    ... 以降のページ
]
```
というデータ構造になるので取得の際は注意が必要です
* `error`: エラーオブジェクト（エラーが発生した場合）
* `size`: 現在取得しているページ数
* `setSize`: ページ数を変更する関数（新しいデータを読み込むのに使用）

ちょっと重くなったので、続きは次回また記述します。

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

### TanStack QueryやApollo Client
業務でuseSWRを使用したのでデータ取得の方法はuseSWR一強かと思っていましたが、TanStack Queryや Apollo Clientというライブラリもあるそうです。

この違いについては、また別の機会に学習してみようと思います。

# 参考リソース

### 公式ドキュメント

https://swr.vercel.app/ja