react-query手把手教程④-快取狀態與除錯工具

修仙大橙子 發表於 2022-06-23
React

快取狀態與除錯工具

寫在前面

由於國內較少有比較系統的react-query教程,因此筆者結合官方文件以及官方課程的內容,希望寫一個較為全面的教程。本文將以各種例子作為切入點,儘可能通俗易懂地講解相關知識點。如果有錯誤,還請大家在評論區指出,筆者會盡快改正。

目錄

由於快取狀態處於幕後,難以直觀地展現相關的變化,因此本章同時也會介紹除錯工具的相關使用,來詳細地說明,並且直觀地展現相關狀態的變化。

快取狀態

react-query通常在掛載元件時獲取資料;在獲取資料後,將資料儲存到快取中,並將該資料提供給元件使用。

在react-query獲取資料的過程中,主要會經歷以下三種狀態:

  • loading
  • error
  • success

這三種狀態(status)的流程關係如下:

graph TD
loading --> success
loading --> error

對於有開發經驗的同學應該不難理解,請求介面後,要麼成功,要麼失敗。

上面的三種狀態其實也對應useQuery鉤子中的isLoadingisSuccessisError屬性。也可以通過status屬性,獲取到loadingsuccesserror

當react-query進行後端請求查詢時,會有以下三個狀態:

  • idle:空閒,表示當前不需要從後端獲取資料
  • fetching: 獲取資料,表示當前正在從後端獲取資料
  • paused:暫停,表示原本嘗試從後端獲取資料,但是通常由於未聯網的原因導致暫停

fetchStatus將會在idlefetchingpaused這三個狀態間經歷迴圈:

graph TD
idle --> fetching
fetching --> paused
paused --> fetching
fetching --> idle

📢注意

在react-query中statusloading狀態(或者isLoadingtrue)指的是第一次從後端獲取成功之前的狀態,你可以檢視這個線上例子來理解這個狀態持續的週期:檢視線上例子

fetchStatusfetching狀態(或者isFetchingtrue)指的是每次從後端獲取資料的載入狀態(包含第一次獲取資料)。

整個生命週期,舉個例子:

假如你使用react-query從Github的介面請求了react的issue列表,你此次的請求結果將會在status中標記為successerror,或者從isSuccessisError中判斷請求成功或者失敗。

請求後端資料成功後,在寫入快取時,此時的快取狀態是fresh(最新)狀態,但是很快(預設過期時間是0ms)就會變為stale(老舊)狀態。

如果使用react的issue列表的每個元件都被解除安裝後,issue列表資料的快取狀態將會被標記為inactive(不活躍)狀態。此時資料將不會被刪除,直到一段時間後(預設為5分鐘),react-query將會從快取中刪除該條資料。

在變為inactive(不活躍)狀態之前,該條資料將會在fresh(最新)stale(老舊)之間來回切換,同時介面請求狀態也會在idlefetching之間切換。

👇🏻下面將詳細描述該例子過程的細節

使用DevTools來觀察快取狀態變化

為了更加直觀地觀察快取狀態的變化,可以使用DevTools進行直觀地展示。你需要在你的入口檔案中,新增下面的程式碼

import * as React from 'react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from 'react-query';
+ import { ReactQueryDevtools } from 'react-query/devtools';

import App from './App';

const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
const queryClient = new QueryClient();

root.render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
+      <ReactQueryDevtools initialIsOpen />
    </QueryClientProvider>
  </StrictMode>
);

上面的示例程式碼中,我們引入並設定了ReactQueryDevtools元件的initialIsOpen屬性,因此將會預設開啟除錯工具

接著我們開始請求介面,你可以在除錯工具中看到當前所有快取狀態的概覽,如下圖所示:

你也可以開啟線上演示觀察,在例子中點選【重新整理】按鈕,來觀察快取狀態的變化。

狀態概覽

fresh(最新)資料及stale(老舊)資料

react-query是否會觸發查詢函式,並從後端介面獲取資料,與快取狀態是:fresh(最新)狀態或stale(老舊)狀態有關。如果快取狀態是stale(老舊),表示該查詢將會有資格重新獲取,但如果快取狀態是fresh(最新)的,就不會重新獲取。

注意📢

如果你想更好地控制介面請求的頻率,請務必記住這些內容。

在預設情況下,後端返回的資料其快取狀態將會立即({staleTime: 0})從fresh(最新)狀態變為stale(老舊)狀態。

其實這樣做不難理解,因為當你請求到資料後,後端的資料有可能就發生了變化,因此當拿到後端返回的資料瞬間,快取狀態就是stale(陳舊)的了。

