用 useQuery 拿資料:GraphQL 查詢入門指南

更新日期: 2025 年 6 月 10 日

當你開始在 React 中使用 Apollo Client 整合 GraphQL,最常遇到的第一個需求就是「從後端拿資料」。

這時候,Apollo 提供的 useQuery hook 就是你最好的工具。

這篇文章會一步一步帶你了解:

  • GraphQL 查詢(Query)的基本寫法
  • useQuery 的使用方式
  • 如何處理 loading / error 狀態
  • 最後會搭配實例練習:顯示一個產品清單

無論你剛接觸 GraphQL 還是已經懂基本語法,這篇都能幫你快速掌握從前端「發送請求」到「畫面渲染資料」的整個流程。

GraphQL 查詢語法快速預習:從結構到實作

在正式開始學習 useQuery 這個 React hook 之前,我們需要先掌握 GraphQL 的查詢語法(Query)是怎麼運作的。

這是你與後端互動的第一步。

GraphQL 查詢是什麼?

GraphQL 是一種 API 查詢語言,讓你可以明確告訴後端「我只想要哪些資料」。

不像 REST API 往往需要開好幾個 endpoint,GraphQL 一個 endpoint 就能查詢所有資料,重點是查得剛剛好,不多不少

以下是一個最基本的查詢:

query GetProducts {
  products {
    id
    name
    price
  }
}

