# 建立銀行應用程式 第3部分:擷取及使用資料的方法 想像《星艦迷航記》裡的星際企業號電腦——當皮卡爾艦長查詢船隻狀態時,資訊即時顯示,整個介面不會停頓或重新載入。我們要打造的,正是這種動態資料擷取的無縫資訊流。 目前你的銀行應用程式就像一份印刷報紙——資訊豐富但靜態。我們將其轉變成類似 NASA 任務控制中心,資料持續流動並即時更新,且不會打斷使用者的工作流程。 你將學習如何非同步與伺服器溝通,處理在不同時間到達的資料,並將原始資訊轉換為對用戶有意義的內容。這正是從示範軟體進入生產級軟體的關鍵差異。 ## ⚡ 下一五分鐘你能做到什麼 **忙碌開發者的快速啟動路徑** ```mermaid flowchart LR A[⚡ 5 分鐘] --> B[設置 API 伺服器] B --> C[用 curl 測試獲取] C --> D[建立登入功能] D --> E[查看實際資料] ``` - **第1-2分鐘**:啟動你的 API 伺服器(`cd api && npm start`)並測試連線 - **第3分鐘**:使用 fetch 建立基本的 `getAccount()` 函數 - **第4分鐘**:使用 `action="javascript:login()"` 連接登入表單 - **第5分鐘**:測試登入,觀察帳戶資料在控制台顯示 **快速測試指令**: ```bash # 驗證 API 是否正在運行 curl http://localhost:5000/api # 測試帳戶資料擷取 curl http://localhost:5000/api/accounts/test ``` **重要原因**:五分鐘內你會見識到非同步資料擷取的魔力,這是現代所有網頁應用程式的核心。它是讓應用程式感覺迅速且生動的基石。 ## 🗺️ 你的資料驅動網頁應用程式學習之旅 ```mermaid journey title 從靜態頁面到動態應用程式 section 理解演變過程 傳統頁面重新載入: 3: You 發現 AJAX/SPA 好處: 5: You 精通 Fetch API 模式: 7: You section 建立認證系統 建立登入功能: 4: You 處理非同步操作: 6: You 管理使用者會話: 8: You section 動態用戶介面更新 學習 DOM 操作: 5: You 建立交易顯示: 7: You 製作回應式儀表板: 9: You section 專業模式 基於模板的渲染: 6: You 錯誤處理策略: 7: You 效能優化: 8: You ``` **旅程終點**:課程結束後,你將理解現代網頁應用如何動態擷取、處理並顯示資料,打造專業級軟體所期望的順暢用戶體驗。 ## 課前測驗 [課前測驗](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 # 預期回應: "Bank API v1.0.0" ``` 這個快速測試能確保各元件正常通訊: - 確認 Node.js 在系統中正確運作 - 確認 API 伺服器活躍並有回應 - 驗證你的應用程式能連線到伺服器(就像任務前確保無線電聯繫) ## 🧠 資料管理生態系統概覽 ```mermaid mindmap root((數據管理)) Authentication Flow Login Process Form Validation Credential Verification Session Management User State Global Account Object Navigation Guards Error Handling API Communication Fetch Patterns GET Requests POST Requests Error Responses Data Formats JSON Processing URL Encoding Response Parsing Dynamic UI Updates DOM Manipulation Safe Text Updates Element Creation Template Cloning User Experience Real-time Updates Error Messages Loading States Security Considerations XSS Prevention textContent Usage Input Sanitization Safe HTML Creation CORS Handling Cross-Origin Requests Header Configuration Development Setup ``` **核心原則**:現代網頁應用是資料協調系統——它協調使用者介面、伺服器 API 與瀏覽器安全模型,創造順暢且回應迅速的體驗。 --- ## 了解現代網頁應用的資料擷取 網頁應用處理資料的方式在過去二十年大幅演進。了解這個演變有助於你體會為什麼 AJAX 與 Fetch API 如此強大,並成為網頁開發必備工具。 讓我們來探索傳統網站與現代動態、反應式應用的不同。 ### 傳統多頁面應用 (Multi-Page Applications, MPA) 早期網頁時代,每次點擊就像換電視台頻道——畫面會變空白,然後慢慢載入新內容。這是早期網頁應用的現實:每次操作都要重建整個頁面。 ```mermaid sequenceDiagram participant User participant Browser participant Server User->>Browser: 點擊連結或提交表單 Browser->>Server: 請求新的 HTML 頁面 Note over Browser: 頁面變空白 Server->>Browser: 返回完整的 HTML 頁面 Browser->>User: 顯示新頁面(閃爍/重載) ```  **這方法令人覺得笨拙的原因:** - 每次點擊都重建整個頁面 - 使用者在操作過程中會被刺眼的頁面閃爍打斷 - 網路頻寬無謂地重複下載相同標頭與頁腳 - 應用程式感覺像是在翻閱資料櫃,而非使用軟體 ### 現代單頁應用 (Single-Page Applications, SPA) AJAX(非同步 JavaScript 與 XML)完全改變了這個模式。就像國際太空站的模組化設計,太空人可以更換單一組件而不用重建整個結構,AJAX 允許我們只更新頁面特定部分,而不重新載入全部。雖然名字包含 XML,但現在我們多用 JSON,核心概念是不變的:只更新需要更改的部分。 ```mermaid sequenceDiagram participant User participant Browser participant JavaScript participant Server User->>Browser: 與頁面互動 Browser->>JavaScript: 觸發事件處理程序 JavaScript->>Server: 只獲取所需數據 Server->>JavaScript: 返回 JSON 數據 JavaScript->>Browser: 更新指定的頁面元素 Browser->>User: 顯示更新內容(無需重新載入) ```  **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+ promises 與 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` 關鍵字表示這函式可能需要等待非同步操作結束 - 我們從頁面抓取登入表單(簡單,直接用 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()` 確保網址安全處理特殊字元 - **轉換** 回應為 JSON 格式方便操作 - **妥善** 處理錯誤,回傳錯誤物件而非崩潰 > ⚠️ **安全提示**:`encodeURIComponent()` 函數會處理網址中特殊字元。像海軍通訊的編碼系統,確保訊息準確無誤地傳達,避免 "#" 或 "&" 等字元被誤解。 > **重要性:** - 避免特殊字元破壞 URL 格式 - 防止網址遭竄改攻擊 - 確保伺服器接收正確資料 - 遵循安全編碼最佳實踐 #### 了解 HTTP GET 請求 這裡有個可能令你驚訝的事:當你使用 `fetch` 而沒有傳入特別設定,預設會是 [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) 請求。非常適合我們的需求——向伺服器請求看某使用者帳戶資料。 想像 GET 請求像是禮貌地向圖書館借書——請求檢視已存在資料。而 POST 請求(註冊時用的)則像送新書加入館藏。 | GET 請求 | POST 請求 | |-------------|-------------| | **目的** | 取得現有資料 | 傳送新資料到伺服器 | | **參數** | 在網址路徑/查詢字串 | 在請求主體中 | | **快取** | 可被瀏覽器快取 | 通常不快取 | | **安全性** | 顯示於網址/紀錄中 | 隱藏在請求主體 | ```mermaid sequenceDiagram participant B as 瀏覽器 participant S as 伺服器 Note over B,S: GET 請求(資料檢索) B->>S: GET /api/accounts/test S-->>B: 200 成功 + 帳戶資料 Note over B,S: POST 請求(資料提交) B->>S: POST /api/accounts + 新帳戶資料 S-->>B: 201 已建立 + 確認訊息 Note over B,S: 錯誤處理 B->>S: GET /api/accounts/nonexistent S-->>B: 404 未找到 + 錯誤訊息 ``` #### 步驟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 // 儲存當前用戶的帳戶資料 let account = null; ``` **為什麼需要它:** - 讓整個應用程式能隨時存取帳戶資料 - 初始設為 `null` 表示「尚未有人登入」 - 當有人成功登入或註冊時更新其內容 - 作為唯一資料來源,避免登入狀態混淆 #### 步驟5:連接表單與功能 接著將你的登入新功能連結到 HTML 表單,修改表單標籤如下: ```html
``` **這個小改動帶來的效果:** - 阻止表單預設的「整頁重新載入」動作 - 改由自訂的 JavaScript 函數處理 - 保持單頁應用的平滑體驗 - 完全控制使用者按下「登入」的行為 #### 步驟6:增強註冊功能 為保持一致性,更新你的 `register` 函式,也讓它存取帳戶資料並跳轉到儀表板: ```javascript // 將這些行添加到您的註冊功能的末尾 account = result; navigate('/dashboard'); ``` **這項優化帶來:** - 註冊完成無縫轉到儀表板 - 登入與註冊流程體驗一致 - 成功註冊後立即取得帳戶資料 #### 測試你的實作 ```mermaid flowchart TD A[用戶輸入憑證] --> B[呼叫登入功能] B --> C[從伺服器取得帳戶資料] C --> D{資料是否成功接收?} D -->|是| E[全域儲存帳戶資料] D -->|否| F[顯示錯誤訊息] E --> G[導向儀表板] F --> H[用戶停留在登入頁面] ``` **現在試運行:** 1. 建立新帳戶,確認流程正常 2. 使用同樣的憑證嘗試登入 3. 如有異常,打開瀏覽器控制台(F12)檢查 4. 確認成功登入後進入儀表板頁面 遇到問題別慌,多數情況下是簡單錯誤或忘了啟動 API 伺服器。 #### 跨來源通訊的小秘密 你可能會想:「我的網頁應用跟 API 伺服器分別跑在不同埠口,怎能互通?」好問題!這是所有網頁開發者都會遇到的話題。 > 🔒 **跨來源安全政策**:瀏覽器強制執行「同源政策」以防止未授權跨域通訊,就像五角大廈的檢查站,核對通訊是否授權後才允許資料傳送。 > **本示範設定:** - 你的網頁應用運行於 `localhost:3000`(開發伺服器) - API 伺服器運行於 `localhost:5000`(後端伺服器) - API 伺服器包含 [CORS 標頭](https://developer.mozilla.org/docs/Web/HTTP/CORS) 明確授權來自網頁應用的通訊 這種架構與實務開發情境相符,前後端通常分別部署於不同伺服器。 > 📚 **擴充學習**:深入瞭解 API 與資料擷取,請參考 Microsoft Learn 的完整 [API 課程](https://docs.microsoft.com/learn/modules/use-apis-discover-museum-art/?WT.mc_id=academic-77807-sagibbon)。 ## 將資料動態呈現在 HTML 中 現在我們透過 DOM 操控將擷取的資料呈現給使用者。就像洗相片暗房的過程,我們將看不見的資料轉成使用者可見且可互動的內容。 DOM 操作是將靜態網頁轉換成動態應用程式的技術,能根據用戶互動和伺服器回應更新內容。 ### 選擇合適的工具完成工作 在使用 JavaScript 更新 HTML 時,你有幾個選擇。把它們想成工具箱中的不同工具—每一個都適合特定工作: | 方法 | 適合用途 | 何時使用 | 安全程度 | |--------|---------------------|----------------|--------------| | `textContent` | 安全顯示用戶資料 | 任何顯示文字時 | ✅ 非常穩固 | | `createElement()` + `append()` | 建立複雜排版 | 新增區域/清單時 | ✅ 可靠無虞 | | `innerHTML` | 設定 HTML 內容 | ⚠️ 建議盡量避免 | ❌ 有風險 | #### 安全顯示文字:textContent [`textContent`](https://developer.mozilla.org/docs/Web/API/Node/textContent) 屬性是顯示用戶資料的好幫手。就像網頁的保鏢—避免任何有害的內容通過: ```javascript // 安全、可靠的文本更新方式 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 // 創建新元素的安全方法 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 的風險:** - 會執行用戶資料中的 `