最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React Query + REST API 最佳實(shí)踐

 更新時(shí)間:2026年06月03日 08:18:46   作者:ZengLiangYi  
本文介紹了如何利用React Query構(gòu)建React應(yīng)用的REST API數(shù)據(jù)層,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

為什么選 React Query

在沒有 React Query 之前,常見的做法是 useState + useEffect + fetch

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  let cancelled = false;
  fetch('/api/notes')
    .then(res => res.json())
    .then(json => { if (!cancelled) setData(json.data); })
    .catch(err => { if (!cancelled) setError(err); })
    .finally(() => { if (!cancelled) setLoading(false); });
  return () => { cancelled = true; };
}, []);

這段代碼有三個(gè)明顯的問題:

  1. 緩存缺失 -- 切換頁面再切回來,數(shù)據(jù)重新請(qǐng)求,白屏閃爍。
  2. 狀態(tài)同步困難 -- 多個(gè)組件用同一份數(shù)據(jù)時(shí),需要手動(dòng)傳遞或用全局狀態(tài)管理。
  3. 競態(tài)風(fēng)險(xiǎn) -- 快速切換頁面,舊請(qǐng)求可能覆蓋新請(qǐng)求的結(jié)果。

React Query 解決了以上所有問題,同時(shí)提供了后臺(tái)刷新、樂觀更新、分頁、輪詢等開箱即用的能力。

QueryClient 配置

ChatCrystal 在應(yīng)用入口 App.tsx 中創(chuàng)建 QueryClient,并通過 QueryClientProvider 注入整個(gè)應(yīng)用:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000,  // 30 秒內(nèi)認(rèn)為數(shù)據(jù)是新鮮的,不重新請(qǐng)求
      retry: 1,           // 請(qǐng)求失敗只重試 1 次
    },
  },
});

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* 路由、主題等組件 */}
    </QueryClientProvider>
  );
}

幾個(gè)關(guān)鍵參數(shù)的含義:

參數(shù)默認(rèn)值說明
staleTime0數(shù)據(jù)被視為"過期"的時(shí)間。設(shè)為 30 秒意味著 30 秒內(nèi)重新掛載組件不會(huì)觸發(fā)網(wǎng)絡(luò)請(qǐng)求
retry3失敗后的重試次數(shù)。API 通常重試 1 次就夠了
gcTime (v5)5 分鐘數(shù)據(jù)不再被任何組件使用后,在緩存中保留的時(shí)間

ChatCrystal 把 staleTime 設(shè)為 30 秒,這對(duì)內(nèi)部工具類應(yīng)用很合理 -- 數(shù)據(jù)不需要實(shí)時(shí),但也不能太舊。

API 層設(shè)計(jì)

ChatCrystal 在 lib/api.ts 中封裝了一個(gè)通用的請(qǐng)求函數(shù):

const BASE = "/api";

async function request<T>(path: string, options?: RequestInit): Promise<T> {
  const headers: Record<string, string> = {};
  if (options?.body) {
    headers["Content-Type"] = "application/json";
  }
  const res = await fetch(`${BASE}${path}`, { headers, ...options });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.error || `Request failed: ${res.status}`);
  }
  const json = await res.json();
  if (!json.success) throw new Error(json.error || "Unknown error");
  return json.data;
}

這里的約定是:后端統(tǒng)一返回 { success: boolean, data: T, error?: string } 的格式,request 函數(shù)負(fù)責(zé)解包。如果有同學(xué)在設(shè)計(jì)自己的 API 層,建議也采用類似的統(tǒng)一響應(yīng)格式。

開發(fā)環(huán)境下的跨域問題由 Vite 的代理解決,不需要在前端處理 CORS:

// client/vite.config.ts
server: {
  port: 13721,
  proxy: {
    '/api': 'http://localhost:3721',
  },
},

所有 /api 開頭的請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到后端的 3721 端口。

自定義 Hook -- 將查詢邏輯封裝為可復(fù)用單元

