消费 RESTful Web 服务指南
在 Web 应用开发中,消费 RESTful Web 服务是一项常见且重要的任务。本文将详细介绍如何配置开发环境、理解 RESTful 服务原理、选择合适的 HTTP 请求库以及实现数据的获取、保存、更新和删除操作。
1. 配置开发环境
首先,我们需要对开发工具进行配置,以确保 Web 服务和开发 Web 服务器能够同时启动。以下是具体的操作步骤:
-
配置
api.routes.json
文件
:在
productapp
文件夹中创建或编辑
api.routes.json
文件,内容如下:
{ "/api/*": "/$1" }
-
配置
package.json文件 :在productapp文件夹的package.json文件中,对scripts部分进行修改,使用npm-run-all包来同时启动 HTTP 开发服务器和json-server。具体代码如下:
"scripts": {
"start": "npm-run-all --parallel reactstart json",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"reactstart": "react-scripts start",
"json": "json-server --p 3500 -r api.routes.json restData.js"
}
通过以上配置,当我们在命令行中运行
npm start
时,开发工具和 Web 服务将同时启动。
2. 添加组件和路由
为了演示如何独立消费 Web 服务,我们需要创建一个新的组件并添加相应的路由。具体步骤如下:
-
创建
IsolatedTable
组件
:在
src
文件夹中创建
IsolatedTable.js
文件,内容如下:
import React, { Component } from "react";
export class IsolatedTable extends Component {
render() {
return <table className="table table-sm table-striped table-bordered">
<thead>
<tr><th colSpan="5"
className="bg-info text-white text-center h4 p-2">
(Isolated) Products
</th></tr>
<tr>
<th>ID</th><th>Name</th><th>Category</th>
<th className="text-right">Price</th>
<th></th>
</tr>
</thead>
<tbody>
<tr><td colSpan="5" className="text-center p-2">No Data</td></tr>
</tbody>
</table>
}
}
该组件目前只是渲染一个空表格作为占位符。
-
更新路由配置
:在
src
文件夹的
Selector.js
文件中,更新路由配置,添加一个新的
Route
和相应的导航链接。代码如下:
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Switch, Redirect }
from "react-router-dom";
import { ToggleLink } from "./routing/ToggleLink";
import { RoutedDisplay } from "./routing/RoutedDisplay";
import { IsolatedTable } from "./IsolatedTable";
export class Selector extends Component {
render() {
const routes = React.Children.map(this.props.children, child => ({
component: child,
name: child.props.name,
url: `/${child.props.name.toLowerCase()}`,
datatype: child.props.datatype
}));
return <Router getUserConfirmation={ this.customGetUserConfirmation }>
<div className="container-fluid">
<div className="row">
<div className="col-2">
<ToggleLink to="/isolated">Isolated Data</ToggleLink>
{ routes.map(r => <ToggleLink key={ r.url } to={ r.url }>
{ r.name }
</ToggleLink>)}
</div>
<div className="col">
<Switch>
<Route path="/isolated" component={ IsolatedTable } />
{ routes.map(r =>
<Route key={ r.url }
path={ `/:datatype(${r.datatype})/:mode?/:id?`}
component={ RoutedDisplay(r.datatype)} />
)}
<Redirect to={ routes[0].url } />
</Switch>
</div>
</div>
</div>
</Router>
}
}
3. 运行 Web 服务和示例应用
在命令行中,进入
productapp
文件夹,运行以下命令启动开发工具和 Web 服务:
npm start
项目初始化完成后,会自动打开一个新的浏览器窗口,显示
http://localhost:3000
。同时,我们可以在另一个浏览器窗口中访问
http://localhost:3500/api/products/2
,服务器将返回如下数据:
{ "id": 2, "name": "Lifejacket", "category": "Watersports", "price": 48.95 }
4. 理解 RESTful Web 服务
REST(Representational State Transfer)是一种用于交付和存储应用程序数据的常见模式。RESTful Web 服务的核心思想是利用 HTTP 的特性,通过请求方法(动词)指定服务器要执行的操作,请求 URL 指定要操作的数据对象。以下是一些常见的 HTTP 动词及其在 RESTful Web 服务中的作用:
| Verb | URL | Description |
| ---- | ---- | ---- |
| GET | /api/products | 检索
products
集合中的所有对象。 |
| GET | /api/products/2 | 从
products
集合中检索
id
为 2 的对象。 |
| POST | /api/products | 向
products
集合中添加一个新对象,请求体包含新对象的 JSON 表示。 |
| PUT | /api/products/2 | 替换
products
集合中
id
为 2 的对象,请求体包含替换对象的 JSON 表示。 |
| PATCH | /api/products/2 | 更新
products
集合中
id
为 2 的对象的部分属性,请求体包含要更新的属性和新值的 JSON 表示。 |
| DELETE | /api/products/2 | 从
products
集合中删除
id
为 2 的产品。 |
需要注意的是,不同的 Web 服务在实现方式上可能存在差异,例如有些服务不接受包含
id
值的请求体,有些服务不支持所有的 HTTP 动词。
5. 选择 HTTP 请求库
在消费 Web 服务时,我们需要选择一个合适的 HTTP 请求库。常见的选择有 Axios、
XMLHttpRequest
和 Fetch API。
-
Axios
:本文使用 Axios 库发送 HTTP 请求,因为它易于使用,能自动处理常见的数据类型,并且不需要复杂的代码来处理 CORS 问题。Axios 广泛应用于 Web 应用开发,不限于 React。
-
XMLHttpRequest
:这是最早用于使用 JavaScript 进行请求的 API,虽然使用起来比较麻烦,但具有广泛的浏览器支持。
-
Fetch API
:是现代浏览器提供的新 API,旨在取代
XMLHttpRequest
,但部分旧浏览器可能不支持。
6. 创建数据源组件
为了将使用 Axios 消费 Web 服务的代码与使用它的组件分离,方便测试和复用,我们创建一个数据源组件。在
src/webservice
文件夹中创建
RestDataSource.js
文件,代码如下:
import Axios from "axios";
export class RestDataSource {
constructor(base_url) {
this.BASE_URL = base_url;
}
GetData(callback) {
this.SendRequest("get", this.BASE_URL, callback);
}
async SendRequest(method, url, callback) {
callback((await Axios.request({
method: method,
url: url
})).data);
}
}
该组件定义了一个构造函数,接收 Web 服务的基本 URL,并定义了
GetData
方法来调用
SendRequest
方法发送 HTTP 请求。
7. 在组件中获取数据
接下来,我们需要将数据获取到组件中并显示给用户。更新
IsolatedTable.js
文件,代码如下:
import React, { Component } from "react";
import { RestDataSource } from "./webservice/RestDataSource";
export class IsolatedTable extends Component {
constructor(props) {
super(props);
this.state = {
products: []
}
this.dataSource = new RestDataSource("http://localhost:3500/api/products")
}
render() {
return <table className="table table-sm table-striped table-bordered">
<thead>
<tr><th colSpan="5"
className="bg-info text-white text-center h4 p-2">
(Isolated) Products
</th></tr>
<tr>
<th>ID</th><th>Name</th><th>Category</th>
<th className="text-right">Price</th>
<th></th>
</tr>
</thead>
<tbody>
{ this.state.products.map(p => <tr key={ p.id }>
<td>{ p.id }</td><td>{ p.name }</td><td>{p.category}</td>
<td className="text-right">
${ Number(p.price).toFixed(2)}
</td><td/>
</tr>) }
</tbody>
</table>
}
componentDidMount() {
this.dataSource.GetData(data => this.setState({products: data}));
}
}
在
componentDidMount
方法中请求数据,确保在组件渲染内容后再发送 HTTP 请求。回调函数更新组件的状态数据,触发更新并将数据显示给用户。
8. 避免不必要的数据请求
在开发过程中,要避免在
render
方法中请求数据,因为
render
方法可能会频繁调用,会产生大量不必要的 HTTP 请求。即使使用
componentDidMount
方法,也要注意避免在可能被卸载和重新挂载的组件中重复请求数据。例如,
IsolatedTable
组件在用户导航到不同位置时会被卸载和重新挂载,每次挂载都会请求新的数据。为了避免这种情况,可以将数据提升到不会被卸载的组件中,存储在上下文或数据存储中。
9. 保存、更新和删除数据
为了实现数据的保存、更新和删除操作,我们在
RestDataSource.js
文件中添加相应的方法:
import Axios from "axios";
export class RestDataSource {
constructor(base_url) {
this.BASE_URL = base_url;
}
GetData(callback) {
this.SendRequest("get", this.BASE_URL, callback);
}
async GetOne(id, callback) {
this.SendRequest("get", `${this.BASE_URL}/${id}`, callback);
}
async Store(data, callback) {
this.SendRequest("post", this.BASE_URL, callback, data)
}
async Update(data, callback) {
this.SendRequest("put", `${this.BASE_URL}/${data.id}`, callback, data);
}
async Delete(data, callback) {
this.SendRequest("delete", `${this.BASE_URL}/${data.id}`, callback, data);
}
async SendRequest(method, url, callback, data) {
callback((await Axios.request({
method: method,
url: url,
data: data
})).data);
}
}
这些方法使用 Axios 发送不同类型的 HTTP 请求,通过
data
属性指定请求的有效负载。需要注意的是,不同的 Web 服务在实现方式上可能存在差异,需要根据实际情况进行调整。
综上所述,通过以上步骤,我们可以实现对 RESTful Web 服务的消费,包括数据的获取、保存、更新和删除操作。在实际开发中,要根据具体需求选择合适的 HTTP 请求库,并注意避免不必要的数据请求,以提高应用的性能和稳定性。
消费 RESTful Web 服务指南
10. 处理 Axios 响应
当使用 Axios 发送 HTTP 请求时,我们会得到一个响应对象。这个响应对象包含了服务器返回的各种信息,以下是 Axios 响应对象的一些重要属性:
| 属性名 | 描述 |
| ---- | ---- |
| status | 返回响应的状态码,如 200 或 404。 |
| statusText | 返回伴随状态码的解释性文本,如 OK 或 Not Found。 |
| headers | 返回一个对象,其属性表示响应头。 |
| data | 返回响应的有效负载。 |
| config | 返回一个包含用于发出请求的配置选项的对象。 |
| request | 返回用于发出请求的底层 XMLHttpRequest 对象,如果需要直接访问浏览器提供的 API,这个属性会很有用。 |
在我们之前创建的
RestDataSource
类中,我们通过
callback(response.data)
将响应的有效负载传递给回调函数,这样我们就可以在组件中处理这些数据。
11. 错误处理
在实际应用中,HTTP 请求可能会失败,因此我们需要在代码中添加错误处理机制。我们可以在
RestDataSource
类的
SendRequest
方法中添加错误处理逻辑,以下是更新后的代码:
import Axios from "axios";
export class RestDataSource {
constructor(base_url) {
this.BASE_URL = base_url;
}
GetData(callback) {
this.SendRequest("get", this.BASE_URL, callback);
}
async GetOne(id, callback) {
this.SendRequest("get", `${this.BASE_URL}/${id}`, callback);
}
async Store(data, callback) {
this.SendRequest("post", this.BASE_URL, callback, data)
}
async Update(data, callback) {
this.SendRequest("put", `${this.BASE_URL}/${data.id}`, callback, data);
}
async Delete(data, callback) {
this.SendRequest("delete", `${this.BASE_URL}/${data.id}`, callback, data);
}
async SendRequest(method, url, callback) {
try {
const response = await Axios.request({
method: method,
url: url
});
callback(response.data);
} catch (error) {
console.error('HTTP request error:', error);
// 可以在这里添加更多的错误处理逻辑,如显示错误信息给用户
}
}
}
在
try
块中,我们尝试发送 HTTP 请求并处理响应。如果请求失败,会进入
catch
块,我们可以在其中记录错误信息,也可以根据需要向用户显示错误提示。
12. 优化数据请求
为了提高应用的性能,我们可以对数据请求进行优化。例如,我们可以实现数据缓存,避免重复请求相同的数据。以下是一个简单的数据缓存示例:
import Axios from "axios";
export class RestDataSource {
constructor(base_url) {
this.BASE_URL = base_url;
this.cache = {};
}
GetData(callback) {
if (this.cache[this.BASE_URL]) {
callback(this.cache[this.BASE_URL]);
} else {
this.SendRequest("get", this.BASE_URL, (data) => {
this.cache[this.BASE_URL] = data;
callback(data);
});
}
}
async GetOne(id, callback) {
const url = `${this.BASE_URL}/${id}`;
if (this.cache[url]) {
callback(this.cache[url]);
} else {
this.SendRequest("get", url, (data) => {
this.cache[url] = data;
callback(data);
});
}
}
async Store(data, callback) {
this.SendRequest("post", this.BASE_URL, callback, data);
// 存储新数据后,清除缓存
this.cache = {};
}
async Update(data, callback) {
const url = `${this.BASE_URL}/${data.id}`;
this.SendRequest("put", url, callback, data);
// 更新数据后,清除缓存
this.cache = {};
}
async Delete(data, callback) {
const url = `${this.BASE_URL}/${data.id}`;
this.SendRequest("delete", url, callback, data);
// 删除数据后,清除缓存
this.cache = {};
}
async SendRequest(method, url, callback) {
try {
const response = await Axios.request({
method: method,
url: url
});
callback(response.data);
} catch (error) {
console.error('HTTP request error:', error);
}
}
}
在这个示例中,我们使用
this.cache
对象来存储已经请求过的数据。在请求数据之前,我们先检查缓存中是否已经存在该数据,如果存在则直接使用缓存数据,否则发送请求并将响应数据存入缓存。当进行存储、更新或删除操作后,我们清除缓存,以确保下次请求时能获取到最新的数据。
13. 跨域请求处理
在实际开发中,我们可能会遇到跨域请求的问题。跨域请求是指浏览器从一个域名的网页去请求另一个域名的资源时,由于浏览器的同源策略,会受到限制。Axios 可以方便地处理跨域请求,以下是一个简单的跨域请求示例:
import Axios from "axios";
const apiUrl = 'https://example.com/api';
Axios.get(apiUrl)
.then(response => {
console.log('Response:', response.data);
})
.catch(error => {
console.error('Error:', error);
});
如果服务器端没有正确配置 CORS(跨域资源共享),我们可能会遇到跨域请求失败的问题。这时,我们需要在服务器端进行相应的配置,允许来自特定域名的请求。例如,在 Node.js 中使用 Express 框架时,可以使用
cors
中间件来配置 CORS:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// 其他路由和中间件配置
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
14. 总结与最佳实践
通过前面的介绍,我们了解了如何消费 RESTful Web 服务,包括配置开发环境、添加组件和路由、选择 HTTP 请求库、创建数据源组件、获取和处理数据等。以下是一些消费 RESTful Web 服务的最佳实践:
-
分离关注点
:将消费 Web 服务的代码与使用它的组件分离,如我们创建的
RestDataSource
类,这样可以提高代码的可测试性和可维护性。
-
避免不必要的数据请求
:不要在
render
方法中请求数据,对于可能会被卸载和重新挂载的组件,要注意避免重复请求相同的数据,可以使用数据缓存来优化。
-
错误处理
:在发送 HTTP 请求时,要添加错误处理机制,确保在请求失败时能给用户友好的提示。
-
选择合适的 HTTP 请求库
:根据项目的需求和兼容性要求,选择合适的 HTTP 请求库,如 Axios 具有简单易用、自动处理常见数据类型等优点。
-
处理跨域请求
:如果遇到跨域请求问题,要在服务器端进行相应的 CORS 配置。
总之,消费 RESTful Web 服务是 Web 开发中的常见任务,通过遵循这些最佳实践,我们可以开发出性能良好、稳定可靠的 Web 应用。
下面是一个简单的流程图,展示了消费 RESTful Web 服务的主要流程:
graph TD;
A[启动开发环境] --> B[创建数据源组件];
B --> C[在组件中获取数据];
C --> D[显示数据给用户];
D --> E[处理用户操作];
E --> F[发送保存、更新或删除请求];
F --> C;
通过这个流程图,我们可以清晰地看到消费 RESTful Web 服务的整个过程,从启动开发环境到最终处理用户的操作并更新数据。在实际开发中,我们可以根据这个流程逐步实现各个功能模块。
超级会员免费看
611

被折叠的 条评论
为什么被折叠?



