48、消费 RESTful Web 服务指南

消费 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 服务的整个过程,从启动开发环境到最终处理用户的操作并更新数据。在实际开发中,我们可以根据这个流程逐步实现各个功能模块。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值