用 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
}
}
這段查詢代表:「請幫我拿到 products
的 id
、name
和 price
欄位。」
query
是 GraphQL 的操作類型(除了query
還有mutation
和subscription
)GetProducts
是這次查詢的名稱(可以自己取,主要用來辨識這筆請求){ products { ... } }
是你實際想要的資料結構
簡單來說,GraphQL 查詢的結構很像你在說:「我要 products
,裡面請幫我拿 id
、name
和 price
。」
✅ 小提醒:
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(像是 useState
或 useEffect
),而是 Apollo Client 為了讓你在 React 中更方便操作 GraphQL 查詢,所特別設計的函式。
你可以這樣理解:
✅
useQuery
= Apollo Client 提供的「查資料專用 Hook」
只要你已經安裝並設定好 Apollo Client,就可以在任何 React 函式元件中,直接使用 useQuery
來發送 GraphQL 查詢,而不需要自己去寫 fetch 或 useEffect。
你不需要再額外寫 fetch
或 useEffect
,它已經跟 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 就會:
- 自動發送這筆 GraphQL 查詢
- 回傳三種狀態讓你使用:
loading
:資料請求中error
:請求失敗data
:請求成功,並回傳查詢結果
- 將查詢結果快取下來(下一次可能不會重複打 API)
- 當資料變動或 refetch 時,自動更新畫面
每個回傳值的實際用途解析
回傳值 | 資料型別 | 用途說明 |
---|---|---|
loading | boolean | 如果正在請求中,這個值為 true,可用來顯示「資料載入中」畫面或動畫 |
error | ApolloError | 如果請求發生錯誤,這個欄位會包含錯誤訊息與細節,可用來顯示錯誤提示或寫入錯誤 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 根據查詢內容決定是否要從快取取資料、還是要呼叫後端。
- 此時
loading
為true
,畫面會顯示「載入中」。
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
,來處理新增、更新、刪除等互動式功能!