ChatCrystal 不在頁面組件中直接寫 useQuery,而是把每個(gè)查詢封裝成自定義 Hook。這有三個(gè)好處:頁面組件更簡潔、查詢邏輯可復(fù)用、queryKey 管理集中化。

// hooks/use-notes.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api';

// 列表查詢 -- 帶篩選和分頁參數(shù)
export function useNotes(params?: {
  tag?: string;
  search?: string;
  offset?: number;
  limit?: number;
}) {
  return useQuery({
    queryKey: ['notes', params],
    queryFn: () => api.getNotes(params),
  });
}

// 單條詳情查詢 -- 啟用條件控制
export function useNote(id: number) {
  return useQuery({
    queryKey: ['note', id],
    queryFn: () => api.getNote(id),
    enabled: id > 0,
  });
}

注意 useNote 中的 enabled: id > 0。當(dāng)路由參數(shù)還沒準(zhǔn)備好時(shí)(比如 id 還是 undefined),查詢不會(huì)執(zhí)行,避免無意義的請(qǐng)求。

queryKey 的設(shè)計(jì)原則

React Query 用 queryKey 做緩存的鍵。設(shè)計(jì)原則是:

  • 列表和詳情用不同的 key:['notes', params] 和 ['note', id] 是兩個(gè)獨(dú)立的緩存條目。
  • 參數(shù)變化自動(dòng)觸發(fā)重新請(qǐng)求:useNotes({ tag: 'react' }) 和 useNotes({ tag: 'vue' }) 各自獨(dú)立緩存。
  • 數(shù)組中的對(duì)象會(huì)被深度比較:所以 params 對(duì)象中的字段順序不影響緩存匹配。

useMutation -- 寫操作與緩存失效

查詢是讀,mutation 是寫。寫操作完成后,緩存中的舊數(shù)據(jù)需要更新。ChatCrystal 的做法是:在 onSuccess 中批量失效相關(guān) query。

export function useDeleteNote() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, reason, comment }: {
      id: number;
      reason: ExperienceReviewReason;
      comment?: string;
    }) => api.deleteNote(id, { reason, comment, source: 'web' }),
    onSuccess: (data, variables) => {
      // 刪除一條筆記后,需要刷新所有相關(guān)數(shù)據(jù)
      queryClient.invalidateQueries({ queryKey: ['notes'] });
      queryClient.invalidateQueries({ queryKey: ['note', variables.id] });
      queryClient.invalidateQueries({ queryKey: ['tags'] });
      queryClient.invalidateQueries({ queryKey: ['note-relations'] });
      queryClient.invalidateQueries({ queryKey: ['relation-graph'] });
      queryClient.invalidateQueries({ queryKey: ['conversations'] });
      queryClient.invalidateQueries({ queryKey: ['conversation', data.conversationId] });
      queryClient.invalidateQueries({ queryKey: ['status'] });
    },
  });
}

invalidateQueries 不會(huì)立即重新請(qǐng)求。它只是把匹配的緩存標(biāo)記為"過期",下次組件掛載時(shí)才會(huì)重新獲取。如果當(dāng)前有組件正在使用這些數(shù)據(jù),React Query 會(huì)立即在后臺(tái)發(fā)起一次刷新。

典型的 mutation 流程

以"總結(jié)對(duì)話"為例,這是一個(gè)異步操作,涉及隊(duì)列系統(tǒng):

export function useSummarize() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (conversationId: string) => api.summarize(conversationId),
    onSuccess: (_data, conversationId) => {
      queryClient.invalidateQueries({ queryKey: ['queue-status'] });
      queryClient.invalidateQueries({ queryKey: ['conversation', conversationId] });
      queryClient.invalidateQueries({ queryKey: ['conversations'] });
      queryClient.invalidateQueries({ queryKey: ['notes'] });
      queryClient.invalidateQueries({ queryKey: ['status'] });
    },
  });
}

