SWRについて改めて解説
以前ブログ内で SWR について少しだけ触れたのですが、当時認識が間違っていたため少し誤った内容を書いてしまいました。
ということで今回は改めて SWR について書いていこうと思います。
SWR とは
stale-while-revalidate の頭文字から来ているみたいです、【常に新鮮なデータを】みたいなニュアンスなんですかね?
ざっくり書くと【Api リクエストを投げまくって常に新しいデータを取得し表示し続けるカスタム Hooks】なのかなーと。
使い方
公式の例が少しわかりづらいので、少しだけわかりやすく書いてみました。
以下は Axios を使って Api リクエストを投げるケースです。
import useSWR from "swr";
import axios from "axios";
const fetcher = (url) => axios.get(url).then((res) => res.data);
function App() {
const { data, error } = useSWR("/api/data", fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return <div>hello {data.name}!</div>;
}
普通 React で Api を呼ぶ場合 useEffect
で実行すると思いますが、その代わりに useSWR
を使うくらいの認識でほぼ問題ないです。
ただ useEffect
よりも高機能かつシンプルな記述で済むのが SWR のメリットですね。
その一方で、きちんとその仕様を理解していないと大変なことになりかねないのが SWR です。
引数
useSWR
は第 1 引数に key
を、第 2 引数に fetcher
を渡します。
で、第 1 引数の key
は第 2 引数の fetcher
の引数に渡されます。
key
にはユニークな文字列を渡せば良いのですが、通常は Api のエンドポイントを渡すことになります。
上記の例だと "/api/data"
が key
として渡されていますが、この文字列がそのまま fetcher
に渡され、get
の url
に渡されることがわかると思いますが。
個人的に、これが SWR
のもっとも重要なポイントとなります。
なので key
に Api のエンドポイントを渡していないケースは、基本的に良くない実装かなと。(あまりないとは思いますが…)
公式サイトの例はすべて key
にエンドポイントが渡されていたため、みなさんも同じように書くようにしましょう。
また第 2 引数の fetcher
も、基本的には Axios や Fetch を使ったシンプルな呼び出しを割り当てることになると思います。
useCallback
によって生成された関数や、その他コンポーネントの内部で作成された関数を割り当てるものではないので気をつけましょう。
メリット
わざわざ npm パッケージをインストールしてまで useEffect
を使わないメリットはあるのか?と聞かれると、これははっきりと「あります!」と答えられます。
以下その根拠を書いていこうと思います。
常に最新のデータを取得できる
SWR のもっとも大きい強みがこれです。
SWR 設定次第でやたらと Api のコールを行うことが可能で、具体的には以下のよう感じです。
さらに Mutation とい機能も提供されており、何かしらの処理を行ったあとに再度 Api のコールを行うことも可能となっています。
公式サイトでは POST を行ったあとに再度 Api のコールを行う例 などが書かれていますね。
他にも Mutation の機能を組み合わせると、とにかく素早く最新の状態のデータを画面に表示することへ特化していることがわかります。
エラーハンドリングが楽
これも 公式サイト を見てもらうのが 1 番手っ取り早いですが。
fetcher
内で throw
されたエラーオブジェクトがそのまま戻り値として取得できるため、扱いが楽です。
通常だと try-catch を書くと思いますが、スコープが面倒だったりしてイヤですよね。
全体的に記述量が減る
エラーハンドリングの話と被りますが、とにかく記述量が少なくなります。
通常だと Api のコールごとに useState
と useEffect
を書かなければいけないですが、それから開放されるのは楽です。
エラーハンドリングも fetcher 側で済ませられますし、ページャーの実装なんかもシンプルに済んでとても良いです。
キャッシュが効く
たとえばページャー周りで。
1 ページ目から 2 ページ目に遷移した後、1 ページ目に戻るみたいな操作は度々発生すると思うのですが。
SWR の場合 key
ごとにキャッシュが生成されているらしく、1 度取得した内容は再描画が早いです。
一体どこにどういうキャッシュを持っているのか謎です、今度調べてみたい。
SSG との相性が良い
Vercel 製なので当たり前ですが、SSG との相性が良いです。
SWR は BFF で取得してきた props を受け取り、それを初期値として返した後、クライアント側で再度 Api のコールを行うことが可能となっています。
言葉で書くとややこしいので 公式の例 を見てもらうのが 1 番わかりやすいかと。
もちろん SSR との相性も良く、BFF で取得したデータを後から容易に更新することが可能となっています。
意外と公式サイトの記述が寂しいのですが、これは結構な強みだと思います。
デメリット
と、メリットばかり書いてきましたが、もちろんデメリットもぼちぼち存在します。
サーバーに負荷がかかる
1 番のデメリットがこれで、初期状態でもぼちぼち Api コールが行われるようになります。
最新のデータを高速で取得し表示するといえば聞こえは良いですが、裏を返せば Api を呼び出しまくっているに他なりません。
Firebase の Function を介していたりすると、従量課金制なので開発環境だと呼び出しの回数がえらいことになりかねないです。
キャッシュは確かに見てくれますが、Api のコールは裏でしれっと行われているので、認識を誤らないようにしましょう。
常に最新のデータを表示したいケースが少ない
よく考えてほしいのですが、そこまでして常に最新のデータを表示したいケースってあるのでしょうか?
ブラウザへ再フォーカスしたタイミングと、インターバルとか、そこまでして最新のデータを表示したいケースってほぼないと思います。
確かに SSG のケースで Static なデータが古いからクライアント側で再取得する、みたいなケースはわからんでもないのですが。
各ページごとに 1 回データを取得して表示する、それで十分なケースのほうが圧倒的に多い気がしてならないです。
使い道が限定されている
上にも書きましたが、SWR の使い方を勘違いしている人が多いです。
とくに useSWR
の引数については間違えている人が多いです、公式サイトの記述以外の使い方はやめるほうが無難です。
SWR って意外と使いみちの幅が狭くて、使用が適したケースってあまり多くないです。
趣味の開発だと利便性は高そうですが、会社での開発では意外と使いづらい印象が…。
レスポンスヘッダーへアクセスしづらい
上記の例などまさにそうなのですが、戻り値の data
に json
のデータが格納されているため、レスポンスヘッダーへアクセスすることができません。
場合によっては致命的な気がするんですが、どうなんでしょうか。
結論
個人的な結論としては「趣味の開発で使うならアリ」くらいな感じです。
仕事で使う場合はスキーマの関係などもありますし、意外と扱いづらい印象です。
そもそも SWR の知識が半端な状態で使うとおかしなことになります、意外と複雑な仕様ですしね。
ちまたでは「Redux はクソ!SWR 最高!」みたいな意見も目にしますが、汎用性という意味では Redux のほうが圧倒的に上かなーと。
SWR を使う場合のコツとしては fetcher
は 1 つに留めることが重要だと思います。
ここがブレるとおかしなことになっちゃいます。
日本語の記事ではやたら絶賛されているのを目にしますが、個人的には『確かに場合によっては便利だけど絶賛するほどのものでもないよね』くらいのスタンスは変わっていないです。
確かに便利なんですが、シンプルゆえにかゆいところへ手が届かないというか、基本的に fetcher
が 1 つしか定義できないのが致命的かなーと。
実際に使ってみると評判との落差がすごいです、『あれ、こんなもんか』と感じた人は多いんじゃないかなぁ。
あと、自分が使う場合 revalidateOnFocus
はデフォルトで false
にしちゃいます、じゃないと開発中にエグい回数 Api のコールが行われるので…。
とはいえ確かに記述っぷりは楽になりますし、Mutation は画期的ですし、SSG との相性も良いんですよね。
ということでなんとも使用も評価が難しいパッケージだなーという印象です。