安安!前幾天講了怎麼在專案裡用些 data fetching functions 做 pre-rendering。不過如果想要直接在 client-side 抓取資料呢?有什麼方法?~ 今天跟大家分享一個 data fetching (抓取資料) 的 library,SWR!
SWR 是 stale-while-revalidate
的簡稱,意思是我們抓取資料的時候,會先拿到 cache 裡 (stale / 舊資料) 的,然後在背後去發請求抓取資料 (revalidate),再回傳最新的資料。SWR 是一個提供抓取資料功能的 react hooks library,沒錯,他是 react hooks!
在 client-side 抓取資料算簡單,可是也必須注意一些事情,例如重複呼叫某 API。有了 SWR 之後,我們不用再擔心這件事!SWR 幫我們做這些:
很簡單~ 在專案的目錄下,輸入以下指令:
yarn add swr
# or
npm install swr
裝完就可以這樣使用:
import useSWR from 'swr'
function User() {
// Github API: https://api.github.com/users/vercel
const { data, error } = useSWR('https://api.github.com/users/vercel', fetcher)
if (error) return <div>發生錯誤 :o</div>
if (!data) return <div>loading...</div>
return <div>Hi {data.name}!</div> // Hi Vercel!
}
這裡 useSWR
hooks 接收兩個 parameters,key
和 fetcher
function。
key
:SWR 會根據這 key 做 cache,通常是請求的 API URL
fetcher
:去做抓取的 function,這 function 接收 key
當 parameter 然後回傳 data (資料)。可以用一般的 fetch
,axios
,或是 GraphQL
,只要可以用 key
去抓取和回傳資料都可以
const fetcher = (...args) => fetch(...args).then(res => res.json())
上面那段 code 只能抓取 vercel 這 Github user 的資料,如果我們想抓其他人的資料呢?要複製貼上嗎?不用~ 我們可以做 React custom hook:
function useUser (id) {
const { data, error } = useSWR(`https://api.github.com/users/${id}`, fetcher)
return {
user: data,
isLoading: !error && !data,
isError: error
}
}
然後在 React component 裡使用:
// Avatar component
function Avatar ({ id }) {
const { user, isLoading, isError } = useUser(id)
if (isLoading) return <Spinner />
if (isError) return <Error />
return <img src={user.avatar_url} alt={user.name} />
}
那如果有不只一個 component 需要用到 user 的資料呢?useUser
hook 可以在很多 component 裡重複使用喔:
// Info component
function Info ({ id }) {
const { user, isLoading } = useUser(id)
if (isLoading) return <Spinner />
if (isError) return <Error />
return <h1>Welcome back, {user.name} (@{user.login})</h1>
}
是不是很方便!而且 SWR 會自動做 cache 還有避免重複發請求,雖然我們使用了 useUser
兩次,可是實際上我們只發一次 API request 喔!這麼做可以減少 props 傳遞的動作,每個獨立的 component 自己處理資料~
伺服器端或是資料庫裡的資料有可能會變,我們該怎麼拿到最新值?該怎麼跟伺服器做同步?SWR 的 revalidation 機制成為答案!有幾個 revalidate 時機:
revalidateOnFocus
):這裡的 focus 是指 瀏覽器 tab 的 focus,使用者有沒有正在看該 tab,如果沒有,每次使用者回到這 tab,SWR 會做 revalidate (重新抓取資料),讓使用者看到最新的資料refreshInterval
):定時去做 revalidate,只要畫面上有用到這些資料,SWR 會根據我們設定的 interval 去做資料更新,讓畫面看起來有即時更新revalidateOnReconnect
):使用者有可能會斷線,例如電腦進入睡眠模式,當使用者重新連到網路時,SWR 會做 revalidate 更新資料~useSWRImmutable
):以上三種 revalidate 時機會讓資料保持同次的狀態,可是如果資料完全不會變,那何必 revalidate 呢?我們可以把 revalidate 機制關掉,讓 SWR 永遠不做資料同步,只抓一次資料!看起來都很棒,想要開始用在 Next.js 的專案裡,該怎麼用呢?
其實如果想要在瀏覽器端去抓取資料的話,直接用 useSWR
在 component 裡就可以~ 就像上面的 code 一樣,基本上就是在 React components 裡面使用 useSWR
或是 custom SWR hooks 喔!
可是 Next.js 又可以做 pre-rendering,例如 SSG 或是 SSR,那還是可以用 SWR 嗎?當然可以的!SWR 是處理 client-side 的資料抓取方式,我們只要可以在 build time 時拿到資料讓頁面不完全是空的,然後 request time 時在瀏覽器再重新抓取最新資料。之前如果用 getStaticProps
或是 getServerSideProps
時,我們都會把 props
傳到 page (頁面) component 裡。可是用 SWR 的方式比較不一樣:
// 一樣在 getStaticProps 抓取資料
export async function getStaticProps () {
const article = await getArticleFromAPI()
// 要回傳 fallback 的資料!
return {
props: {
fallback: {
// 這 key 要跟 useSWR 用的 key 一樣喔~
'/api/article': article
}
}
}
}
// Article component
function Article() {
// 因為 fallback 有包含 `/api/article` 這 key,
// data 預設值會是 fallback 裡設定的值
// 當使用者在瀏覽器看到這畫面時,SWR 會抓取最新料
const { data } = useSWR('/api/article', fetcher)
return <h1>{data.title}</h1>
}
// Page component 收到 getStaticProps 回傳的 props.fallback
export default function Page({ fallback }) {
// SWRConfig 去做設定,在這裡設定 fallback 值
return (
<SWRConfig value={{ fallback }}>
<Article />
</SWRConfig>
)
}
所以產生頁面時會先塞資料進 HTML 檔案,不過當使用者在瀏覽器看到這畫面時,SWR 會抓取最新料,更新 HTML 裡原本的內容。
如果大家對於 client-side data fetching 有興趣或需求,真的可以去看看 SWR!簡單又方便,還能做 cache,也不用做 props drilling,實在很開心~ 不過我目前也沒有在大專案用過 SWR,所以不太清楚會不會不適用在較複雜的專案Q
祝大家週末愉快!
晚安 <3