ReactJS – Http 客户端编程

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

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

React 不提供自己的 http 编程 api,但它支持浏览器内置的fetch() api 以及像 axios 这样的第三方客户端库来进行客户端编程。本章让我们学习如何在 React 应用程序中进行 http 编程。开发者需要具备 Http 编程的基本知识才能理解本章。

Expense Rest Api 服务器

进行 Http 编程的前提是具备 Http 协议和 REST API 技术的基础知识。Http编程涉及服务器和客户端两部分。React 支持创建客户端应用程序。Express 是一个流行的 Web 框架,提供创建服务器端应用程序的支持。

让我们首先使用 express 框架创建一个 Expense Rest Api 服务器,然后使用浏览器内置的 fetch api从我们的ExpenseManager应用程序访问它。

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

cd /go/to/workspace 
mkdir apiserver 
cd apiserver

使用以下命令初始化一个新的节点应用程序 –

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

接下来,创建一个文件feedb.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

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

{ 
   "message": "Ok" 
}

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

最后,将 url 更改为http://localhost:8000/api/expense并回车。浏览器将以 JSON 格式显示初始费用条目。

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

让我们在接下来的部分中通过fetch() api 在我们的费用管理器应用程序中使用我们新创建的费用服务器。

fetch() api

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

首先,按照创建 React 应用程序章节中的说明,使用Create React AppRollup bundler创建一个新的 react 应用程序react-http-app 。

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

接下来,在应用程序的根目录下创建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("http://localhost: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('http://localhost: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() {
   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

接下来,打开浏览器,在地址栏输入http://localhost:3000,回车。

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