# 建立銀行應用程式第三部分:資料的獲取與使用方法 想像《星際迷航》中企業號的電腦——當皮卡德艦長詢問船艦狀態時,資訊會立即顯示,而不需要整個介面關閉並重新建構。這種流暢的資訊流正是我們透過動態資料獲取所要建立的。 目前你的銀行應用程式就像一份印刷的報紙——雖然資訊豐富,但卻是靜態的。我們將把它轉變成類似於NASA的任務控制中心,資料能夠持續流動並即時更新,而不會中斷使用者的工作流程。 你將學習如何以非同步方式與伺服器溝通,處理不同時間到達的資料,並將原始資訊轉化為對使用者有意義的內容。這就是展示版軟體與生產就緒軟體之間的差異。 ## 課前測驗 [課前測驗](https://ff-quizzes.netlify.app/web/quiz/45) ### 先決條件 在深入資料獲取之前,請確保你已準備好以下元件: - **前一課程**:完成[登入與註冊表單](../2-forms/README.md)——我們將在此基礎上進行構建 - **本地伺服器**:安裝 [Node.js](https://nodejs.org) 並[運行伺服器 API](../api/README.md)以提供帳戶資料 - **API 連接**:使用以下指令測試你的伺服器連接: ```bash curl http://localhost:5000/api # Expected response: "Bank API v1.0.0" ``` 此快速測試可確保所有元件正常通信: - 驗證你的系統上 Node.js 是否運行正常 - 確認你的 API 伺服器是否活躍並回應 - 驗證你的應用程式是否能夠連接伺服器(就像任務開始前檢查無線電聯絡) --- ## 理解現代網頁應用程式中的資料獲取 過去二十年來,網頁應用程式處理資料的方式發生了巨大變化。理解這種演變將幫助你了解為什麼像 AJAX 和 Fetch API 這樣的現代技術如此強大,以及它們為什麼成為網頁開發者的必備工具。 讓我們來探索傳統網站的運作方式與我們今天構建的動態響應式應用程式之間的差異。 ### 傳統多頁應用程式 (MPA) 在網頁的早期,每次點擊就像在老式電視上換頻道——螢幕會變空白,然後慢慢調到新內容。這就是早期網頁應用程式的現實,每次互動都意味著完全重新建構整個頁面。 ```mermaid sequenceDiagram participant User participant Browser participant Server User->>Browser: Clicks link or submits form Browser->>Server: Requests new HTML page Note over Browser: Page goes blank Server->>Browser: Returns complete HTML page Browser->>User: Displays new page (flash/reload) ```  **為什麼這種方式感覺很笨重:** - 每次點擊都需要完全重新建構整個頁面 - 使用者的思路會被那些煩人的頁面閃爍中斷 - 你的網路連接需要重複下載相同的頁眉和頁腳 - 應用程式感覺更像是在翻閱檔案櫃,而不是使用軟體 ### 現代單頁應用程式 (SPA) AJAX(非同步 JavaScript 和 XML)完全改變了這種模式。就像國際太空站的模組化設計,宇航員可以更換單個元件而不需要重建整個結構,AJAX 允許我們更新網頁的特定部分,而不需要重新載入整個頁面。儘管名稱中提到 XML,但我們今天主要使用 JSON,但核心原則仍然是:僅更新需要更改的部分。 ```mermaid sequenceDiagram participant User participant Browser participant JavaScript participant Server User->>Browser: Interacts with page Browser->>JavaScript: Triggers event handler JavaScript->>Server: Fetches only needed data Server->>JavaScript: Returns JSON data JavaScript->>Browser: Updates specific page elements Browser->>User: Shows updated content (no reload) ```  **為什麼 SPA 感覺更好:** - 只有實際更改的部分會被更新(聰明吧?) - 不再有令人不安的中斷——使用者可以保持專注 - 更少的資料通過網路傳輸,載入速度更快 - 一切感覺都很流暢和響應迅速,就像手機上的應用程式一樣 ### 現代 Fetch API 的演進 現代瀏覽器提供了 [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API),取代了舊的 [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)。就像操作電報與使用電子郵件的區別,Fetch API 使用 promises 來實現更簡潔的非同步程式碼,並自然地處理 JSON。 | 功能 | XMLHttpRequest | Fetch API | |------|----------------|-----------| | **語法** | 複雜的基於回調 | 簡潔的基於 promise | | **JSON 處理** | 需要手動解析 | 內建 `.json()` 方法 | | **錯誤處理** | 錯誤資訊有限 | 提供全面的錯誤細節 | | **現代支援** | 與舊版相容 | 支援 ES6+ 的 promise 和 async/await | > 💡 **瀏覽器相容性**:好消息——Fetch API 在所有現代瀏覽器中都能正常運作!如果你對特定版本感到好奇,[caniuse.com](https://caniuse.com/fetch) 提供完整的相容性資訊。 > **重點:** - 在 Chrome、Firefox、Safari 和 Edge 中表現良好(基本上涵蓋了所有使用者) - 只有 Internet Explorer 需要額外的支援(老實說,是時候放棄 IE 了) - 為我們稍後使用的優雅 async/await 模式做好了完美準備 ### 實現使用者登入與資料獲取 現在讓我們實現登入系統,將你的銀行應用程式從靜態顯示轉變為功能性應用程式。就像安全的軍事設施使用的身份驗證協議一樣,我們將驗證使用者憑證,然後提供訪問其特定資料的權限。 我們將逐步構建,從基本身份驗證開始,然後添加資料獲取功能。 #### 步驟 1:建立登入功能基礎 打開你的 `app.js` 文件,新增一個 `login` 函數。這將處理使用者的身份驗證過程: ```javascript async function login() { const loginForm = document.getElementById('loginForm'); const user = loginForm.user.value; } ``` **讓我們來分解一下:** - 那個 `async` 關鍵字?它告訴 JavaScript「嘿,這個函數可能需要等待一些事情」 - 我們從頁面中抓取表單(沒什麼特別的,只是通過 ID 找到它) - 然後我們提取使用者輸入的使用者名稱 - 這裡有個小技巧:你可以通過表單輸入的 `name` 屬性來訪問任何表單輸入——不需要額外的 getElementById 調用! > 💡 **表單訪問模式**:每個表單控件都可以通過其名稱(在 HTML 中使用 `name` 屬性設置)作為表單元素的屬性來訪問。這提供了一種清晰、易讀的方式來獲取表單資料。 #### 步驟 2:建立帳戶資料獲取函數 接下來,我們將建立一個專用函數,從伺服器檢索帳戶資料。這遵循與你的註冊函數相同的模式,但重點是資料獲取: ```javascript 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 非同步請求資料 - **構建**帶有使用者名稱參數的 GET 請求 URL - **應用** `encodeURIComponent()` 安全處理 URL 中的特殊字元 - **轉換**回應為 JSON 格式,便於資料操作 - **優雅地處理**錯誤,返回錯誤物件而不是崩潰 > ⚠️ **安全提示**:`encodeURIComponent()` 函數處理 URL 中的特殊字元。就像海軍通信中使用的編碼系統一樣,它確保你的訊息準確無誤地到達,防止像 "#" 或 "&" 這樣的字元被誤解。 > **為什麼這很重要:** - 防止特殊字元破壞 URL - 防止 URL 操作攻擊 - 確保伺服器接收到預期的資料 - 遵循安全編碼實踐 #### 理解 HTTP GET 請求 以下是可能會讓你驚訝的事情:當你使用 `fetch` 而沒有任何額外選項時,它會自動建立 [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) 請求。這對我們正在做的事情非常完美——向伺服器請求「嘿,我可以查看這個使用者的帳戶資料嗎?」 將 GET 請求想像成禮貌地向圖書館借書——你是在請求查看已存在的資料。而 POST 請求(我們在註冊時使用)更像是提交一本新書以加入收藏。 | GET 請求 | POST 請求 | |----------|-----------| | **目的** | 檢索現有資料 | 向伺服器發送新資料 | | **參數** | 在 URL 路徑/查詢字串中 | 在請求正文中 | | **快取** | 瀏覽器可以快取 | 通常不快取 | | **安全性** | 在 URL/日誌中可見 | 隱藏在請求正文中 | #### 步驟 3:整合所有功能 現在到了令人滿意的部分——讓我們將你的帳戶獲取函數與登入過程連接起來。這是所有事情融會貫通的地方: ```javascript 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'); } ``` 此函數遵循清晰的順序: - 從表單輸入中提取使用者名稱 - 向伺服器請求使用者的帳戶資料 - 處理過程中發生的任何錯誤 - 儲存帳戶資料並在成功後導航到儀表板 > 🎯 **Async/Await 模式**:由於 `getAccount` 是一個非同步函數,我們使用 `await` 關鍵字來暫停執行,直到伺服器回應。這防止程式碼在資料未定義的情況下繼續執行。 #### 步驟 4:為你的資料創建存放地 你的應用程式需要一個地方來記住載入的帳戶資訊。想像這就像你的應用程式的短期記憶——一個可以隨時存放當前使用者資料的地方。在你的 `app.js` 文件頂部新增以下行: ```javascript // This holds the current user's account data let account = null; ``` **為什麼我們需要這個:** - 使帳戶資料在應用程式的任何地方都可以訪問 - 從 `null` 開始表示「目前還沒有人登入」 - 在有人成功登入或註冊時更新 - 作為單一的真實來源——不會混淆誰已登入 #### 步驟 5:連接你的表單 現在讓我們將你的全新登入功能連接到 HTML 表單。像這樣更新你的表單標籤: ```html
``` **這個小改動的作用:** - 阻止表單執行其預設的「重新載入整個頁面」行為 - 改為調用你的自定義 JavaScript 函數 - 保持一切流暢且符合單頁應用程式的風格 - 讓你完全掌控使用者點擊「登入」時的行為 #### 步驟 6:增強你的註冊功能 為了保持一致性,更新你的 `register` 函數,使其也能儲存帳戶資料並導航到儀表板: ```javascript // Add these lines at the end of your register function account = result; navigate('/dashboard'); ``` **此增強提供:** - **流暢**地從註冊過渡到儀表板 - **一致**的使用者體驗,適用於登入和註冊流程 - **即時**訪問成功註冊後的帳戶資料 #### 測試你的實現 ```mermaid flowchart TD A[User enters credentials] --> B[Login function called] B --> C[Fetch account data from server] C --> D{Data received successfully?} D -->|Yes| E[Store account data globally] D -->|No| F[Display error message] E --> G[Navigate to dashboard] F --> H[User stays on login page] ``` **是時候試試看了:** 1. 創建一個新帳戶以確保一切正常運作 2. 嘗試使用相同的憑證登入 3. 如果有任何問題,查看瀏覽器的控制台(按 F12) 4. 確保成功登入後進入儀表板 如果有任何問題,不要驚慌!大多數問題都是簡單的修正,例如拼寫錯誤或忘記啟動 API 伺服器。 #### 關於跨來源通信的簡短說明 你可能會想:「我的網頁應用程式如何與這個 API 伺服器通信,當它們運行在不同的端口上?」好問題!這涉及到每個網頁開發者最終都會遇到的問題。 > 🔒 **跨來源安全性**:瀏覽器強制執行「同源政策」,以防止不同域之間未經授權的通信。就像五角大樓的檢查系統一樣,它們在允許資料傳輸之前驗證通信是否獲得授權。 > **在我們的設置中:** - 你的網頁應用程式運行在 `localhost:3000`(開發伺服器) - 你的 API 伺服器運行在 `localhost:5000`(後端伺服器) - API 伺服器包含 [CORS 標頭](https://developer.mozilla.org/docs/Web/HTTP/CORS),明確授權來自你的網頁應用程式的通信 此配置模仿了前端和後端應用程式通常運行在不同伺服器上的真實世界開發。 > 📚 **深入了解**:通過這個全面的[Microsoft Learn API 模組](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon)深入了解 API 和資料獲取。 ## 在 HTML 中讓你的資料活起來 現在我們將通過 DOM 操作使獲取的資料對使用者可見。就像在暗房中沖洗照片的過程一樣,我們將不可見的資料轉化為使用者可以看到和互動的內容。 DOM 操作是一種技術,可以將靜態網頁轉化為基於使用者互動和伺服器回應而更新內容的動態應用程式。 ### 選擇合適的工具 當涉及到使用 JavaScript 更新你的 HTML 時,你有幾個選擇。可以將它們想像成工具箱中的不同工具——每個工具都適合特定的工作: | 方法 | 適合的用途 | 使用時機 | 安全等級 | |------|------------|----------|----------| | `textContent` | 安全顯示使用者資料 | 任何顯示文字的時候 | ✅ 非常安全 | | `createElement()` + `append()` | 建立複雜佈局 | 創建新區塊/列表 | ✅ 非常安全 | | `innerHTML` | 設置 HTML 內容 | ⚠️ 儘量避免使用 | ❌ 風險較高 | #### 安全顯示文字的方法:textContent [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) 屬性是你在顯示使用者資料時的最佳夥伴。它就像你的網頁的保鏢——任何有害的內容都無法通過: ```javascript // The safe, reliable way to update text const balanceElement = document.getElementById('balance'); balanceElement.textContent = account.balance; ``` **textContent 的好處:** - 將所有內容視為純文字(防止腳本執行) - 自動清除現有內容 - 對於簡單的文字更新非常高效 - 提供內建的安全性,防止惡意內容 #### 創建動態 HTML 元素 對於更複雜的內容,可以將[`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement)與[`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append)方法結合使用: ```javascript // Safe way to create new elements const transactionItem = document.createElement('div'); transactionItem.className = 'transaction-item'; transactionItem.textContent = `${transaction.date}: ${transaction.description}`; container.append(transactionItem); ``` **理解這種方法:** - **創建**新的 DOM 元素,並以程式化方式操作 - **保持**對元素屬性和內容的完全控制 - **允許**構建複雜的嵌套元素結構 - **保護**安全性,將結構與內容分離 > ⚠️ **安全考量**:雖然[`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML)在許多教程中出現,但它可能執行嵌入的腳本。就像歐洲核子研究中心(CERN)的安全協議防止未授權的代碼執行一樣,使用`textContent`和`createElement`提供了更安全的替代方案。 > **使用 innerHTML 的風險:** - 會執行用戶數據中的`