# 构建银行应用程序第三部分:数据获取与使用方法 ## 课前测验 [课前测验](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