點(diǎn)擊"生成摘要"按鈕后:

  1. mutationFn 發(fā)送 POST 請(qǐng)求,將任務(wù)加入隊(duì)列。
  2. onSuccess 失效隊(duì)列狀態(tài)的緩存,StatusBar 開始輪詢進(jìn)度。
  3. 隊(duì)列完成后,相關(guān)頁面的數(shù)據(jù)會(huì)在下次訪問時(shí)自動(dòng)刷新。

輪詢 -- 處理異步任務(wù)狀態(tài)

ChatCrystal 有一個(gè)任務(wù)隊(duì)列系統(tǒng),需要實(shí)時(shí)顯示進(jìn)度。React Query 的 refetchInterval 可以實(shí)現(xiàn)智能輪詢:

export function useQueueTasks() {
  return useQuery({
    queryKey: ['queue-status'],
    queryFn: () => api.getQueueStatus(),
    refetchInterval: (query) => {
      const data = query.state.data as QueueSnapshot | undefined;
      // 有活躍任務(wù)時(shí) 2 秒輪詢一次,否則 10 秒一次
      return data && data.active > 0 ? 2000 : 10000;
    },
  });
}

refetchInterval 支持傳入函數(shù),可以根據(jù)當(dāng)前數(shù)據(jù)動(dòng)態(tài)調(diào)整輪詢頻率。這個(gè)技巧很實(shí)用 -- 任務(wù)進(jìn)行中時(shí)高頻輪詢以獲取實(shí)時(shí)進(jìn)度,空閑時(shí)降低頻率以節(jié)省資源。

Dashboard 頁面也用了一個(gè)簡單的 30 秒輪詢來保持狀態(tài)數(shù)據(jù)新鮮(這個(gè) hook 定義在 hooks/use-conversations.ts 中):

export function useStatus() {
  return useQuery({
    queryKey: ['status'],
    queryFn: () => api.getStatus(),
    refetchInterval: 30_000,
  });
}

加載與錯(cuò)誤狀態(tài)處理

React Query 的 useQuery 返回三個(gè)關(guān)鍵狀態(tài)字段:isLoadingisError、data。頁面組件根據(jù)這些字段渲染不同內(nèi)容。

// 頁面組件中的典型模式
export function Notes() {
  const { data, isLoading } = useNotes({ /* params */ });

  if (isLoading) {
    return <p className="text-muted text-sm">加載中...</p>;
  }

  if (!data || data.items.length === 0) {
    return <div className="text-center py-12">暫無筆記</div>;
  }

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
      {data.items.map(note => (
        <NoteCard key={note.id} note={note} />
      ))}
    </div>
  );
}

對(duì)于 mutation 的錯(cuò)誤,ChatCrystal 在搜索頁面展示了典型的錯(cuò)誤處理:

const search = useMutation({
  mutationFn: (q: string) => api.search(q, 10, expand),
});

// 在 JSX 中
{search.isError && (
  <p className="text-error text-sm">{search.error.message}</p>
)}

mutation 的按鈕也需要根據(jù) isPending 狀態(tài)禁用,防止重復(fù)提交:

<button
  onClick={handleSearch}
  disabled={search.isPending || !query.trim()}
>
  {search.isPending ? <Loader2 className="animate-spin" /> : '搜索'}
</button>

分頁實(shí)現(xiàn)

ChatCrystal 使用傳統(tǒng)的偏移量分頁,每頁 20 條:

export function Notes() {
  const [page, setPage] = useState(0);
  const limit = 20;

  const { data, isLoading } = useNotes({
    search: search || undefined,
    tag: activeTag || undefined,
    offset: page * limit,
    limit,
  });

  // 翻頁時(shí)重置到第一頁
  const handleSearch = (value: string) => {
    setSearch(value);
    setPage(0);
  };

  // 分頁控件
  {data && data.total > limit && (
    <div className="flex items-center justify-center gap-4 mt-4">
      <button disabled={page === 0} onClick={() => setPage(p => p - 1)}>
        上一頁
      </button>
      <span>{page + 1} / {Math.ceil(data.total / limit)}</span>
      <button disabled={(page + 1) * limit >= data.total} onClick={() => setPage(p => p + 1)}>
        下一頁
      </button>
    </div>
  )}
}

