React全家桶项目开发
一、全家桶技术
React+函数组件+hooks+ReactRouter+ant design+ redux+echarts+其他技术栈
项目搭建
js
npx create-react-app react-mall
需要自己改造项目
项目效果图:

二、antd组件库的搭建
在React开发过程中,使用ant design这个UI组件库来完成开发。
这个UI组件库蚂蚁金服开源的uI组件库,目前国内针对React项目用的最多的一个库
React版本地址为:ant-design.antgroup.com/docs/react/...
antd也提供vue的版本。你可以在Vue2或者Vue3中使用antd
Vue版本地址为:www.antdv.com/docs/vue/in...
(1)安装antd
js
yarn add antd
npm i antd
目前本文档截至使用的版本5.9.0
(2)组件中使用antd
jsx
import React from 'react'
import {Button} from "antd"
export default function AntdComp() {
return (
<div>
<h3>AntdComp</h3>
<Button type='default'>按钮</Button>
<Button type='dashed'>按钮</Button>
</div>
)
}
以后在项目中,需要什么组件就自己引入这个组件就可以了。
antd5的版本默认已经实现了按需打包。引入过后用过组件才会打包,
antd的样式无需引入,antd4需要手动引入样式。打包的时候,默认就针对你用到组件打包样式
目前antd5中,大部分的组件都基于函数组件来开发的。
三、配置项目启动环境
启动项目的命令
js
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
react-scripts
:实际上是一个启动插件,启动项目,默认加载package.json里面配置文件。默认情况下,如果你启动项目。打包项目的时候,你需要给项目配置webpack内容。必须在package.json中
替换项目启动
craco插件第三方的插件,使用这个插件来替代react-scripts来启动项目。
(1)下载craco
你可以自己配置启动的webpack,或者一些环境变量等等
js
yarn add @craco/craco
下载完成后,我们需要在启动环境中替换为craco
(2)找到package.json
js
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
(3)在项目根目录下面创建文件
在项目根目录下面创建文件craco.config.js
这个文件名字不能写错。默认采用craco来加载项目,读取这个文件。
可以在这个文件中配置webpack相关的内容
js
module.exports = {
}
(4)配置less环境
antd底层默认采用less来设计的代码
我们自己引入样式也有less来设计
js
yarn add craco-less
下载完成后,想要让craco启动项目能加载less环境。
打开craco.config.js
文件。配置less的加载规则
js
const CracoLessPlugin = require("craco-less")
module.exports = {
plugins: [
{ plugin: CracoLessPlugin }
]
}
(5)组件中引入less
在assets/styles/antdcomp.module.less文件中
less
.container{
width:100px,
height:100px,
span{
color:red
}
}
引入到组件中
jsx
import React from 'react'
import {Button} from "antd"
import style from "../assets/styles/antdcomp.module.less"
export default function AntdComp() {
return (
<div>
<h3>AntdComp</h3>
<Button type='default'>按钮</Button>
<Button type='dashed'>按钮</Button>
<div className={style.container}>
<span>woniu</span>
</div>
</div>
)
}
四、主题色设计
antd使用组件库每个组件都默认设置了蓝色的主题。
如果需要修改主题色,参考antd官方给的配置。
全局配置,也可以局部配置
主题配置文档:ant-design.antgroup.com/docs/react/...
在antd4.x版本中,如果要配置主题色,我们在配置文件中全局配置。无法支持局部配置
(1)全局配置
控制所有组件主题色。需要在App.jsx这个根组件中,配置主题色
jsx
import AntdComp from "./components/AntdComp";
import Header from "./components/Header";
import { Button, ConfigProvider, Space } from 'antd';
function App() {
return (
<ConfigProvider
theme={{
token: {
// Seed Token,影响范围大
colorPrimary: '#7cb305'
},
}}
>
<Space>
<Button type="primary">Primary</Button>
<Button>Default</Button>
</Space>
<AntdComp></AntdComp>
<Header></Header>
</ConfigProvider>
);
}
export default App;
ConfigProvider
:这个组件antd5提供一个配置项组件,theme的属性可以设置新的主题颜色,覆盖默认的antd主题色。
(2)局部修改
可以针对某一个组件,组件中某一个局部区域进行主题色修改
需要在布局代码中使用ConfigProvider
Header.jsx子组件中,配置主题色
jsx
import React from 'react'
import { Button, ConfigProvider } from "antd"
export default function Header() {
return (
<div>
<ConfigProvider theme={{
token: {
// Seed Token,影响范围大
colorPrimary: '#d4380d'
},
}}>
<h3>Header</h3>
<Button>修改</Button>
</ConfigProvider>
</div>
)
}
五、函数组件开发
(1)基本概念
类组件开发特点:
- 基于面向对象开发模式,需要一定面向对象基础。
- 类组件包含了完整组件内部状态、外部状态、生命周期函数。
- 类组件相对来说复杂一点,包含this指向问题。尤其是事件绑定。
函数组件开发特点:
- 采用函数式编程,开发难度更低。符合JavaScript开发模式
- 函数组件默认没有组件内部状态,我们需要引入hooks解决组件内部状态
- 函数组件中没有生命周期函数,我们也需要引入hooks来解决生命周期函数问题
- 函数组件中,无需再使用this。开发难度会更低
hooks:实际上封装好的一系列函数,我们可以再函数组件中引入这些hook函数。辅助我们开发。
函数组件基础结构
js
import React from 'react'
const Login = () => {
return (
<div>Login</div>
)
}
export default Login
用箭头函数来设计,并返回当前这个函数。页面加载你这个函数返回JSX模板。
说明:暴露出去的函数。外部直接调用。并不会实例化这个函数
目前公司中React项目开发,80%的项目都会采用函数组件来设计。采用类组件配合函数组件一起使用
(2)函数组件通信
跟类组件不一样的地方,在于子组件接受外部参数的时候。通过函数组件参数来接受props
jsx
import React from 'react'
import {Button} from "antd"
//props = {msg,getMsgValue}
const Login = ({msg,getMsgValue}) => {
return (
<div>
<h3>Login</h3>
<p>{msg}</p>
<Button onClick={()=>getMsgValue("xiniu")}>修改msg</Button>
</div>
)
}
export default Login
Login函数后面的参数,默认接受props对象。你可以解构出来。
在函数组件中定义函数,推荐用箭头函数
jsx
import React from 'react'
import { Button } from "antd"
const Login = ({ msg, getMsgValue }) => {
//函数定义,采用箭头函数来设计
const checkButton = () => {
getMsgValue("woniuxueyuan")
}
return (
<div>
<h3>Login</h3>
<p>{msg}</p>
<Button onClick={ checkButton }>修改msg</Button>
</div>
)
}
export default Login
(3)useState函数
React官方为了解决函数组件没有内部状态。提供了一系列函数来辅助我们开发。
我们将这些函数称为hooks函数。
其中第一个要学习的函数useState,用于定义组件内部状态
jsx
import React,{useState} from 'react'
import { Button } from "antd"
const Login = ({ msg, getMsgValue }) => {
const [username,setUsername] = useState("小王")
const [count,setCount] = useState(100)
const [student,setStudent] = useState({id:1,name:"小张",classes:{name:"三年二班"}})
const checkButton = () => {
setUsername("xiaofei")
}
const updateStudent = ()=>{
const student2 = {...student}
student2.name = "xiaofeifei"
setStudent(student2)
}
return (
<div>
<h3>Login</h3>
<p>{msg}</p>
<p>{username}</p>
<Button onClick={ checkButton }>修改msg</Button>
<p>{count}</p>
<Button onClick={ ()=>setCount(0) }>-</Button>
<Button onClick={ ()=>setCount(200) }>+</Button>
<p>{student.name}</p>
<p>{student.classes.name}</p>
<Button onClick={ updateStudent }>修改student</Button>
</div>
)
}
export default Login
useState来定义组件内部状态特点:
- 每个数据都分别采用useState来进行管理。所有数据分类处理
- 每个变量定义好了,都会有一个唯一的修改函数,对应修改过程
- 如果遇到引用类型数据,修改的时候检测修改对象地址是否新的。否则无法更新
- 修改函数,函数名字是可以用户自定义的。比如
changecount
,但是官方要求我们最好按照set+驼峰命名的方式来设计。可读性和维护性更高 - 修改函数,也是异步更新的,无法马上拿到修改过后结果。如果你要修改后,拿到结果。目前还无法解决
(4)useMemo函数
useMemo是React官方提供出的计算属性函数。可以用于页面数据进行数据处理
语法一:
js
const filterDate = useMemo(()=>{
return 计算结果
},[监控属性])
const filterDate = useMemo(()=>{
return 计算结果
},[监控属性1,监控属性2])
指定监控页面上某个属性,当这个值发生允许计算属性。
语法二:
js
const filterDate = useMemo(()=>{
return 计算结果
})
监控页面上所有的数据,只有数据变化,计算属性都要执行一次。
这种写法比较消耗性能。页面很多数据可能都更新。
(5)useCallback函数
这个函数也是计算属性。
语法和useMemo一样。
但是返回值不是结果,而是一个函数,使用的需要调用一下
jsx
const filterData = useCallback(() => {
switch (status) {
case "all": return tasks;
case "done": return tasks.filter(item => item.state);
case "undone": return tasks.filter(item => !item.state)
}
}, [status])
useCallback用于父子组件通信,解决页面更新问题。
(6)useRef函数
在函数组件中,提供了一个useRef的函数。这个函数可以获取页面中ref指向的节点。
用这种方式更加合理一些。
老方法:
js
<input type="text" ref={element=>Register.inputElement = element} />
ref接受一个函数,接受到结果需要用组件名字挂载。
新方法
js
import {useRef} from "react"
const inputRef = useRef()
//获取文本框值
const getValue = ()=>{
const value = inputRef.current.value
}
<input ref={inputRef}>
目前推荐大家使用useRef来获取节点,并得到结果
(7)useEffect副作用
在函数组件中,并没有生命周期函数。
所以React官方提出了useEfffect的函数,副作用。可以模拟生命周期函数
基于目前useEffect的特性,模拟componentDidMount
jsx
import React, { useEffect, useState } from 'react'
export default function ForgetPassword() {
// 模拟componentDidMount
const [count,setCount] = useState(0)
useEffect(() => {
console.log("componentDidMount");
}, [])
return (
<div>
<h3>ForgetPassword</h3>
<p>{count}</p>
<button onClick={()=>setCount(10)}>修改</button>
</div>
)
}
模拟componentDidUpdate
js
/**
* setCount修改count的值,如何获取修改结果
* 类似于Vue watch
*/
useEffect(()=>{
console.log("componentDidUpdate");
console.log(count);
},[count])
语法二:
js
useEffect(() => {
console.log("componentDidUpdate");
})
表示页面上只要数据发生变化,都要执行一次这个函数。包括props
模拟componentWillUnmount
js
import React, { useEffect } from 'react'
export default function Children() {
useEffect(()=>{
//清除函数
console.log("componentDidMount");
return ()=>{
console.log("componentWillUnmount");
}
},[])
return (
<div>Children</div>
)
}
销毁函数和挂载函数,同一个语法。只是销毁的时候。多添加一个回调函数。
当React调用清除函数的时候,代表组件要销毁了
六、搭建路由
(1)概念讲解
React官网给他定义是一个库,并不是一个框架。
React官方并没有提供路由库,路由插件。
推荐我们使用React结合ReactRouter来进行路由的映射。
文档:reactrouter.com/en/main/sta...
框架路由开发模式,主要由一下几种:
- 编程式路由:需要自己在页面中写代码进行路由编程,包括映射。包括路由引入
- 配置式路由:只需要将路由映射写到配置文件中,剩下的就是自动渲染 Vuejs
- 约定式路由:无需配置路由,你的项目文件系统就是路由。Login.jsx\Register.jsx Login
Vuejs配置式路由
js
src/router/index.js
import VueRouter from "vue-router"
import Login from "./Login.vue"
const routes = [
{
path:"/login",
name:"Login"
component:Login
}
]
const router = new VueRouter({
routes,
mode:"hash"
})
export default router
默认情况下,React项目中使用编程式路由。哪里用到,就在哪里配置
(2)搭建路由
下载对应路由包
js
yarn add react-router-dom
npm i react-router-dom
路由想要在App.jsx 组件搭建。
打开App.jsx配置路由
jsx
import AntdComp from "./components/AntdComp";
import Header from "./components/Header";
import Login from "./views/Login";
import Register from "./views/Register";
import ForgetPassword from "./views/ForgetPassword";
import { Button, ConfigProvider, Space } from 'antd';
import { BrowserRouter, Routes, Route } from "react-router-dom"
function App() {
return (
<ConfigProvider
theme={{
token: {
// Seed Token,影响范围大
colorPrimary: '#7cb305'
},
}}
>
{/* 路由器 */}
<BrowserRouter>
{/* 路由映射列表 */}
<Routes>
{/* 路由具体路径匹配 */}
<Route path="/login" element={<Login></Login>}></Route>
<Route path="/register" element={<Register></Register>}></Route>
<Route path="/forget" element={<ForgetPassword></ForgetPassword>}></Route>
</Routes>
</BrowserRouter>
</ConfigProvider>
);
}
export default App;
(3)路由配置详解
路由器:BrowserRouter、HashRouter
路由器目前有两种,决定了当前路由的模式。
BrowserRouter:默认采用history模式
HashRouter:采用hash模式。路径访问的时候需要/#/
路由映射:Routes、Route主要负责进行路由路径匹配,提供渲染的组件
Routes:表示可以包含多个映射规则。从上到小的进行匹配。当匹配成功结束匹配
Route:进行路由映射,path路由路径,element提供映射组件。指定的这个地方渲染组件
路由导航:Link、NavLink
通过Link和NavLink组件可以实现路由的切换,类似于Vue router-link
(4)路由映射规则
默认索引和重定向
jsx
<Routes>
<Route index element={<Login></Login>}></Route>
</Routes>
默认进来匹配的组件就是Login组件。默认索引。index只能用一次
重定向规则
jsx
import {Navigate} from "react-router-dom"
<Routes>
<Route path="/" element={<Navigate to="/login"></Navigate>}></Route>
</Routes>
我们还可以用重定向解决404的问题
完整页面
jsx
import AntdComp from "./components/AntdComp";
import Header from "./components/Header";
import Login from "./views/Login";
import Register from "./views/Register";
import ForgetPassword from "./views/ForgetPassword";
import { Button, ConfigProvider, Space } from 'antd';
import NotFind from "./views/NotFind";
import { BrowserRouter,HashRouter, Routes, Route,Link,NavLink,Navigate } from "react-router-dom"
function App() {
return (
<ConfigProvider
theme={{
token: {
// Seed Token,影响范围大
colorPrimary: '#7cb305'
},
}}
>
{/* 路由器 */}
<BrowserRouter>
{/* 路由映射列表 */}
<ul>
<li>
<Link to="/register">注册</Link>
</li>
<li>
<NavLink to="/forget">忘记密码</NavLink>
</li>
</ul>
<Routes>
{/* 路由具体路径匹配 */}
<Route path="/" element={<Navigate to="/login"></Navigate>}></Route>
<Route path="/login" element={<Login></Login>}></Route>
<Route path="/register" element={<Register></Register>}></Route>
<Route path="/forget" element={<ForgetPassword></ForgetPassword>}></Route>
<Route path="/404" element={<NotFind></NotFind>}></Route>
<Route path="*" element={<Navigate to="/404"></Navigate>}></Route>
</Routes>
</BrowserRouter>
</ConfigProvider>
);
}
export default App;
(5)嵌套路由
在Home路由下面嵌入Route路由实现嵌套
jsx
import AntdComp from "./components/AntdComp";
import Header from "./components/Header";
import Login from "./views/Login";
import Register from "./views/Register";
import ForgetPassword from "./views/ForgetPassword";
import { Button, ConfigProvider, Space } from 'antd';
import NotFind from "./views/NotFind";
import Home from "./views/Home";
import User from "./views/subs/User";
import Role from "./views/subs/Role";
import { BrowserRouter, HashRouter, Routes, Route, Link, NavLink, Navigate } from "react-router-dom"
function App() {
return (
<ConfigProvider
theme={{
token: {
// Seed Token,影响范围大
colorPrimary: '#7cb305'
},
}}
>
{/* 路由器 */}
<BrowserRouter>
{/* 路由映射列表 */}
<ul>
<li>
<Link to="/register">注册</Link>
</li>
<li>
<NavLink to="/forget">忘记密码</NavLink>
</li>
</ul>
<Routes>
{/* 路由具体路径匹配 */}
<Route path="/" element={<Navigate to="/login"></Navigate>}></Route>
<Route path="/login" element={<Login></Login>}></Route>
<Route path="/home" element={<Home></Home>}>
<Route index element={<User></User>}></Route>
<Route path="role" element={<Role></Role>}></Route>
</Route>
<Route path="/register" element={<Register></Register>}></Route>
<Route path="/forget" element={<ForgetPassword></ForgetPassword>}></Route>
<Route path="/404" element={<NotFind></NotFind>}></Route>
<Route path="*" element={<Navigate to="/404"></Navigate>}></Route>
</Routes>
</BrowserRouter>
</ConfigProvider>
);
}
export default App;
在Home下面配置路由渲染出口
jsx
import React from 'react'
import { Outlet, Link } from "react-router-dom"
export default function Home() {
return (
<div style={{ display: "flex" }}>
<div style={{ width: "200px", backgroundColor: "pink" }}>
<ul>
<li>
<Link to="/home/user">用户</Link>
</li>
<li>
<Link to="/home/role">角色</Link>
</li>
</ul>
</div>
<div>
<h3>content</h3>
<Outlet></Outlet>
</div>
</div>
)
}
Outlet组件就是路由渲染出口。
搭建好二级路由后,整体的布局效果如下:

