ReactJS - HTTP 客户端编程



HTTP 客户端编程使应用程序能够通过 JavaScript 连接并从 HTTP 服务器获取数据。它减少了客户端和服务器之间的数据传输,因为它只获取所需的数据而不是整个设计,从而提高了网络速度。它改善了用户体验,并成为每个现代 Web 应用程序不可或缺的功能。

如今,许多服务器端应用程序通过 REST API(HTTP 协议上的功能)公开其功能,并允许任何客户端应用程序使用这些功能。

React 本身没有提供自己的 HTTP 编程 API,但它支持浏览器的内置 fetch() API 以及 axios 等第三方客户端库来进行客户端编程。在本章中,让我们学习如何在 React 应用程序中进行 HTTP 编程。开发人员应该具备 HTTP 编程的基本知识才能理解本章内容。

费用 REST API 服务器

进行 HTTP 编程的先决条件是对 HTTP 协议和 REST API 技术的基本了解。HTTP 编程涉及服务器和客户端两个部分。React 提供了创建客户端应用程序的支持。Express 是一款流行的 Web 框架,它提供了创建服务器端应用程序的支持。

让我们首先使用 Express 框架创建一个费用 REST API 服务器,然后使用浏览器的内置 fetch API 从我们的 ExpenseManager 应用程序访问它。

打开命令提示符并创建一个新文件夹 express-rest-api

cd /go/to/workspace 
mkdir apiserver 
cd apiserver

使用以下命令初始化一个新的 Node 应用程序:

npm init

npm init 将提示我们输入基本项目详细信息。让我们为项目名称输入 apiserver,并为入口点输入 server.js。将其他配置保留为默认选项。

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (apiserver)
version: (1.0.0)
description: Rest api for Expense Application
entry point: (index.js) server.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to \path\to\workspace\expense-rest-api\package.json:
{
   "name": "expense-rest-api",
   "version": "1.0.0",
   "description": "Rest api for Expense Application",
   "main": "server.js",
   "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
   "license": "ISC"
}
Is this OK? (yes) yes

接下来,使用以下命令安装 express、nedb & cors 模块:

npm install express nedb cors
  • express 用于创建服务器端应用程序。

  • nedb 是一个用于存储费用数据的数据存储。

  • corsexpress 框架的一个中间件,用于配置客户端访问详细信息。

接下来,让我们创建一个文件 data.csv 并用初始费用数据填充它以进行测试。该文件结构为每行包含一个费用条目。

Pizza,80,2020-10-10,Food
Grape Juice,30,2020-10-12,Food
Cinema,210,2020-10-16,Entertainment
Java Programming book,242,2020-10-15,Academic
Mango Juice,35,2020-10-16,Food
Dress,2000,2020-10-25,Cloth
Tour,2555,2020-10-29,Entertainment
Meals,300,2020-10-30,Food
Mobile,3500,2020-11-02,Gadgets
Exam Fees,1245,2020-11-04,Academic

接下来,创建一个文件 expensedb.js 并包含加载初始费用数据到数据存储的代码。该代码检查数据存储中是否存在初始数据,并且仅在存储中没有数据时才加载。

var store = require("nedb")
var fs = require('fs');
var expenses = new store({ filename: "expense.db", autoload: true })
expenses.find({}, function (err, docs) {
   if (docs.length == 0) {
      loadExpenses();
   }
})
function loadExpenses() {
   readCsv("data.csv", function (data) {
      console.log(data);

      data.forEach(function (rec, idx) {
         item = {}
         item.name = rec[0];
         item.amount = parseFloat(rec[1]);
         item.spend_date = new Date(rec[2]);
         item.category = rec[3];

         expenses.insert(item, function (err, doc) {
            console.log('Inserted', doc.item_name, 'with ID', doc._id);
         })
      })
   })
}
function readCsv(file, callback) {
   fs.readFile(file, 'utf-8', function (err, data) {
      if (err) throw err;
      var lines = data.split('\r\n');
      var result = lines.map(function (line) {
         return line.split(',');
      });
      callback(result);
   });
}
module.exports = expenses