后端返回 { items, total, offset, limit } 的格式,前端根據(jù) total 計(jì)算總頁數(shù)。注意 offset 是 queryKey 的一部分,翻頁時(shí) React Query 會(huì)自動(dòng)為每一頁創(chuàng)建獨(dú)立的緩存條目 -- 這意味著回翻時(shí)不需要重新請(qǐng)求。

如果需要無限滾動(dòng)(infinite scroll),React Query 還提供了 useInfiniteQuery,用 getNextPageParamfetchNextPage 實(shí)現(xiàn)自動(dòng)加載下一頁。但 ChatCrystal 的數(shù)據(jù)量不大,傳統(tǒng)分頁更合適。

樂觀更新

React Query 支持樂觀更新(Optimistic Update),在 mutation 發(fā)送請(qǐng)求之前就更新 UI,讓操作感覺更流暢。ChatCrystal 目前沒有使用這個(gè)模式,但這是一個(gè)值得了解的最佳實(shí)踐。

// 樂觀更新示例(非 ChatCrystal 代碼,僅為教學(xué))
export function useUpdateNote() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (updated: Note) => api.updateNote(updated),
    onMutate: async (updated) => {
      // 1. 取消正在進(jìn)行的查詢,避免覆蓋我們的樂觀更新
      await queryClient.cancelQueries({ queryKey: ['note', updated.id] });
      // 2. 快照當(dāng)前數(shù)據(jù),以便回滾
      const previous = queryClient.getQueryData(['note', updated.id]);
      // 3. 樂觀更新緩存
      queryClient.setQueryData(['note', updated.id], updated);
      return { previous };
    },
    onError: (_err, updated, context) => {
      // 4. 出錯(cuò)時(shí)回滾到快照
      queryClient.setQueryData(['note', updated.id], context?.previous);
    },
    onSettled: (_data, _err, updated) => {
      // 5. 無論成功失敗,都重新獲取服務(wù)端數(shù)據(jù)
      queryClient.invalidateQueries({ queryKey: ['note', updated.id] });
    },
  });
}

這個(gè)模式的要點(diǎn)是:onMutate 中做樂觀更新和快照,onError 中回滾,onSettled 中同步服務(wù)端真實(shí)數(shù)據(jù)。

懶加載路由與 Suspense

ChatCrystal 的路由使用了 React 的 lazy + Suspense,結(jié)合 React Router v7:

const NotesPage = lazy(() =>
  import('@/pages/Notes.tsx').then(module => ({ default: module.Notes }))
);

// 路由定義
<Route path="/notes" element={
  <RouteSuspense>
    <NotesPage />
  </RouteSuspense>
} />

頁面組件按需加載,首屏只加載當(dāng)前路由需要的代碼。RouteSuspense 包裹了統(tǒng)一的加載提示。

總結(jié)

ChatCrystal 的數(shù)據(jù)層架構(gòu)可以歸納為三層:

  1. API 層lib/api.ts)-- 封裝 fetch,統(tǒng)一錯(cuò)誤處理和響應(yīng)格式。
  2. Hook 層hooks/ 目錄)-- 每個(gè)業(yè)務(wù)域一個(gè)文件,用 useQuery / useMutation 封裝讀寫操作,管理 queryKey 和緩存失效策略。
  3. 頁面層pages/ 目錄)-- 只關(guān)心 UI 渲染,從 Hook 獲取數(shù)據(jù)和狀態(tài)。

這種分層的好處是:頁面組件不關(guān)心數(shù)據(jù)從哪里來,Hook 不關(guān)心數(shù)據(jù)怎么展示。當(dāng)后端 API 變化時(shí),只需要修改 api.ts;當(dāng)緩存策略需要調(diào)整時(shí),只需要修改對(duì)應(yīng)的 Hook。

