Using SWR for Better Data Fetching

React
#Notes #React #SWR

傳統 API Data Fetching 經常需要依賴 state、redux 管理狀態,同時也要為效能優化去實作,如:Cache、Revalidate、Asynchronous、Dependent Fetching 等機制,但這些都可以透過 SWR 來簡化。

前言

最近工作上討論到過去專案上如何去優化載入速度及減少請求時,認識到 SWR 這個東西

因為當時的頁面反應速度非常慢,加上還是 Server Side Rendering (SSR) 的架構,導致每次載入頁面或做任何行為, 都會向後端發送請求,直到拿到新的頁面後再重新 Render。

據同事所說,當時每切換一個頁面需要花 2~3 秒時間才會載入,如果又同時在頁面上操作其他行為,還會不斷發送其他請求,更別提這是一個 EC,它還需要能夠乘載高併發流量,這根本不可能吧!

雖然在我入職後已經是前後端分離,且 SSR CSR 時機有明確定義,也用了 cache 加速載入,但還是覺得這故事很有趣!

SWR(stale-while-revalidate)

先來認識一下 SWR

SWR 是 Next.js 團隊 Vercel 所開發的 Custom Hook

名稱源自於 stale-while-revalidate,是來自 HTTP RFC 5861 一種快取策略。

這種策略的核心概念是:

在第一次載入發送 API request 時,先將 response 的資料存入快取中,當又有相同的 request 時,就可以直接返回的快取資料(stale),同時在 backgorund 去 revalidate 快取是否過期。

如果資料發生變動時,就會再 fetch 一次取得新的資料並更新快取中的資料,這樣用戶也就不會在等待過程看到一片空白的畫面,而是由舊的變成新的。

image

這樣的機制能夠確保資料的即時性,還能有效減少伺服器的負擔和頻繁的請求,從而提高效能。

HTTP Cache-Control

不過其實 HTTP Cache-Control 本身就能做到 stale-while-revalidate 並交給瀏覽器來處理了,那為何還要再用 SWR 這種套件再做一次相同的事情呢?

主要是因為 HTTP Cache-Control 比較適合管理靜態資源,像 API 這種比較屬於 dynamic data,透過 HTTP Cache-Control 只能 cache data,在一段時間內不需要再向 Server 請求,這機制比較屬於靜態的「緩存」行為。

因此當 API 資料需要根據不同頁面或行為更新時,HTTP Cache 就無法滿足需求了。

一般的 Data Fetching

一般在寫 fetch api 時通常會用 useEffect 在第一次載入時去觸發,然後存在 state 中

可能像這樣:

function Component() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        setData(response);
      } catch (error) {
        setError(error);
      }
      setIsLoading(false);
    };

    fetchData();
  }, []);

  if (error) return <p>Error: {error}</p>;
  if (isLoading) return <p>Loading...</p>;
  return <div>{data}</div>;
}

不過當 API 一多重複性就高,你可能就會把它封裝成 Custom Hook

function useFetch(url) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error.message);
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, error, isLoading };
}

export default useFetch;

然後在各別元件中使用

function Component() {
  const { data, error, isLoading } = useFetch('https://api.example.com/data');

  if (error) return <p>Error: {error}</p>;
  if (isLoading) return <p>Loading...</p>;
  return <div>{data}</div>;
}

而 SWR 套件也是這樣哦!

不過它多幫你實作了 Cache、Revalidate、Asynchronous、Dependent Fetching 等機制,讓你能很方便的運用他們。

useSWR

最基本的用法就是 useSWR

主要接受兩個參數:

  • key:唯一的鍵值,SWR 會根據 key 來做 cache,有點像快取的識別證,當 key 相同時,會直接從快取拿請求過的舊的資料。

  • fetcher:用來負責執行 request,並返回 response 的函式。你可以自訂這個函式,SWR 會去呼叫他來處理 Data Fetching,並將結果傳回 useSWR 的 data。

    // 定義 fetcher 函式
    const fetcher = url => fetch(url).then(res => res.json());
    

綜合範例:

function Component() {
  const fetcher = url => fetch(url).then(res => res.json());
  const { data, error } = useSWR('https://api.example.com/data', fetcher);

  if (error) return <p>Error: {error}</p>;
  if (!data) return <p>Loading...</p>;
  return <div>{data}</div>;
}

另外還有一個很棒的機制,通常會把 Data Fetching 封裝成 Hook 在不同元件中去複用,但這樣照理來說會有非常多個相同的 API request 對吧?

可實際上 SWR 因為是依賴「Global Cache」和「Deduplication」,所以原本多個相同的請求會只變成一個。

什麼是 Deduplication?

Deduplication 是 重複數據刪除技術,Deduplication 主要用於在短時間內「合併」相同的請求。

而在 SWR 中 softRevalidate 就是依賴 Deduplication 將重複的 API request 合併,避免觸發相同的 request。

這樣即便在載入過程中有多個元件同時需要相同的資料,SWR 也只會發出一次請求,之後相同的請求會直接從快取中取得結果。

參考來源