接下来,创建一个文件 server.js 并包含列出、添加、更新和删除费用条目的实际代码。

var express = require("express")
var cors = require('cors')
var expenseStore = require("./expensedb.js")
var app = express()
app.use(cors());
var bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
var HTTP_PORT = 8000
app.listen(HTTP_PORT, () => {
   console.log("Server running on port %PORT%".replace("%PORT%", HTTP_PORT))
});
app.get("/", (req, res, next) => {
   res.json({ "message": "Ok" })
});
app.get("/api/expenses", (req, res, next) => {
   expenseStore.find({}, function (err, docs) {
      res.json(docs);
   });
});
app.get("/api/expense/:id", (req, res, next) => {
   var id = req.params.id;
   expenseStore.find({ _id: id }, function (err, docs) {
      res.json(docs);
   })
});
app.post("/api/expense/", (req, res, next) => {
   var errors = []
   if (!req.body.item) {
      errors.push("No item specified");
   }
   var data = {
      name: req.body.name,
      amount: req.body.amount,
      category: req.body.category,
      spend_date: req.body.spend_date,
   }
   expenseStore.insert(data, function (err, docs) {
      return res.json(docs);
   });
})
app.put("/api/expense/:id", (req, res, next) => {
   var id = req.params.id;
   var errors = []
   if (!req.body.item) {
      errors.push("No item specified");
   }
   var data = {
      _id: id,
      name: req.body.name,
      amount: req.body.amount,
      category: req.body.category,
      spend_date: req.body.spend_date,
   }
   expenseStore.update( { _id: id }, data, function (err, docs) {
      return res.json(data);
   });
})
app.delete("/api/expense/:id", (req, res, next) => {
   var id = req.params.id;
   expenseStore.remove({ _id: id }, function (err, numDeleted) {
      res.json({ "message": "deleted" })
   });
})
app.use(function (req, res) {
   res.status(404);
});

现在,是时候运行应用程序了。

npm run start

接下来,打开浏览器并在地址栏中输入 https://127.0.0.1:8000/

{ 
   "message": "Ok" 
}

它确认我们的应用程序运行良好。

最后,将 URL 更改为 https://127.0.0.1:8000/api/expense 并按 Enter 键。浏览器将以 JSON 格式显示初始费用条目。

[
   ...
   {
      "name": "Pizza",
      "amount": 80,
      "spend_date": "2020-10-10T00:00:00.000Z",
      "category": "Food",
      "_id": "5H8rK8lLGJPVZ3gD"
   },
   ...
]

让我们在下一节中通过 fetch() API 在我们的费用管理器应用程序中使用我们新创建的费用服务器。

fetch() API

让我们创建一个新应用程序来展示 React 中的客户端编程。

首先,使用 Create React AppRollup 捆绑器创建一个新的 React 应用程序 react-http-app,方法是按照 创建 React 应用程序 一章中的说明进行操作。

接下来,在您喜欢的编辑器中打开应用程序。

接下来,在应用程序的根目录下创建 src 文件夹。

接下来,在 src 文件夹下创建 components 文件夹。

接下来,在 src/components 文件夹下创建一个文件 ExpenseEntryItemList.css 并包含通用表格样式。

html {
   font-family: sans-serif;
}
table {
   border-collapse: collapse;
   border: 2px solid rgb(200,200,200);
   letter-spacing: 1px;
   font-size: 0.8rem;
}
td, th {
   border: 1px solid rgb(190,190,190);
   padding: 10px 20px;
}
th {
   background-color: rgb(235,235,235);
}
td, th {
   text-align: left;
}
tr:nth-child(even) td {
   background-color: rgb(250,250,250);
}
tr:nth-child(odd) td {
   background-color: rgb(245,245,245);
}
caption {
   padding: 10px;
}
tr.highlight td { 
    background-color: #a6a8bd;
}