這段查詢代表:「請幫我拿到 productsidnameprice 欄位。」

  • query 是 GraphQL 的操作類型(除了 query 還有 mutationsubscription
  • GetProducts 是這次查詢的名稱(可以自己取,主要用來辨識這筆請求)
  • { products { ... } } 是你實際想要的資料結構

簡單來說,GraphQL 查詢的結構很像你在說:「我要 products,裡面請幫我拿 idnameprice。」

小提醒:

GraphQL 像是點菜單,你可以自由組合你要的資料欄位

不需要後端為你額外建立一個 getProductListLite API,你自己就能決定要多精簡的資料。

使用 gql 撰寫查詢語法:Apollo 的 inline query 寫法

在 Apollo Client 中,我們會使用 gql 這個特殊的標籤函式(tagged template literal function),來定義 GraphQL 查詢。

因為查詢是直接寫在 JavaScript 或 TypeScript 檔案裡,所以這種方式又被俗稱為 “inline query”(內嵌查詢)

以下是寫法範例:

import { gql } from '@apollo/client';

const GET_PRODUCTS = gql`
  query GetProducts {
    products {
      id
      name
      price
    }
  }
`;

這段程式碼做了什麼?

區塊功能說明
gql 標籤將字串轉換為 GraphQL 查詢結構,Apollo Client 能正確辨識與送出請求
GET_PRODUCTS儲存這段查詢內容,稍後會傳給 useQuery(GET_PRODUCTS) 使用
query ... 區塊實際的 GraphQL 查詢語法,與後端協定取得資料

這種寫法簡潔直觀,很適合初學者學習或小型專案開發。

另一種做法:使用 .graphql 檔案管理查詢

除了 inline 寫法,Apollo Client 也支援使用 .graphql(或 .gql)副檔名的外部檔案來撰寫查詢。

這種方式比較常見於中大型專案,尤其當查詢邏輯很多、需要多人協作時。

範例如下:

# ./queries/getProducts.graphql
query GetProducts {
  products {
    id
    name
    price
  }
}

然後搭配工具(如 Webpack loader 或 Vite plugin)就能這樣 import:

import GET_PRODUCTS from './queries/getProducts.graphql';
import { useQuery } from '@apollo/client';

const { data, loading, error } = useQuery(GET_PRODUCTS);

✅ 這種方式也很適合搭配 GraphQL Code Generator 使用,自動產生型別安全的 hooks,像是 useGetProductsQuery,大幅提升開發效率與正確性。

查詢名稱為什麼要取?(無論 inline 或 external)

query GetProducts { ... }

這裡的 GetProducts 是這筆查詢的名稱。雖然不是必填,但強烈建議你加上,因為它:

  • 有助於除錯(在 GraphQL playground 或 devtools 中會顯示名稱)
  • 如果後端開啟查詢紀錄,可以追蹤是哪個操作導致錯誤
  • 更容易與其他查詢區分,特別是多人協作時

使用 useQuery 發送請求:從初始化到畫面渲染的全流程

在上一節中,我們學會了如何撰寫一段 GraphQL 查詢。

現在,我們要將這段查詢「真的送出去」,並將取得的資料顯示在畫面上。

這就需要用到 Apollo Client 提供的 hook —— useQuery

useQuery 是什麼?它從哪裡來?有什麼用?

useQuery 是由 Apollo Client 所提供的React 專用工具函式,全名叫做 React Hook

它不是 React 原生提供的 Hook(像是 useStateuseEffect),而是 Apollo Client 為了讓你在 React 中更方便操作 GraphQL 查詢,所特別設計的函式。

你可以這樣理解:

useQuery = Apollo Client 提供的「查資料專用 Hook」

只要你已經安裝並設定好 Apollo Client,就可以在任何 React 函式元件中,直接使用 useQuery 來發送 GraphQL 查詢,而不需要自己去寫 fetch 或 useEffect。

你不需要再額外寫 fetchuseEffect,它已經跟 React 的生命週期與重新渲染機制整合好了

useQuery 幫你整合了什麼?

傳統做法(要自己處理)useQuery 幫你做掉了
自己寫 fetch 請求 GraphQL API✅ 內建送出 GraphQL 查詢
自己寫 useState 管理 loading / error 狀態✅ 自動提供 loading、error、data 三種狀態
自己用 useEffect 管理副作用與觸發時機✅ 查詢會在元件 mount 時自動觸發
自己處理快取、refetch、polling✅ 內建快取策略,可用 refetch() 手動更新資料
自己處理 re-render 的條件與觸發時機✅ Apollo 在查詢完成時自動更新 React 元件狀態(重新渲染)

🎯 初學者可以這樣理解:

當你在 React 中使用 useQuery

const { data, loading, error } = useQuery(GET_PRODUCTS);

你就像是在寫:

useEffect(() => {
  fetch('/graphql', { ... })
    .then(res => res.json())
    .then(data => setData(data))
    .catch(err => setError(err));
}, []);

而且 Apollo 還幫你:

  • 加了型別支援
  • 加了自動快取
  • 加了資料更新通知(re-render)
  • 加了錯誤格式化與處理
  • 加了開發工具支援(例如 Apollo DevTools)

這就是為什麼 useQuery 是 React 使用 Apollo Client 時的「核心武器」之一。

最基本的使用方式:一行就發送請求

假設你已經定義好 GraphQL 查詢(例如 GET_PRODUCTS),你只要寫一行:

import { useQuery } from '@apollo/client';

const { data, loading, error } = useQuery(GET_PRODUCTS);

Apollo 就會:

  1. 自動發送這筆 GraphQL 查詢
  2. 回傳三種狀態讓你使用:
  • loading:資料請求中
  • error:請求失敗
  • data:請求成功,並回傳查詢結果
  1. 將查詢結果快取下來(下一次可能不會重複打 API)
  2. 當資料變動或 refetch 時,自動更新畫面

每個回傳值的實際用途解析

回傳值資料型別用途說明
loadingboolean如果正在請求中,這個值為 true,可用來顯示「資料載入中」畫面或動畫
errorApolloError如果請求發生錯誤,這個欄位會包含錯誤訊息與細節,可用來顯示錯誤提示或寫入錯誤 log
data任意(object)查詢成功後回傳的資料,結構會依照你的查詢內容而定

這三個值會隨著請求狀態「即時更新」,你只需要在畫面中用條件判斷搭配 JSX 顯示不同畫面,就能達成非常流暢的資料體驗。

完整實作:從資料請求到畫面渲染

以下是一個實際範例:顯示產品清單,資料來自後端 GraphQL 查詢。

import { useQuery } from '@apollo/client';
import { GET_PRODUCTS } from './queries';

function ProductList() {
  const { data, loading, error } = useQuery(GET_PRODUCTS);

  if (loading) return <p>載入中...</p>;
  if (error) return <p>錯誤發生:{error.message}</p>;

  return (
    <ul>
      {data.products.map((product) => (
        <li key={product.id}>
          {product.name} - NT${product.price}
        </li>
      ))}
    </ul>
  );
}

1️⃣ const { data, loading, error } = useQuery(GET_PRODUCTS);

  • 元件一載入(mount),useQuery 立刻自動發送請求。
  • Apollo Client 根據查詢內容決定是否要從快取取資料、還是要呼叫後端。
  • 此時 loadingtrue,畫面會顯示「載入中」。

2️⃣ if (loading) return <p>載入中...</p>;

  • 請求尚未完成時,這段會被執行,讓畫面有即時回饋。
  • 如果你要放 Skeleton 或 Spinner,也可以在這裡加上。

3️⃣ if (error) return <p>錯誤發生:{error.message}</p>;

  • 如果 GraphQL 查詢失敗(例如伺服器錯誤、欄位拼錯、網路斷線),這段會顯示錯誤訊息。
  • 初期開發時,這是幫助你除錯的重要訊號。

4️⃣ return ( ... <ul> ... )

  • 查詢完成、資料取得,data 就會包含 products 陣列。
  • 接著你可以用 .map() 來逐一渲染每一筆資料。
  • 這整段邏輯會隨著 data 更新而自動 re-render,完全不需手動操作狀態或 refetch。

進階技巧:查詢變數與手動重新發送請求

當你的查詢邏輯開始依賴使用者輸入、選單切換、或需要重新整理資料時,你就需要學會使用變數與 refetch 技巧

查詢變數(GraphQL Variables):讓查詢變得動態

在初學階段,我們的查詢通常是「靜態的」——例如直接拿所有商品列表。

但實際應用中,你經常會想要根據使用者的輸入(例如搜尋關鍵字)動態地查資料。這時候就要用到 GraphQL 查詢變數

假設你想要根據關鍵字搜尋商品,這是帶有變數的查詢語法:

query SearchProducts($keyword: String!) {
  products(keyword: $keyword) {
    id
    name
  }
}
  • $keyword 是一個查詢變數,型別為 String!(非空字串)
  • 查詢的 products 欄位接收一個 keyword 參數

這樣你就可以在查詢時,動態決定要搜尋什麼關鍵字。

🔗 對應到 React:怎麼傳入變數?

Apollo 的 useQuery 可以透過 variables 參數傳入變數:

import { useQuery } from '@apollo/client';

const { data, loading, error } = useQuery(SEARCH_PRODUCTS, {
  variables: {
    keyword: '手機', // 可以是任意字串,也可以來自 state
  },
});

你也可以用 state 綁定搜尋框的輸入,動態更新查詢參數:

const [keyword, setKeyword] = useState('');

const { data, loading, error } = useQuery(SEARCH_PRODUCTS, {
  variables: { keyword },
});

這樣每當 keyword 變動,Apollo 會自動重新執行查詢,取得對應的結果。

⚠️ 小提醒:變數變動 = 自動 refetch

Apollo 的行為預設是:

當你傳入的 variables 值有改變時,useQuery 會自動重新送出查詢。

這非常適合用在搜尋框、篩選條件、分類切換等互動功能上。

手動重新查詢:用 refetch() 重新整理資料

除了依據變數自動查詢,你也可以使用 refetch() 這個方法手動重新抓資料。

const { data, loading, error, refetch } = useQuery(GET_PRODUCTS);

return (
  <>
    <button onClick={() => refetch()}>
      重新整理資料
    </button>
    {loading ? <p>載入中...</p> : (
      <ul>
        {data.products.map(p => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    )}
  </>
);

這個 refetch() 方法可以在任意時機觸發,適合用在:

  • 使用者點「重新整理」按鈕
  • 提交表單後刷新資料
  • 外部事件觸發資料更新(像是 websocket 回傳通知時)

🔄 補充:也可以傳入新的變數給 refetch

refetch 支援傳入新的查詢變數,例如:

refetch({ keyword: '新查詢詞' });

這會用新的變數值重新執行查詢,非常適合配合輸入框或篩選欄位:

<input
  value={keyword}
  onChange={(e) => setKeyword(e.target.value)}
/>

<button onClick={() => refetch({ keyword })}>
  搜尋
</button>

錯誤處理與 UX 小技巧:讓畫面更穩定、使用者體驗更好

當你在應用中發送 API 請求時,失敗是常態而不是例外

不論是網路中斷、查詢寫錯、後端回傳資料異常,這些情況都很容易發生。

如果你沒有妥善處理這些狀況,畫面可能會出錯、閃爍、或直接顯示空白,導致使用者無所適從。

常見錯誤類型與對應解釋

當你使用 useQuery 時,可能會遇到這些錯誤訊息:

錯誤訊息發生原因說明
Network error無法連接到 GraphQL 伺服器。可能是 API 網址錯了,伺服器沒開,或是使用者網路中斷。
GraphQL error查詢語法本身沒錯,但查詢的欄位名稱拼錯,或後端 Schema 沒有你要的欄位。可能回傳錯誤陣列。
data 為 undefined通常是還在 loading 階段時就去讀取資料,或伺服器雖然有回傳但資料是 null。需要加以保護。
Cannot read property ...沒有加上條件判斷就存取了 data.xxx,導致在 data 還沒來時就出錯。
Unexpected token < in JSON通常是 API 回傳了 HTML(像是 404 頁面),代表 URL 或伺服器配置錯誤。

✅ 正確錯誤處理範例

const { data, loading, error } = useQuery(GET_PRODUCTS);

if (loading) return <Spinner />;
if (error) return <Alert message={`發生錯誤:${error.message}`} type="error" />;

if (!data || !data.products) {
  return <Alert message="查無資料或資料格式異常" type="warning" />;
}

這樣可以確保使用者不會在畫面空白時感到困惑,同時幫助你除錯與收集錯誤訊息。

UX 提示:畫面狀態不是只有「有資料」和「沒資料」

良好的使用者體驗,來自於每個狀態都有明確的視覺回饋。當你用 useQuery 發送請求時,畫面會經歷至少三種狀態

畫面狀態該做什麼建議元件
載入中告訴使用者正在抓資料,可以顯示 loading 動畫Spinner、Skeleton
錯誤發生顯示錯誤訊息,並盡可能提供「重試」機會Alert、ErrorBlock
資料為空查詢成功但回傳資料為空,要提示「查無結果」或建議下一步EmptyState、提示文字
資料正確回傳正常渲染資料清單清單元件、表格等

📦 建議搭配元件

UI 狀態可以搭配的 UI 元件(範例)
載入中<Skeleton />, <Spin />, <Loading />
錯誤提示<Alert type="error" />, <ErrorBoundary />
查無資料<Empty description="查無商品" />
資料正常呈現你自己設計的資料列表、卡片、表格等

範例整合:

function ProductList() {
  const { data, loading, error } = useQuery(GET_PRODUCTS);

  if (loading) return <Skeleton active />;
  if (error) return <Alert message={`載入失敗:${error.message}`} type="error" />;
  if (!data?.products?.length) return <Empty description="查無商品資料" />;

  return (
    <ul>
      {data.products.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

結語:先會 useQuery,才能深入 Apollo 世界

useQuery 是進入 Apollo Client 的第一步。

當你學會了如何從前端發送查詢、取得資料並處理狀態,其他功能(如快取、變更、訂閱)也會變得更好掌握。

下一步,我們將進入 Apollo 的寫入工具 —— useMutation,來處理新增、更新、刪除等互動式功能!

Similar Posts