【React】useSWRはAPIからデータ取得をする快適なReact Hooksだと伝えたい
Vercel製のuseSWRはReactの非同期データ取得をラクにする
SWRとは、Next.jsを作成しているVercel製のライブラリです。**SWRはuseSWR
というReact Hooksを提供し、APIを通じたデータの取得をラクに記述する手助けをしてくれます。**このライブラリはなんとGitHubスター数を10,700も獲得しています。
SWRはライブラリ名で、stale-while-revalidateというRFC 5861で策定されたキャッシュ戦略の略称です。このSWRがデータ取得の扱いをラクにしてくれて最高なのです。
React開発者が嬉しいuseSWRの書き心地
useSWRは外部APIからのデータ取得、ローディング状態、エラーが発生した時をシンプルに記述できます。これがあらゆるReact開発者にとって(というか、ReactでAPIにリクエストを頻繁に送るアプリケーションを実務で書いている自分が)とても嬉しいことです。
例えば、Suspenseを使わず、useEffectで取得したデータをuseStateに格納して利用するコードがあるとします。
const Profile = () => {
const [profile, setProfile] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
(async () => {
setLoading(true)
const res = await fetch('/api/user')
setProfile(await res.json())
setLoading(false)
})()
}, [])
if (profile === null || loading) {
return <div>loading</div>
}
return <div>hello {profile.name}!</div>
}
記述量が多くて辛いですね。しかも通信エラーが起きた時のハンドリングをしていません。それでももうこの長さです。
しかし、useSWRを使うともっと短く記述できるんです。
import useSWR from 'swr'
const fetcher = () => fetch('/api/user')
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
**たったこれだけでローディング、通信エラー、データ取得の状態を表せます。**とってもシンプルなインターフェースです。
なお、useSWRの第一引数はキャッシュのキーです。第二引数は、データを取得する関数を返すPromiseです。
**嬉しいことに、useSWRは第二引数で与えたfetcherが一度取得したデータをクライアント側でキャッシュしてくれます。**これで、APIを通じて取得したデータをstoreに格納せずに済むのです。
useSWRによるキャッシュの流れ
useSWRがキャッシュからデータを取得する流れは以下です。
- キャッシュからデータを返そうとする(Stale)
- キャッシュにデータがなければ、データを取得する
- キャッシュにデータがあれば、再度データを取得してキャッシュを更新する(Revalidate)
SWRは「stale-while-revalidate」の略でしたね。これだけを理解すればあなたはもうuseSWRを使いこなせること間違いなしです。
さらに嬉しいuseSWRの機能
useSWRはデフォルトでRevalidateに関する嬉しい機能を備えています。Revalidateは(データ取得+キャッシュ更新)を意味します。
- ブラウザをクリックしたり、タブを移動して戻ってきたときにRevalidateする(Focus Revalidation)
- 指定した時間ごとにRevalidateする(pollingができる)
mutate
関数を使ってデータ更新時にキャッシュも更新できる(Local mutation)- 無限スクロールの場合、ページ遷移後もスクロール位置を保存する
- エラー時にリトライしてくれる
- タイムアウトの設定が簡単
よく使うオプションを紹介します
useSWRを使う際によく使うであろうオプションを紹介します。
|プロパティ|役割|デフォルト値| |--|--|--| |initialData|データfetch前に表示する初期データ|なし| |revalidateOnFocus|windowのフォーカス時にRevalidateする|true| |revalidateOnReconnect|通信が切れて復活したらRevalidateする|true| |refreshInterval|pollingの期間|0(pollingしない)| |focusThrottleInterval|1度だけRevalidateする期間|5000| |loadingTimeout|タイムアウトする時間|3000|
エラー時の対応も柔軟に設定できます。
|プロパティ|役割|デフォルト値| |--|--|--| |shouldRetryOnError|エラー時にリトライするか|true| |errorRetryInterval|リトライのインターバル|5000| |errorRetryCount|リトライするmaxの回数|なし|
useSWRの第三引数にオプションとして指定することで、Revalidateを柔軟に設定できます。
const { data, error } = useSWR('/api/user', fetcher, {
initialData: { name: 'React Developer' }
})
全てのoptionを確認するにはSWRのREADMEをご覧ください。
useSWRはSSRで利用できる
useSWRはSSR(Sever Side Rendering)で利用できます。ただし、optionのinitialData
の指定をしないとバグの原因になります。
Next.jsのgetServerSideProps
というサーバーでしか実行されない関数と一緒に用いた例は以下の通りです。
export async function getServerSideProps() {
const data = await fetcher('/api/data')
return { props: { data } }
}
function App (props) {
const initialData = props.data
const { data } = useSWR('/api/data', fetcher, { initialData })
return <div>{data}</div>
}
getServerSideProps
でNext.jsのビルド時にAPIからデータを取得し、それを初期データとして表示します。
しかし、ユーザーのプロフィールなどをユーザー自身が編集したときデータは更新されます。
クライアントでuseSWRを使うことで、バックエンドでデータ更新があった時もSWRがRevalidateしてくれるので、常に最新のデータをユーザーに表示できるのです。
useSWRはGraphQLに対応している
また、SWRはGraphQLにも対応しています。サンプルコードを掲載しておきます。
import { request } from 'graphql-request'
import useSWR from 'swr'
const API = 'https://api.graph.cool/simple/v1/movies'
function MovieActors() {
const { data, error } = useSWR(
`{
Movie(title: "Inception") {
actors {
id
name
}
}
}`,
(query) => request(API, query)
)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return data.Movie.actors
.map((actor) => <li key={actor.id}>{actor.name}</li>)
}
SWRのコードを読んでキャッシュの仕組みを理解する
SWRのキャッシュの仕組みを知るために、実際にSWRのコードを読んでみましょう。index.tsのuseSWRの定義でcache.set(key, newData, false)
とあり(322行目)、cache
にデータを格納していますね。
function useSWR<Data = any, Error = any>(
key: keyInterface,
fn?: fetcherFn<Data>,
config?: ConfigInterface<Data, Error>
): responseInterface<Data, Error> {
// ...
// start a revalidation
const revalidate = useCallback(
async (
revalidateOpts: RevalidateOptionInterface = {}
): Promise<boolean> => {
// ...
cache.set(key, newData, false)
cache.set(keyErr, undefined, false)
// ...
return true
},
[key]
)
// ...
return useMemo(() => {
// ...
}, [revalidate])
}
このcache
を追ってみます。cache
はconfig.tsからimportされていますので、config.tsを見てます。
import Cache from './cache'
// cache
const cache = new Cache()
export {
// ...
cache
}
そして、cache.ts
には以下のようにCacheクラスの定義がありました。
export default class Cache implements CacheInterface {
private __cache: Map<string, any>
constructor(initialData: any = {}) {
// Mapオブジェクトを生成している
this.__cache = new Map(Object.entries(initialData))
}
get(key: keyInterface): any {
const [_key] = this.serializeKey(key)
return this.__cache.get(_key)
}
set(key: keyInterface, value: any, shouldNotify = true): any {
const [_key] = this.serializeKey(key)
this.__cache.set(_key, value)
if (shouldNotify) mutate(key, value, false)
this.notify()
}
keys() {
return Array.from(this.__cache.keys())
}
serializeKey(key: keyInterface): [string, any, string] {
// keyが関数や配列の時にシリアライズする処理
return [key, args, errorKey]
}
}
コンストラクタでthis.__cache = new Map(Object.entries(initialData))
と定義されていますね。これがSWRのキャッシュの正体です。
Mapとは、key/Valueのデータ構造で、HashやHashMapとも呼ばれます。値はキーに対して、常に一意に決まります。
「useSWRがクライアントでキャッシュする」という意味は、ブラウザで実行するJavaScriptがメモリ上に揮発性のデータを確保するということです。
まとめ
いかがでしたでしょうか。useSWRはReactでデータフェッチを楽に扱える強力なHooksです。
実務ではFetchしてきたデータをstoreに格納していますが、SWRを使ってクライアントキャッシュに格納するように書き換えています。
SWRはVercel製ということもあり、そのシンプルなインターフェースはVercelのプロダクト「Vercel」や「Next.js」に共通しているZero Config
を彷彿とさせるものです。
ぜひ本番環境でも使ってみてくださいね。
Happy Coding 🎉