資料獲取
現在您已經建立並填充了資料庫,讓我們來討論可以為應用程式獲取資料的不同方式,並構建儀表板概覽頁面。
選擇資料獲取方式
API 層
API 是應用程式代碼與資料庫之間的中間層。在以下幾種情況下,您可能會使用 API:
- 當您使用提供 API 的第三方服務時。
- 當您從客戶端獲取資料時,您希望有一個在伺服器上運行的 API 層,以避免將資料庫憑證暴露給客戶端。
在 Next.js 中,您可以使用 路由處理器 (Route Handlers) 來建立 API 端點。
資料庫查詢
當您建立一個全端應用程式時,您還需要編寫與資料庫交互的邏輯。對於像 Postgres 這樣的 關聯式資料庫 (relational databases),您可以使用 SQL 或 ORM。
在以下幾種情況下,您需要編寫資料庫查詢:
- 當建立 API 端點時,您需要編寫與資料庫交互的邏輯。
- 如果您使用 React 伺服器元件 (Server Components)(在伺服器上獲取資料),您可以跳過 API 層,直接查詢資料庫,而不會冒著將資料庫憑證暴露給客戶端的風險。
讓我們進一步了解 React 伺服器元件。
使用伺服器元件獲取資料
預設情況下,Next.js 應用程式使用 React 伺服器元件 (Server Components)。使用伺服器元件獲取資料是一種相對較新的方法,使用它們有以下幾個好處:
- 伺服器元件支援 JavaScript Promises,原生提供了處理異步任務(如資料獲取)的解決方案。您可以使用
async/await
語法,而不需要useEffect
、useState
或其他資料獲取函式庫。 - 伺服器元件在伺服器上運行,因此您可以將昂貴的資料獲取和邏輯保留在伺服器上,僅將結果發送給客戶端。
- 由於伺服器元件在伺服器上運行,您可以直接查詢資料庫,而無需額外的 API 層。這節省了編寫和維護額外代碼的時間。
使用 SQL
對於您的儀表板應用程式,您將使用 postgres.js 函式庫和 SQL 來編寫資料庫查詢。我們使用 SQL 有以下幾個原因:
- SQL 是查詢關聯式資料庫的行業標準(例如 ORM 在底層生成 SQL)。
- 對 SQL 有基本了解可以幫助您理解關聯式資料庫的基礎知識,從而將這些知識應用到其他工具中。
- SQL 功能強大,允許您獲取和操作特定資料。
postgres.js
函式庫提供了對 SQL 注入 (SQL injections) 的防護。
如果您以前沒有使用過 SQL,也不用擔心——我們已經為您提供了查詢語句。
轉到 /app/lib/data.ts
。在這裡您會看到我們正在使用 postgres
。sql
函式 允許您查詢資料庫:
您可以在伺服器上的任何地方(例如伺服器元件中)調用 sql
。但為了讓您更輕鬆地導航元件,我們將所有資料查詢保留在 data.ts
文件中,您可以將它們導入到元件中。
注意: 如果您在第 6 章中使用了您自己的資料庫提供商,您需要更新資料庫查詢以適應您的提供商。您可以在
/app/lib/data.ts
中找到這些查詢。
為儀表板概覽頁面獲取資料
現在您了解了獲取資料的不同方式,讓我們為儀表板概覽頁面獲取資料。轉到 /app/dashboard/page.tsx
,粘貼以下代碼,並花些時間探索它:
上面的代碼是故意被注釋掉的。我們現在將開始逐一檢查每個部分。
page
是一個 異步 (async) 伺服器元件。這允許您使用await
來獲取資料。- 還有 3 個接收資料的元件:
<Card>
、<RevenueChart>
和<LatestInvoices>
。它們目前被注釋掉,尚未實現。
為 <RevenueChart/>
獲取資料
要為 <RevenueChart/>
元件獲取資料,請從 data.ts
導入 fetchRevenue
函式並在您的元件中調用它:
接下來,讓我們執行以下操作:
- 取消注釋
<RevenueChart/>
元件。 - 轉到元件文件 (
/app/ui/dashboard/revenue-chart.tsx
) 並取消注釋其中的代碼。 - 檢查
localhost:3000
,您應該會看到一個使用revenue
資料的圖表。

