# 建立銀行應用程式第 3 部分:獲取和使用資料的方法 ## 課前測驗 [課前測驗](https://ff-quizzes.netlify.app/web/quiz/45) ### 簡介 在每個網頁應用程式的核心都有一個關鍵元素:*資料*。資料可以有多種形式,但其主要目的是向使用者顯示資訊。隨著網頁應用程式變得越來越互動且複雜,使用者如何存取和互動資訊已成為網頁開發中的關鍵部分。 在本課中,我們將學習如何從伺服器非同步地獲取資料,並使用這些資料在不重新載入 HTML 的情況下在網頁上顯示資訊。 ### 先決條件 在學習本課之前,您需要完成網頁應用程式的 [登入和註冊表單](../2-forms/README.md) 部分。此外,您需要安裝 [Node.js](https://nodejs.org) 並在本地執行 [伺服器 API](../api/README.md),以便獲取帳戶資料。 您可以在終端中執行以下命令來測試伺服器是否正常運行: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## AJAX 和資料獲取 傳統的網站在使用者點擊連結或提交表單時,會透過重新載入整個 HTML 頁面來更新顯示的內容。每次需要載入新資料時,網頁伺服器都會返回一個全新的 HTML 頁面,這需要瀏覽器處理,並中斷當前的使用者操作,限制了重新載入期間的互動性。這種工作流程也被稱為 *多頁應用程式*(Multi-Page Application,MPA)。  隨著網頁應用程式變得更加複雜和互動性更強,一種名為 [AJAX(Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) 的新技術應運而生。這種技術允許網頁應用程式使用 JavaScript 非同步地向伺服器發送和檢索資料,而無需重新載入 HTML 頁面,從而實現更快的更新和更流暢的使用者互動。當從伺服器接收到新資料時,可以使用 [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) API 透過 JavaScript 更新當前的 HTML 頁面。隨著時間的推移,這種方法演變成現在所謂的 [*單頁應用程式*(Single-Page Application,SPA)](https://en.wikipedia.org/wiki/Single-page_application)。  在 AJAX 剛推出時,唯一可用的非同步獲取資料的 API 是 [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)。但現代瀏覽器現在也實現了更方便且功能更強大的 [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API),它使用 Promise 並更適合處理 JSON 資料。 > 雖然所有現代瀏覽器都支援 `Fetch API`,但如果您希望您的網頁應用程式能在舊版或過時的瀏覽器上運行,最好先檢查 [caniuse.com 上的相容性表](https://caniuse.com/fetch)。 ### 任務 在[上一課](../2-forms/README.md)中,我們實現了註冊表單來建立帳戶。現在,我們將新增程式碼來使用現有帳戶登入並獲取其資料。打開 `app.js` 檔案,新增一個新的 `login` 函數: ```js async function login() { const loginForm = document.getElementById('loginForm') const user = loginForm.user.value; } ``` 我們首先使用 `getElementById()` 獲取表單元素,然後透過 `loginForm.user.value` 獲取輸入框中的使用者名稱。每個表單控制項都可以透過其名稱(在 HTML 中使用 `name` 屬性設定)作為表單的屬性來存取。 類似於我們為註冊所做的操作,我們將建立另一個函數來執行伺服器請求,但這次是用於檢索帳戶資料: ```js async function getAccount(user) { try { const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user)); return await response.json(); } catch (error) { return { error: error.message || 'Unknown error' }; } } ``` 我們使用 `fetch` API 從伺服器非同步地請求資料,但這次除了 URL 外不需要任何額外的參數,因為我們只是查詢資料。預設情況下,`fetch` 會建立一個 [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) HTTP 請求,這正是我們需要的。 ✅ `encodeURIComponent()` 是一個用於對 URL 中的特殊字元進行編碼的函數。如果我們不調用此函數而直接在 URL 中使用 `user` 值,可能會出現什麼問題? 現在我們來更新 `login` 函數以使用 `getAccount`: ```js async function login() { const loginForm = document.getElementById('loginForm') const user = loginForm.user.value; const data = await getAccount(user); if (data.error) { return console.log('loginError', data.error); } account = data; navigate('/dashboard'); } ``` 首先,由於 `getAccount` 是一個非同步函數,我們需要使用 `await` 關鍵字來等待伺服器的結果。與任何伺服器請求一樣,我們還需要處理錯誤情況。目前,我們只新增一條日誌訊息來顯示錯誤,稍後再進一步處理。 接著,我們需要將資料儲存到某個地方,以便稍後用於顯示儀表板資訊。由於 `account` 變數尚不存在,我們將在檔案的頂部建立一個全域變數: ```js let account = null; ``` 在使用者資料儲存到變數後,我們可以使用我們已有的 `navigate()` 函數從 *登入* 頁面導航到 *儀表板*。 最後,我們需要在提交登入表單時調用 `login` 函數,通過修改 HTML: ```html