你可以將配置中的staleTime,設定一個毫秒數的數字,那麼快取將會在staleTime毫秒後過期(從fresh(最新)變為stale(陳舊))

注意:此時staleTime不能為0,不然沒有任何意義。

在下面的例子中,將staleTime設定為了60s,可以在DevTools中觀察一下快取狀態:

點選檢視線上演示

import * as React from 'react';
import { useQuery } from 'react-query';
import './style.css';

export default function App() {
  const getRepos = (username) =>
    fetch(`https://api.github.com/users/${username}/repos`).then((res) =>
      res.json()
    );

  const reposQuery = useQuery(
    ['repos', 'facebook'],
    () => getRepos('facebook'),
+    { staleTime: 1000 * 60 }
  );
  return (
    <div>
      <button
        onClick={() => {
          reposQuery.refetch();
        }}
      >
        重新整理
      </button>
      <p>
        {reposQuery.isLoading && '首次載入中...'}
        {reposQuery.isFetching && '重新整理中...'}
      </p>
      <p>{JSON.stringify(reposQuery.data)}</p>
    </div>
  );
}

如果把staleTime設定為Infinity,表示當前查詢的資料將只會獲取一次,且會在整個網頁的生命週期內快取。

現在應該很清楚地瞭解了,快取狀態是如何從fresh(最新)變為stale(老舊)的過程了。那麼是資料一旦變為stale(老舊)就會重新獲取麼?並不是,而是需要滿足一定的觸發條件才可以。下面將介紹這些觸發條件:

什麼時候會觸發重新獲取資料操作?

在react-query中並不是快取從fresh(最新)轉換為stale(老舊)狀態時,就會重新獲取。而是依賴於以下五個觸發條件重新獲取資料資料:

①元件掛載時

當元件首次載入,將會觸發資料的獲取。如果元件被解除安裝後再次被載入,此時也會觸發資料的重新獲取。

②查詢鍵改變時

在前面章節的示例中也提到過,當查詢鍵改變時,將會自動觸發資料的重新獲取。

ps: 如果你的查詢鍵中有物件,也不要擔心react-query無法檢測出來變化,因為react-query會進行深度比對。

③頁面重新被聚焦

當使用者把瀏覽器重新聚焦(比如在瀏覽器中開啟了要除錯的頁面,之後切換到了vscode裡面編輯程式碼,編輯完成後需要看下除錯的頁面效果,又切換到了瀏覽器),或者切換標籤頁(比如開了兩個瀏覽器的選項卡,一個是百度,一個是你的頁面,從百度的選項卡切換至你的頁面)時,react-query都會自動重新獲取資料。

這個觸發條件是預設開啟的,如果希望關閉這個觸發條件,可以把refetchOnWindowFocus選項設定為false來禁止。

④網路重新連線

在當前使用者斷網重新聯網後,react-query將會重新獲取資料。比如你的使用者拿著手機在地下通道中,訊號中斷導致斷網,在使用者從地下通道出來時,訊號連線上後,又重新聯網,此時資料將會重新獲取。

這個觸發條件同樣是預設開啟的。如果希望關閉這個觸發條件,可以把refetchOnReconnect選項設定為false來禁止。

⑤定時重新整理

這是一個需要你自己配置的一個觸發條件。當你在配置中設定refetchInterval為數字(代表xxx毫秒)時。無論此時資料是fresh(最新)還是stale(老舊)的快取狀態,react-query都會在你設定的毫秒時間間隔內重新獲取資料

以上五種就是react-query內建的重新獲取觸發條件。

注意❗️❗️❗️❗️📢

除了定時重新整理外,其它的觸發器,都需要狀態是stale(陳舊)才可以觸發,你可以在之前的例子中,嘗試先失焦頁面後,再聚焦(也就是③的觸發條件),你會發現當資料在fresh(最新)狀態時,並沒有重新請求資料。

清理快取

在快取狀態處於inactive(不活躍)狀態或者使用這個查詢資料的元件解除安裝時,超過5分鐘(預設情況下)後,react-query將會自動清除該快取。

如果你希望自定義這個時間,你可以使用cacheTime配置,下面的例子中將cacheTime設定為0,實現的效果是,當查詢資料在inactive狀態時,立即從快取中刪除。

const userQuery = useQuery(
  ["user", username],
  () =>
    fetch(`https://api.github.com/users/${username}`)
    .then(res => res.json()),
  { cacheTime: 0 }
);

當在快取中刪除查詢資料後,此時的表現和這個查詢資料從未載入過一樣,在查詢資料從後端返回前,將看到載入狀態,獲取到資料後,將看到查詢資料。