讓我們繼續導入更多資料並將其顯示在儀表板上。
為 <LatestInvoices/>
獲取資料
對於 <LatestInvoices />
元件,我們需要獲取最新的 5 張發票,按日期排序。
您可以使用 JavaScript 獲取所有發票並對其進行排序。由於我們的資料量很小,這不是問題,但隨著應用程式的增長,這可能會顯著增加每次請求傳輸的資料量以及排序所需的 JavaScript 代碼量。
與其在記憶體中對最新發票進行排序,您可以使用 SQL 查詢僅獲取最後 5 張發票。例如,這是您的 data.ts
文件中的 SQL 查詢:
在您的頁面中,導入 fetchLatestInvoices
函式:
然後,取消注釋 <LatestInvoices />
元件。您還需要取消注釋 <LatestInvoices />
元件本身的相關代碼,該元件位於 /app/ui/dashboard/latest-invoices
。
如果您訪問您的 localhost,您應該會看到只有最後 5 張發票從資料庫中返回。希望您開始看到直接查詢資料庫的優勢!

練習:為 <Card>
元件獲取資料
現在輪到您為 <Card>
元件獲取資料了。這些卡片將顯示以下資料:
- 已收取的發票總金額。
- 待處理的發票總金額。
- 發票總數。
- 客戶總數。
再次提醒,您可能會想獲取所有發票和客戶,並使用 JavaScript 來操作資料。例如,您可以使用 Array.length
來獲取發票和客戶的總數:
但使用 SQL,您可以只獲取您需要的資料。這比使用 Array.length
要長一些,但這意味著在請求期間需要傳輸的資料更少。這是 SQL 的替代方案:
您需要導入的函式名為 fetchCardData
。您需要對函式返回的值進行解構。
提示:
- 檢查卡片元件以了解它們需要的資料。
- 檢查
data.ts
文件以了解函式返回的內容。
準備好後,展開下面的切換按鈕查看最終代碼:
太棒了!您現在已經為儀表板概覽頁面獲取了所有資料。您的頁面應該如下所示:

然而...有兩件事您需要注意:
- 資料請求無意中互相阻塞,形成了一個 請求瀑布 (request waterfall)。
- 預設情況下,Next.js 預渲染 (prerenders) 路由以提高性能,這稱為 靜態渲染 (Static Rendering)。因此,如果您的資料發生變化,它不會反映在您的儀表板中。
讓我們在本章中討論第 1 點,然後在下一章中詳細討論第 2 點。
什麼是請求瀑布?
「瀑布」指的是依賴於前一個請求完成的網路請求序列。在資料獲取的情況下,每個請求只有在前一個請求返回資料後才能開始。

例如,我們需要等待 fetchRevenue()
執行完畢後,fetchLatestInvoices()
才能開始運行,依此類推。
這種模式並不一定是壞的。在某些情況下,您可能希望使用瀑布,因為您希望在下一個請求之前滿足某個條件。例如,您可能希望先獲取用戶的 ID 和個人資料信息。一旦您有了 ID,您可能會繼續獲取他們的朋友列表。在這種情況下,每個請求都依賴於前一個請求返回的資料。
然而,這種行為也可能是無意的,並影響性能。
並行資料獲取
避免瀑布的一個常見方法是同時啟動所有資料請求——並行進行。
在 JavaScript 中,您可以使用 Promise.all()
或 Promise.allSettled()
函式同時啟動所有 Promise。例如,在 data.ts
中,我們在 fetchCardData()
函式中使用 Promise.all()
:
通過使用這種模式,您可以:
- 同時啟動所有資料獲取,這比等待每個請求在瀑布中完成要快。
- 使用可以應用於任何函式庫或框架的原生 JavaScript 模式。
然而,僅依賴這種 JavaScript 模式有一個 缺點:如果一個資料請求比其他請求慢,會發生什麼?讓我們在下一章中了解更多。