七、搭建服务器
服务器端代码采用Nodejs+mongodb设计。
每个同学自己把mango的数据文件,导入到本地数据库。
将后端代码发送给我大家,本地启动。
sell-server的服务器代码。
(1)创建数据库
在本地mongodb数据库里面导入我们需要的数据
创建一个数据库WMallTesting
选中这个数据库,右键运行脚本
(2)启动项目
需要你本地电脑安装nodemon插件
js
npm run dev
启动项目需要看到下面信息才代表成功
js
[nodemon] starting `babel-node ./app.js`
you server is running at 8002
connected WMallTesting to database mongodb://127.0.0.1:27017/WMallTesting
(3)直接测试接口
在浏览器输入地址
js
http://127.0.0.1:8002/goods/findGoods
能够得到数据,证明服务器环境成功
后端跨域已经打开。token认证已经关闭。等我们完成登录后,在加上身份认证配置
(4)接口文档
地址:www.showdoc.com.cn/12791639356...
密码:xcb
八、前端请求封装
目前项目开发过程中,默认采用axios来进行请求处理。
(1)下载axios
js
npm i axios
或者
yarn add axios
(2)封装axios请求工具
在utils\axiosUtils.js中,设计下面代码
js
import axios from "axios"
const newAxios = axios.create({
baseURL: "http://127.0.0.1:8002",
timeout: 5000
})
// 设置请求拦截器
// newAxios.interceptors.request.use()
// 响应拦截器
export default newAxios
请求拦截器和响应拦截器,我们设计了登录和身份认证业务后再完善。
(3)设计请求接口
在src/apis/goodsApi.js文件中,设计如下代码
js
import axios from "../utils/axiosUtils"
/**
* 获取所有商品
* @returns
*/
export const findAllGoodsApi = () => {
// axios得到结果 Promise
return axios.get("/goods/findGoods")
}
/**
* 根据条件查询商品
* @param {} data
* @returns
*/
export const findByTypeApi = (data) => {
return axios.post("/goods/findGoodsByName",data)
}
(4)页面中使用封装api
js
import React, { useState, useEffect } from 'react'
import { Card, Space, Table, Tag, Button, Input, Select } from "antd"
import { Link } from "react-router-dom"
import {findAllGoodsApi} from "../../apis/goodsApi"
export default function ProductList() {
const [data, setData] = useState([])
useEffect(() => {
fetchData()
}, [])
/**
* 发送请求
*/
const fetchData =async () => {
const res = await findAllGoodsApi()
console.log(res);
setData(res.data.data)
}
useEffect副作用函数,模拟componentDidMount,进入页面就发送请求。
拿到数据后根据更新函数,覆盖初始值
(5)表格分页设计
antd的表格自带前端分页,如果你要修改为后端分页,请关闭默认分页
jsx
<Table
bordered
rowKey="_id"
columns={columns}
dataSource={data}
loading={false}
pagination={false}
/>
当pagination=false就代表关闭默认分页。自己在单独引入pagination组件来实现后端分页。
如果你就需要前端分页,我们需要配置前端分页的一些参数
比如:每页显示多少条,是否显示下拉框、是否可以允许输入页码跳转等等
jsx
<Table
bordered
rowKey="_id"
columns={columns}
dataSource={data}
loading={false}
pagination= {{
defaultCurrent:1,
defaultPageSize:6,
showQuickJumper:true,
showSizeChanger:true,
pageSizeOptions:[6,12,18]}}
/>
分页的配置
商品页面数据渲染效果

九、添加功能
(1) 复制antd的代码
antd提供了完整表单案列。
我们可以直接将案列拷贝我们项目中运行

需要大家自己改造表单内容,显示文本。删除不需要表单组件
(2)修改提交功能
antd表单支持一键获取所有表单元素的值
前提是每个表单组件都设置name属性
jsx
<Form.Item label="商品名字" name="name">
<Input placeholder="请输入商品名字" />
</Form.Item>
给Form组件添加一个onFinish事件
jsx
<Form
labelCol={{
span: 6,
}}
wrapperCol={{
span: 18,
}}
layout="horizontal"
style={{
maxWidth: 600,
}}
onFinish={onFinish}
>
还需要将按钮修改为提交按钮,才能触发onFinish
jsx
<Form.Item>
<Button type="primary" htmlType="submit">
添加
</Button>
</Form.Item>
(3)验证规则添加
默认引入模板,没有增加验证规则。
我们可以自己添加rules
jsx
<Form.Item
label="商品名字"
name="name"
rules={[
{
required: true,
message: '商品名字不能为空',
},
{
min:3,
message: '长度最少3个',
},
{
max:10,
message: '长度最多10个',
},
]}
>
基础的验证,官方提供规则
自定义正则表单式
jsx
{
pattern:/^[a-zA-Z0-9]{5}$/,
message:"输入内容不合法"
}
效果如下:
