# 构建银行应用 第3部分:获取和使用数据的方法 想想《星际迷航》里的企业号计算机——当皮卡德舰长询问舰船状态时,信息会瞬间出现,而不会让整个界面关闭并重新构建。那种无缝的信息流正是我们要通过动态数据获取打造的。 目前,你的银行应用就像一份印刷版报纸——信息丰富但静态。我们要把它转变成更像NASA的任务控制中心,数据持续流动并实时更新,且不会打断用户的工作流程。 你将学习如何异步与服务器通信,处理不同时间到达的数据,并将原始信息转化为对用户有意义的内容。这正是演示版本和生产就绪软件之间的区别。 ## ⚡ 接下来 5 分钟你能做些什么 **忙碌开发者快速启动路径** ```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 ``` **为什么这很重要**:5分钟后,你将见识到驱动每个现代网页应用的异步数据获取的魔力。这是让应用响应迅速、生动的基础。 ## 🗺️ 你的数据驱动网页应用学习之旅 ```mermaid journey title 从静态页面到动态应用 section 理解演变过程 传统页面重载: 3: You 发现 AJAX/SPA 的优势: 5: You 掌握 Fetch API 模式: 7: You section 构建身份验证 创建登录功能: 4: You 处理异步操作: 6: You 管理用户会话: 8: You section 动态 UI 更新 学习 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 表单验证 证书验证 会话管理 User State 全局账户对象 导航守卫 错误处理 API Communication Fetch Patterns GET 请求 POST 请求 错误响应 Data Formats JSON 处理 URL 编码 响应解析 Dynamic UI Updates DOM Manipulation 安全文本更新 元素创建 模板克隆 User Experience 实时更新 错误信息 加载状态 Security Considerations XSS Prevention textContent 用法 输入清理 安全 HTML 创建 CORS Handling 跨域请求 头配置 开发设置 ``` **核心原则**:现代网页应用是数据协调系统——它们在用户界面、服务器API和浏览器安全模型之间协作,创造流畅、响应式体验。 --- ## 理解现代网页应用中的数据获取 网页应用处理数据的方式在过去二十年里经历了巨大变化。理解这种演变,帮助你欣赏为何现代技术如 AJAX 和 Fetch API 功能强大,且已成为网页开发者必备工具。 让我们探讨传统网站的工作方式与我们今天构建的动态、响应式应用之间的差异。 ### 传统多页面应用(MPA) 在网络早期,每次点击就像切换老式电视频道——屏幕会变空白,然后慢慢调入新内容。这就是早期网页应用的现实,每次交互都意味着完全重建整页。 ```mermaid sequenceDiagram participant User participant Browser participant Server User->>Browser: 点击链接或提交表单 Browser->>Server: 请求新的HTML页面 Note over Browser: 页面变为空白 Server->>Browser: 返回完整的HTML页面 Browser->>User: 显示新页面(闪烁/重载) ``` ![多页面应用的更新流程](../../../../translated_images/zh-CN/mpa.7f7375a1a2d4aa77.webp) **这种方式为何显得笨重:** - 每次点击都要重新构建整页 - 页面闪烁打断用户思考 - 你的网络反复下载相同的头部和底部资源 - 应用感觉更像操作文件柜而非真正软件 ### 现代单页面应用(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: 显示更新内容(无刷新) ``` ![单页面应用的更新流程](../../../../translated_images/zh-CN/spa.268ec73b41f992c2.webp) **为何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 使用 Promise 编写更清晰的异步代码,并天然支持 JSON。 | 特性 | XMLHttpRequest | Fetch API | |---------|----------------|----------| | **语法** | 复杂基于回调 | 清晰基于Promise | | **JSON处理** | 需要手动解析 | 内置 `.json()` 方法 | | **错误处理** | 错误信息有限 | 详尽的错误信息 | | **现代支持** | 兼容旧版 | 支持ES6+ Promise和 async/await | > 💡 **浏览器兼容性**:好消息——Fetch API 在所有现代浏览器中均可使用!如果想了解具体版本,[caniuse.com](https://caniuse.com/fetch) 上有完整兼容信息。 > **总结:** - Chrome、Firefox、Safari 和 Edge 等主流浏览器支持良好(用户几乎都用这些) - 仅IE需额外适配(说实话,是时候放弃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()` 安全编码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 浏览器 participant S as 服务器 Note over B,S: GET 请求(数据获取) B->>S: GET /api/accounts/test S-->>B: 200 OK + 账户数据 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服务器通信?”这个问题是每个web开发者都会遇到的。 > 🔒 **跨源安全**:浏览器强制执行“同源策略”防止未授权跨域通信。就像五角大楼的检查关卡,验证通信权限后才允许数据传输。 > **在我们的配置中:** - 你的网页应用运行在 `localhost:3000`(开发服务器) - API服务器运行在 `localhost:5000`(后端服务器) - API服务器配备了明确授权网页应用通信的 [CORS 头信息](https://developer.mozilla.org/docs/Web/HTTP/CORS) 这种配置模拟了前后端通常分开运行的实际开发环境。 > 📚 **深入了解**:使用微软学习平台的这门全面课程深化API和数据获取知识 [使用 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 的风险:** - 会执行用户数据中的 `