如果你正在構(gòu)建一個(gè)需要頻繁與 REST API 交互的 React 應(yīng)用,React Query 是目前最好的選擇。它不只是一個(gè)數(shù)據(jù)獲取庫,更是一個(gè)完整的異步狀態(tài)管理方案。

到此這篇關(guān)于React Query + REST API 最佳實(shí)踐的文章就介紹到這了,更多相關(guān)React Query + REST API 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React中10種Hook的使用介紹

    React中10種Hook的使用介紹

    Hook 是 React 16.8 的新增特性,本文主要介紹了10種Hook的使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • 更強(qiáng)大的React 狀態(tài)管理庫Zustand使用詳解

    更強(qiáng)大的React 狀態(tài)管理庫Zustand使用詳解

    這篇文章主要為大家介紹了更強(qiáng)大的React 狀態(tài)管理庫Zustand使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • React中的stopPropagation和preventDefault實(shí)踐記錄

    React中的stopPropagation和preventDefault實(shí)踐記錄

    本文詳細(xì)介紹了事件冒泡、捕獲與React合成事件體系下的表現(xiàn)區(qū)別,包括事件傳播階段、處理方式、事件池機(jī)制等核心概念,對(duì)React?stopPropagation和preventDefault相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • React中useState的使用方法及注意事項(xiàng)

    React中useState的使用方法及注意事項(xiàng)

    useState通過在函數(shù)組件里調(diào)用它來給組件添加一些內(nèi)部state,下面這篇文章主要給大家介紹了關(guān)于React中useState的使用方法及注意事項(xiàng)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • react實(shí)現(xiàn)可播放的進(jìn)度條

    react實(shí)現(xiàn)可播放的進(jìn)度條

    這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)可播放的進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • react自定義實(shí)現(xiàn)狀態(tài)管理詳解

    react自定義實(shí)現(xiàn)狀態(tài)管理詳解

    這篇文章主要為大家詳細(xì)介紹了react如何自定義實(shí)現(xiàn)狀態(tài)管理,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-01-01
  • 淺談使用React.setState需要注意的三點(diǎn)

    淺談使用React.setState需要注意的三點(diǎn)

    本篇文章主要介紹了淺談使用React.setState需要注意的三點(diǎn),提出了三點(diǎn)對(duì) React 新手來說是很容易忽略的地方,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • react實(shí)現(xiàn)頭部導(dǎo)航,選中狀態(tài)底部出現(xiàn)藍(lán)色條塊問題

    react實(shí)現(xiàn)頭部導(dǎo)航,選中狀態(tài)底部出現(xiàn)藍(lán)色條塊問題

    這篇文章主要介紹了react實(shí)現(xiàn)頭部導(dǎo)航,選中狀態(tài)底部出現(xiàn)藍(lán)色條塊問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • antd?table動(dòng)態(tài)修改表格高度的實(shí)現(xiàn)

    antd?table動(dòng)態(tài)修改表格高度的實(shí)現(xiàn)

    本文主要介紹了antd?table動(dòng)態(tài)修改表格高度的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • React 自動(dòng)聚焦字段使用詳解

    React 自動(dòng)聚焦字段使用詳解

    這篇文章主要為大家介紹了React 自動(dòng)聚焦字段使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07

最新評(píng)論

泊头市| 苍溪县| 兴城市| 阿拉尔市| 嘉祥县| 锦州市| 泾阳县| 慈溪市| 伊吾县| 临江市| 唐河县| 西平县| 洛南县| 澄江县| 肥城市| 河东区| 榕江县| 潮州市| 衡阳市| 正安县| 门源| 锦州市| 黄梅县| 平果县| 会东县| 封丘县| 浠水县| 通道| 扎鲁特旗| 平罗县| 尉犁县| 云梦县| 中阳县| 墨江| 陇南市| 东辽县| 纳雍县| 叶城县| 龙川县| 郎溪县| 嵊泗县|