# 建立銀行應用程式第三部分:獲取和使用數據的方法 想像《星際迷航》中企業號的電腦——當皮卡德艦長詢問船隻狀態時,信息會立即顯示出來,而不需要整個界面關閉並重新構建。這種無縫的信息流正是我們在這裡通過動態數據獲取所要構建的。 目前,你的銀行應用程式就像一份印刷的報紙——信息豐富但靜態。我們將把它轉變成更像 NASA 的任務控制中心,數據能夠持續流動並實時更新,而不會中斷用戶的工作流程。 你將學習如何與伺服器進行異步通信,處理不同時間到達的數據,並將原始信息轉化為對用戶有意義的內容。這就是演示軟體和生產就緒軟體之間的差別。 ## ⚡ 接下來的 5 分鐘你可以做什麼 **忙碌開發者的快速入門路徑** ```mermaid flowchart LR A[⚡ 5 minutes] --> B[Set up API server] B --> C[Test fetch with curl] C --> D[Create login function] D --> E[See data in action] ``` - **第 1-2 分鐘**:啟動你的 API 伺服器(`cd api && npm start`)並測試連接 - **第 3 分鐘**:使用 fetch 創建一個基本的 `getAccount()` 函數 - **第 4 分鐘**:將登錄表單連接到 `action="javascript:login()"` - **第 5 分鐘**:測試登錄並觀察帳戶數據出現在控制台中 **快速測試命令**: ```bash # Verify API is running curl http://localhost:5000/api # Test account data fetch curl http://localhost:5000/api/accounts/test ``` **為什麼這很重要**:在 5 分鐘內,你將看到異步數據獲取的魔力,這是每個現代網頁應用程式的動力基礎。這是讓應用程式感覺響應迅速且充滿活力的基石。 ## 🗺️ 通過數據驅動的網頁應用程式的學習旅程 ```mermaid journey title From Static Pages to Dynamic Applications section Understanding the Evolution Traditional page reloads: 3: You Discover AJAX/SPA benefits: 5: You Master Fetch API patterns: 7: You section Building Authentication Create login functions: 4: You Handle async operations: 6: You Manage user sessions: 8: You section Dynamic UI Updates Learn DOM manipulation: 5: You Build transaction displays: 7: You Create responsive dashboards: 9: You section Professional Patterns Template-based rendering: 6: You Error handling strategies: 7: You Performance optimization: 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 # Expected response: "Bank API v1.0.0" ``` 此快速測試可確保所有組件正常通信: - 驗證 Node.js 在你的系統上正確運行 - 確認你的 API 伺服器處於活動狀態並響應 - 驗證你的應用程式能夠連接到伺服器(就像在任務開始前檢查無線電聯絡) ## 🧠 數據管理生態系統概述 ```mermaid mindmap root((Data Management)) 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 這樣的現代技術如此強大,以及它們為什麼成為網頁開發者的必備工具。 讓我們來探索傳統網站的工作方式與我們今天構建的動態響應式應用程式的區別。 ### 傳統多頁應用程式 (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+ 的 promises 和 async/await | > 💡 **瀏覽器兼容性**:好消息——Fetch API 在所有現代瀏覽器中都能正常工作!如果你對特定版本感到好奇,[caniuse.com](https://caniuse.com/fetch) 提供了完整的兼容性信息。 > **總結:** - 在 Chrome、Firefox、Safari 和 Edge 中表現良好(基本上涵蓋了所有用戶使用的瀏覽器) - 只有 Internet Explorer 需要額外的幫助(坦白說,是時候放棄 IE 了) - 為我們稍後使用的優雅的 async/await 模式做好了完美準備 ### 實現用戶登錄和數據檢索 現在,讓我們實現登錄系統,將你的銀行應用程式從靜態顯示轉變為功能性應用程式。就像安全軍事設施中使用的身份驗證協議一樣,我們將驗證用戶憑據,然後提供訪問其特定數據的權限。 我們將逐步構建,從基本身份驗證開始,然後添加數據獲取功能。 #### 第一步:創建登錄函數基礎 打開你的 `app.js` 文件,添加一個新的 `login` 函數。這將處理用戶身份驗證過程: ```javascript async function login() { const loginForm = document.getElementById('loginForm'); const user = loginForm.user.value; } ``` **讓我們來分解一下:** - 那個 `async` 關鍵字?它告訴 JavaScript「嘿,這個函數可能需要等待一些事情」 - 我們從頁面中抓取表單(沒什麼花哨的,只是通過 ID 找到它) - 然後我們提取用戶輸入的用戶名 - 這裡有個小技巧:你可以通過表單控件的 `name` 屬性訪問任何表單輸入——不需要額外的 getElementById 調用! > 💡 **表單訪問模式**:每個表單控件都可以通過其名稱(在 HTML 中使用 `name` 屬性設置)作為表單元素的屬性進行訪問。這提供了一種清晰、可讀的方式來獲取表單數據。 #### 第二步:創建帳戶數據獲取函數 接下來,我們將創建一個專用函數來從伺服器檢索帳戶數據。這遵循與你的註冊函數相同的模式,但重點是數據檢索: ```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/日誌中可見 | 隱藏在請求正文中 | ```mermaid sequenceDiagram participant B as Browser participant S as Server Note over B,S: GET Request (Data Retrieval) B->>S: GET /api/accounts/test S-->>B: 200 OK + Account Data Note over B,S: POST Request (Data Submission) B->>S: POST /api/accounts + New Account Data S-->>B: 201 Created + Confirmation Note over B,S: Error Handling B->>S: GET /api/accounts/nonexistent S-->>B: 404 Not Found + Error Message ``` #### 第三步:整合所有功能 現在到了令人滿意的部分——讓我們將你的帳戶獲取函數與登錄過程連接起來。這是所有事情都能完美結合的地方: ```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` 關鍵字來暫停執行,直到伺服器響應。這可以防止代碼在數據未定義的情況下繼續執行。 #### 第四步:為你的數據創建一個存放地 你的應用程式需要一個地方來記住加載後的帳戶信息。將這行代碼添加到你的 `app.js` 文件的頂部: ```javascript // This holds the current user's account data let account = null; ``` **為什麼我們需要這個:** - 使帳戶數據可以從應用程式的任何地方訪問 - 使用 `null` 作為初始值表示「目前還沒有人登錄」 - 在有人成功登錄或註冊時更新 - 作為單一的真實來源——不會混淆誰已登錄 #### 第五步:連接你的表單 現在,讓我們將你的全新登錄函數連接到 HTML 表單。像這樣更新你的表單標籤: ```html
``` **這個小改動的作用:** - 阻止表單執行其默認的「重新加載整個頁面」行為 - 改為調用你的自定義 JavaScript 函數 - 保持一切流暢且像單頁應用程式一樣 - 讓你完全控制用戶點擊「登錄」時的行為 #### 第六步:增強你的註冊函數 為了保持一致性,更新你的 `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 模組](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 的風險:** - 會執行使用者資料中的 `