基于React Hooks的增删改查(CRUD)实例
1 效果展示
2 后台部分实现
2.1 创建Express项目
使用WebStorm创建Express项目server1,并且需要安装一些模块:
# sequelize模块在关系型数据库和对象之间做一个映射,操作数据库更加方便
npm install sequelize
# mysql2模块支持sequelize模块
npm install mysql2
# cors模块解决了跨域的问题
npm install cors
修改端口号,由于原本的3000端口号有些普遍,会与前端页面冲突,因此在bin文件夹下的www.js文件中,修改端口号。具体为第15行代码,将3000修改成8089
var port = normalizePort(process.env.PORT || '8089');
2.2 数据库部分
数据库部分与info数据库中的stu表进行连接,stu表的内容如下:
stu表数据结构如下:
1、在当前项目下新建文件夹config,在该文件夹下创建dbconfig.js文件,该文件是数据库的配置文件,用来连接数据库,具体代码如下:
var Sequelize = require("sequelize");
// 参数分别是:数据库名、用户名、密码
const DB = new Sequelize("info", "root", "123456", {
host: "localhost", // 主机地址
port: 3306, // 数据库端口号
dialect: "mysql", // 数据库类型
pool: { // 数据库连接池
max: 5, // 最大连接数量
min: 0, // 最小连接数量
idle: 10000, // 如果10秒内没有被使用,释放该线程
}
})
module.exports = DB;
2、在当前项目下新建文件夹Model,在该文件夹下新建文件stuModel.js,该文件用来建立数据库模型,将数据库的表映射到某个对象上,相当于与某张表建立联系,具体代码如下:
const DB = require("../config/dbconfig"); // 导入数据库配置文件
const Sequelize = require("sequelize"); // 导入模块
// stu是数据库的表名
const stuModel = DB.define("stu", {
id: {
primaryKey: true, // 设置为主键
type: Sequelize.STRING, // 数据类型
field: "sid", // 将表中的列名与对象名进行映射,当表名与对象名不同时使用
},
name: {
type: Sequelize.STRING,
allowNull: false, // 不允许为空
field: "sname"
},
age: {
type: Sequelize.INTEGER,
allowNull: false
},
gender: {
type: Sequelize.STRING,
allowNull: false
}
}, {
freezeTableName: true, // 表示适应用户给定的表名
timestamps: false // 不显示时间戳
})
module.exports = stuModel;
2.3 接口部分
1、在routes文件夹下新建文件student.js,配置自己的路由接口。在该文件中主要实现了增删改查的功能,具体文件如下:
var express = require("express");
var router = express.Router(); // 使用路由模块化管理
var stuModel = require("../Model/stuModel"); // 导入模型文件
// 获取所有学生信息:http://localhost:8089/student/search
router.get("/search", (req, res) => {
stuModel.findAll({ // findAll,查询所有的数据
raw: true // 显示时间戳
}).then(result => {
// 向前端发送JSON格式的数据,设置成功查询的code为1001
res.json({
code: 1001,
msg: result
})
}).catch(err => {
res.json({
code: 1002, // 设置1002表示发生错误
msg: err
})
})
})
// 添加学生信息:http://localhost:8089/student/add
router.post("/add", (req, res) => {
stuModel.create({ // create表示创建信息
id: req.body.id, // post请求使用req.body来获取
name: req.body.name,
age: req.body.age,
gender: req.body.gender
}).then(result => {
res.json({
code: 1001,
msg: "插入成功"
})
}).catch(err => {
res.json({
code: 1002,
msg: "插入失败"
})
})
})
// 删除学生信息:http://localhost:8089/student/delete
router.post("/delete", (req, res) => {
let id = req.body.id; // 删除时首先要获取被删除的人的学号id再删除整个信息
stuModel.destroy({ // destroy表示删除某个信息
where: {
id: id
}
}).then(result => {
res.json({
code: 1001,
msg: "删除成功"
})
}).catch(err => {
res.json({
code: 1002,
msg: "删除失败"
})
})
})
// 更新某条信息:http://localhost:8089/student/update
router.put("/update", (req, res) => {
// 更新时先根据id查询到要更新的信息,然后再求改其他的信息,id信息不可修改
stuModel.findOne({ // findOne表示查找某条信息
where: {
id: req.body.id
}
}).then(user => { // user表示查询到的结果
user.update({ // update表示更新数据
name: req.body.name,
age: req.body.age,
gender: req.body.gender
}).then(result => {
res.json({
code: 1001,
msg: "更新成功"
})
}).catch(err => {
res.json({
code: 1002,
msg: "更新失败"
})
})
}).catch(err => {
res.json({
code: 1002,
msg: "查询失败"
})
})
})
module.exports = router;
2、在app.js文件中引入跨域模块,并根据刚刚写好的接口配置路由。
// 解决跨域
var cors = require("cors");
app.use(cors());
// 配置路由
var stuRouter = require('./routes/student');
app.use('/student', stuRouter);
3 前端部分实现
3.1 创建React项目
1、使用WebStorm创建React项目demo1,并且安装一些模块:
# axios模块用来发起请求
npm install axios
3.2 编写组件
2、在src文件夹下新建文件夹components,用来存放自定义的组件。在该文件夹下新建文件UserTable.js,该文件主要用来在网页上显示数据表格,具体代码如下:
import React from "react";
const UserTable = (props) => {
return (
// 边框为1
<table border={1}>
<thead>
<tr>
<th width={100}>学号</th>
<th width={100}>姓名</th>
<th width={100}>年龄</th>
<th width={100}>性别</th>
<th width={200} colSpan={2}>操作</th>
</tr>
</thead>
<tbody>
{/*显示数据库中多行信息,使用{}括起来*/}
{
// props.users表示从数据库中获取的所有信息,存放在数组中
// 如果有信息显示所有信息
props.users.length > 0 ? (
props.users.map(user => {
return (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.age}</td>
<td>{user.gender}</td>
<td>
{/*点击编辑后将用户信息传到props下的editRow函数中*/}
<button onClick={() => {
props.editRow(user)
}}>编辑
</button>
{/*点击删除后将用户id传给props下的deleteRow函数*/}
<button onClick={() => {
props.deleteRow(user.id)
}}>删除
</button>
</td>
</tr>
)
})
) : (
// 如果没有信息则显示没有用户信息
<tr>
<td colSpan={5}>没有用户信息</td>
</tr>
)
}
</tbody>
</table>
)
}
export default UserTable;
3、在components文件夹下新建文件AddUserForm.js,用来编写添加用户信息的表单,具体代码如下:
import React, {useState} from "react";
const AddUserForm = (props) => {
const initFormState = {id: " ", name: " ", age: " ", gender: " "};
// 将初始的stu设置为空
const [stu, setStu] = useState(initFormState);
// 定义获取input值的方法
const handleInputChange = (event) => {
const {name, value} = event.target; // event.target获取到了当前input输入的值
setStu({...stu, [name]: value}); // 将修改后的值存放到stu中
}
return (
<form onSubmit={(event => {
event.preventDefault(); // 屏蔽默认提交
if (!stu.id || !stu.name || !stu.age || !stu.gender) {
return; // 如果四项有一项为空,则返回
}
props.addRow(stu); // 将当前的stu传入到props下的addRow方法中
setStu(initFormState); // 传入结束再让添加显示为空
})}>
<label>
学号:<input type={"text"} name={"id"} value={stu.id} onChange={handleInputChange}/>
</label>
<br/><br/>
<label>
姓名:<input type={"text"} name={"name"} value={stu.name} onChange={handleInputChange}/>
</label>
<br/><br/>
<label>
年龄:<input type={"text"} name={"age"} value={stu.age} onChange={handleInputChange}/>
</label>
<br/><br/>
<label>
性别:<input type={"text"} name={"gender"} value={stu.gender} onChange={handleInputChange}/>
</label>
<br/><br/>
<button>添加信息</button>
</form>
)
}
export default AddUserForm;
4、在src文件夹下新建文件EditUserForm.js,该文件主要编写了更新信息的页面,具体代码如下:
import React, {useEffect, useState} from "react";
const EditUserForm = (props) => {
// props.currentStu是传给EditUserForm的参数,是自定义的
const [stu, setStu] = useState(props.currentStu);
// 当props发生改变则设置当前状态
useEffect(() => {
setStu(props.currentStu);
}, [props]);
// 当输入框中的内容发生改变时,则修改当前的状态
const handleInputChange = (event) => {
const {name, value} = event.target; // 获取正在修改的输入框的值
setStu({...stu, [name]: value});
}
return (
<form onSubmit={(event) => {
event.preventDefault(); // 阻止默认提交
props.updateRow(stu); // 为props中的updateRow方法传入参数stu
}}>
<label>
学号:<input type={"text"} name={"id"} value={stu.id} disabled onChange={handleInputChange}/>
</label>
<br/><br/>
<label>
姓名:<input type={"text"} name={"name"} value={stu.name} onChange={handleInputChange}/>
</label>
<br/><br/>
<label>
年龄:<input type={"text"} name={"age"} value={stu.age} onChange={handleInputChange}/>
</label>
<br/><br/>
<label>
性别:<input type={"text"} name={"gender"} value={stu.gender} onChange={handleInputChange}/>
</label>
<br/><br/>
<button>更新学生</button>
{/*点击按钮将props下的setEditing状态改成false*/}
<button onClick={() => props.setEditing(false)}>取消更新</button>
</form>
)
}
export default EditUserForm;
5、编写App.js文件,在该文件中编写CRUD操作,以及将组件集合起来,具体代码如下:
import logo from './logo.svg';
import './App.css';
import UserTable from "./components/UserTable";
import AddUserForm from "./components/AddUserForm";
import EditUserForm from "./components/EditUserForm";
import {useEffect, useState, Fragment} from "react";
import axios from "axios";
function App() {
const initFormState = {id: " ", name: " ", age: " ", gender: " "};
const [student, setStudent] = useState(initFormState); // 将初始值传给当前的student
const [editing, setEditing] = useState(false); // 设置初识editing为false
const [currentStu, setCurrentStu] = useState(initFormState); // 设置初始为空
// CRUD操作
// 查询所有学生
const getRows = () => {
axios.get("http://localhost:8089/student/search")
.then(res => { // res是请求成功后服务器返回的响应数据,这里是查询出来的所有人的信息
setStudent(res.data.msg); // 设置当前学生信息为传回来的结果,msg是在后端定义的
}).catch(err => {
console.log(err);
})
}
// 添加学生信息
const addRow = (stu) => {
axios.post("http://localhost:8089/student/add", stu)
.then(res => {
getRows(); // 请求成功后重新获取数据库中的信息
}).catch(err => {
console.log(err);
})
}
// 删除学生信息
const deleteRow = (id) => {
setEditing(false);
axios.delete("http://localhost:8089/student/delete", {
data: {
id: id
}
}).then(res => {
getRows(); // 删除成功后重新获取数据库中的数据
})
}
// 修改学生信息
const updateRow = (stu) => {
setEditing(false);
axios.put("http://localhost:8089/student/update", stu)
.then(res => {
getRows(); // 更新成功后重新获取数据库中的信息
}).catch(err => {
console.log(err);
})
}
const editRow = (stu) => {
setEditing(true);
setCurrentStu({
id: stu.id,
name: stu.name,
age: stu.age,
gender: stu.gender
})
}
// 当editing发生改变时获取所有学生信息
useEffect(() => {
getRows();
}, [editing]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo"/>
</header>
<h1>CURD App With Hooks</h1>
<div className={"flex-row"}>
<div className={"flex-large"}>
{
editing ? (
<Fragment>
<h2>编辑学生</h2>
<EditUserForm currentStu={currentStu} updateRow={updateRow} setEditing={setEditing}/>
</Fragment>
) : (
<Fragment>
<h2>添加学生</h2>
<AddUserForm addRow={addRow}/>
</Fragment>
)
}
</div>
<div className={"flex-large"}>
<h2>View Students</h2>
<UserTable users={student} editRow={editRow} deleteRow={deleteRow}/>
</div>
</div>
</div>
);
}
export default App;
6、编写App.css文件,设置样式,代码如下:
.App {
text-align: center;
}
.App-logo {
height: 10vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 10vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.flex-row {
display: flex;
}
.flex-large {
margin-left: 300px;
}