# 建立銀行應用程式第三部分:資料的獲取與使用方法 ## 課前測驗 [課前測驗](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(非同步 JavaScript 和 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