接下来,在 src/components 文件夹下创建一个文件 ExpenseEntryItemList.js 并开始编辑。

接下来,导入 React 库。

import React from 'react';

接下来,创建一个类 ExpenseEntryItemList 并使用 props 调用构造函数。

class ExpenseEntryItemList extends React.Component {
   constructor(props) {
      super(props);
   }
}

接下来,在构造函数中用空列表初始化状态。

this.state = {
   isLoaded: false,
   items: []
}

接下来,创建一个方法 setItems 来格式化从远程服务器接收到的项目,然后将其设置为组件的状态。

setItems(remoteItems) {
   var items = [];
   remoteItems.forEach((item) => {
      let newItem = {
         id: item._id,
         name: item.name,
         amount: item.amount,
         spendDate: item.spend_date,
         category: item.category
      }
      items.push(newItem)
   });
   this.setState({
      isLoaded: true,
      items: items
   });
}

接下来,添加一个方法 fetchRemoteItems 从服务器获取项目。

fetchRemoteItems() {
   fetch("https://127.0.0.1:8000/api/expenses")
      .then(res => res.json())
      .then(
         (result) => {
            this.setItems(result);
         },
         (error) => {
            this.setState({
               isLoaded: false,
               error
            });
         }
      )
}

这里:

  • fetch API 用于从远程服务器获取项目。

  • setItems 用于格式化并将项目存储在状态中。

接下来,添加一个方法 deleteRemoteItem 从远程服务器删除项目。

deleteRemoteItem(id) {
   fetch('https://127.0.0.1:8000/api/expense/' + id, { method: 'DELETE' })
      .then(res => res.json())
      .then(
         () => {
            this.fetchRemoteItems()
         }
      )
}

这里:

  • fetch API 用于从远程服务器删除和获取项目。

  • setItems 再次用于格式化并将项目存储在状态中。

接下来,调用 componentDidMount 生命周期 API 以在组件挂载阶段将项目加载到组件中。

componentDidMount() { 
   this.fetchRemoteItems(); 
}

接下来,编写一个事件处理程序以从列表中删除项目。

handleDelete = (id, e) => { 
   e.preventDefault(); 
   console.log(id); 

   this.deleteRemoteItem(id); 
}

接下来,编写 render 方法。

render() {
   let lists = [];
   if (this.state.isLoaded) {
      lists = this.state.items.map((item) =>
         <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
            <td>{item.name}</td>
            <td>{item.amount}</td>
            <td>{new Date(item.spendDate).toDateString()}</td>
            <td>{item.category}</td>
            <td><a href="#" onClick={(e) => this.handleDelete(item.id, e)}>Remove</a></td>
         </tr>
      );
   }
   return (
      <div>
         <table onMouseOver={this.handleMouseOver}>
            <thead>
               <tr>
                  <th>Item</th>
                  <th>Amount</th>
                  <th>Date</th>
                  <th>Category</th>
                  <th>Remove</th>
               </tr>
            </thead>
            <tbody>
               {lists}
            </tbody>
         </table>
      </div>
   );
}

最后,导出组件。

export default ExpenseEntryItemList;

接下来,在 src 文件夹下创建一个文件 index.js 并使用 ExpenseEntryItemList 组件。

import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList';

ReactDOM.render(
   <React.StrictMode>
         <ExpenseEntryItemList />
   </React.StrictMode>,
   document.getElementById('root')
);

最后,在根文件夹下创建一个 public 文件夹并创建 index.html 文件。

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8">
      <title>React App</title>
   </head>
   <body>
      <div id="root"></div>
      <script type="text/JavaScript" src="./index.js"></script>
   </body>
</html>

接下来,打开一个新的终端窗口并启动我们的服务器应用程序。

cd /go/to/server/application 
npm start

接下来,使用 npm 命令提供客户端应用程序。

npm start

接下来,打开浏览器并在地址栏中输入 https://127.0.0.1:3000 并按 Enter 键。

Material

尝试通过点击删除链接来删除项目。

Materials
广告