Next.jsチーム謹製のData Fetcher・useSWRについてのまとめ
TL;DR
- Next.js チーム謹製の Data Fetcher / useSWR についてのまとめです
- 最近実務で
useSWR
を使う機会があったので、基本的な使い方についてドキュメントを読んだ内容のメモ的な記事です
What's SWR
データ取得のための React Hooks ライブラリです。
公式ドキュメントによると、
SWR では、 コンポーネントはデータの更新を継続的かつ自動的に受け取ることができます。
そして、 UI は常に高速でリアクティブなモノになります。
だそうです。
公式ドキュメントの冒頭で記載されているサンプルコードを見てみます。
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>
}
useSWR フック
key
文字列とfetcher
関数を受け取ります。
key
はデータの一意な識別子で、通常は API の URL を想定しています。
fetcher
は引数にkey
を受け取りデータを返す任意の非同期関数で、ネイティブの fetch などを使うことができます。
このフックはdata
,error
,isLoding
の 3 つを返してくれるので、リクエストの状態に基づいて表示を切り替えることが可能です。
再利用可能にする
関数に useSWR を内包することで、再利用可能となります。
function useUser (id) {
const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
return {
user: data,
isLoading,
isError: error
}
}
useEffect に対する優位性
例えば親コンポーネントないで初期表示時にuser
の情報を取得し、state にセットした上で子コンポーネントに渡しているとします。
この場合であれば親子なので良いですが、場合によっては孫・ひ孫に至るまで props にてuser
をバケツリレーする必要があります。
import { useSearchParams } from "next/navigation";
// 親コンポーネント
function Page () {
const [user, setUser] = useState(null)
const searchParams = useSearchParams();
const id = searchParams.get("userId");
// データの取得
useEffect(() => {
fetch('/api/user/${id}')
.then(res => res.json())
.then(data => setUser(data))
}, [id])
// グローバルなローディング状態
if (!user) return <Spinner/>
return <div>
<Navbar user={user} />
<Content user={user} />
</div>
}
// 子コンポーネント群
function Navbar ({ user }) {
return <div>
...
<Avatar user={user} />
</div>
}
function Content ({ user }) {
return <h1>Welcome back, {user.name}</h1>
}
function Avatar ({ user }) {
return <img src={user.avatar} alt={user.name} />
}
context
を使うことでバケツリレーは避けられますが、レンダリング効率の問題や、動的なコンテンツの問題(トップレベルのコンポーネントが子コンポーネントに過不足なくデータを渡せているかどうか?)が残ります。
しかし、再利用可能となった useSWR を使うことでこの問題を解決することができます。
先ほどのコードをリファクタしたものを見てみると、
import { useSearchParams } from "next/navigation";
// useSWRを内包したフックを作る
function useUser () {
const searchParams = useSearchParams();
const id = searchParams.get("userId");
const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
return {
user: data,
isLoading,
isError: error
}
}
// 親コンポーネント
function Page () {
return <div>
<Navbar />
<Content />
</div>
}
// 子コンポーネント群
function Navbar () {
return <div>
...
<Avatar />
</div>
}
function Content () {
const { user, isLoading } = useUser()
if (isLoading) return <Spinner />
return <h1>Welcome back, {user.name}</h1>
}
function Avatar () {
const { user, isLoading } = useUser()
if (isLoading) return <Spinner />
return <img src={user.avatar} alt={user.name} />
}
これによりデータ fetch は各子コンポーネントに結合され、データはコンポーネントごとに独立します。
しかし、一見コンポーネントがレンダリングされるごとにデータ fetch が行われるように見えますが、その心配はありません。
なぜなら、useSWR は同じkey
を使うことでリクエストは自動的に重複排除、キャッシュ、共有されるからです。
また、ユーザーのフォーカスやネットワークの再接続時にアプリケーションがデータを再取得できるようにもなります。
fetcher に複数の引数を渡す
例えば API にtoken
を付けて fetch を行う場合に、URL と token をfetcher
に渡すというシナリオが考えられます。
その場合、配列に格納した上でfetcher
へ値を引き渡します。
const { data: user } = useSWR(['/api/user', token], ([url, token]) => fetchWithToken(url, token))
オブジェクトの受け渡し
同様にオブジェクトをfetcher
へ引き渡すことも可能です。
const { data: orders } = useSWR({ url: '/api/orders', args: user }, fetcher)
条件付き fetch
key
に null が渡ってきた場合、useSWR はリクエストを開始しません。
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)
おわりに
useEffect との優位性のところが素晴らしいと思いました。
error
, isLoading
も返してくれるので、データ fetch に必要な情報の痒いところに手が届いてる感がすごいです。
メンバー募集中!
サーバーサイド Kotlin コミュニティを作りました!
Kotlin ユーザーはぜひご参加ください!!
また関西在住のソフトウェア開発者を中心に、関西エンジニアコミュニティを一緒に盛り上げてくださる方を募集しています。
よろしければ Conpass からメンバー登録よろしくお願いいたします。
Discussion