React全家桶项目开发
一、全家桶技术栈
React+函数组件+ReactRouter+axios处理网络请求+redux进行状态管理+echarts可视化图表统计+其他技术栈+antd进行页面设计
woniumall项目:包含登录、注册、权限(用户、角色、授权)、商品管理、分类、财务数据、店铺管理等等
搭建一个新项目,在项目中配置各种环境
二、搭建项目环境
(1)创建基础项目
lua
npx create-react-app woniumall
创建项目名字不能有大写,而且名字不能中文。
(2)项目目录
assets:存放静态资源,主要图片、样式、字体之内的
apis:负责网络请求封装。将请求代码和组件分离开
utils:封装项目中用到的工具。
config:存放项目中需要定义配置信息。路由列表、菜单列表等等、省市区三级联动
views:存放项目页面的目录,跟路由相关的组件都是页面
components:存放组件的地方,公共的一些代码
(3)全局样式
项目开发过程中,很多标签都有默认样式。
我们去掉这些默认样式,统一一下标准。减少浏览器差异
assets/index.css
jsx
body{
margin: 0px;
padding: 0px;
}
ul{
list-style: none;
margin: 0px;
padding: 0px;
}
a{
text-decoration: none;
color:black
}
在src/index.js入口文件中引入
arduino
import "./assets/styles/index.css"
因为没有用模块化,这个样式全局样式,影响以后所有组件
(4)App.jsx采用函数组件开发
jsx
import React from 'react'
function App() {
return (
<div>App</div>
)
}
export default App
目前在公司里面开发,基本上主流都采用函数组件开发。配合类组件来一起设计。
函数组件和类组件区别:
- 类组件基于面向对象来进行开发。函数组件基于函数式编程来进行开发。函数组件更简单,符合前端常见开发模式。
- 函数组件中没有this指向的问题。类组件严格判断this的指向。函数组件开发过程更加方便
- 类组件有完整生命周期、有完整组件状态。但是函数组件没有(生命周期、内部状态)
三、下载antd组件库
商业项目开发,可以考虑用开源的免费的第三方的UI组件库辅助你开发。
减少我们重复造轮子的过程。
采用antd组件库来完成。
(1)下载antd
css
npm i antd
下载的版本目前默认5.12.x版本
(2)使用antd
在组件中引入你需要组件
python
import {Button} from "antd"
<Button type="primary">文字</Button>
按需引入自己组件,无需引入样式,根据你使用的组件,默认加载组件的样式
四、配置项目启动环境
默认React项目启动采用的是
js
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
平时启动项目:yarn start
启动项目插件:react-scripts
底层也是webpack来加载项目。配置代码默认放在package.json文件中。后续开发维护都不方便。
需要下载一个新的插件来启动我们项目。
我们项目打包配置,可以单独放在一个文件中进行维护
(1)下载插件
sql
yarn add @craco/craco
第三方的启动插件。用这个插件来启动,单独加载webpack配置。
后续让这个插件加载less文件
(2)配置craco插件
在我们package.json文件中,找到scripts
,修改代码如下
js
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
(3)项目根目录新增craco.config.js文件
在根目录。不是src目录里面
ini
module.exports = {}
(4)配置less环境
React项目默认没有less或者sass环境的。
需要开发者自己配置。React中一般默认用less
csharp
yarn add craco-less
在项目配置文件中,加载这个插件,让css能够支持用less替换
在craco.config.js文件中配置craco-less插件
jsx
const CracoLessPlugin = require("craco-less")
module.exports = {
plugins:[
{plugin:CracoLessPlugin}
]
}
在assets/styles/App.module.less
jsx
.container{
width: 100px;
height: 100px;
background-color: pink;
span{
color:red
}
}
在App.jsx组件中引入
jsx
import React from 'react'
import {Button} from "antd"
import styles from "./assets/styles/App.module.less"
function App() {
let temp = "xiaowang"
return (
<div>
<h2>App</h2>
<p>{temp}</p>
<Button type="primary">antd按钮</Button>
<div className={styles.container}>
<span>测试</span>
</div>
</div>
)
}
export default App
五、配置antd的主题色
默认情况下,你使用antd的组件,有一个蓝色主题。
自己项目风格,无法根antd的主题色保持统一,需要我们自己定制主题。
在5.0这个版本中,配置主题色粒度更小。可以细化到全局主题,局部主题。
还可以实现主题的动态切换。4.0的主题写死在配置文件中的。
在App.jsx组件中配置全局主题:以后所有子组件都在App.jsx中加载。
jsx
import React from 'react'
import { Button, ConfigProvider } from "antd"
function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#7cb305'
}
}}>
<Button type="primary">Primary Button</Button>
<Button>Default Button</Button>
<Button type="dashed">Dashed Button</Button>
<Button type="text">Text Button</Button>
<Button type="link">Link Button</Button>
</ConfigProvider>
)
}
export default App
如果某个组件需要独立配置主题色。你也可以同样代码配置一次
Header.jsx
jsx
import React from 'react'
import { Button, ConfigProvider } from "antd"
export default function Header() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#fa541c'
}
}}>
<div>
<h3>Header</h3>
<Button>头部按钮</Button>
</div>
</ConfigProvider>
)
}
六、搭建路由环境
React官方并没有提供路由的相关内容。
你需要在React项目中使用路由推荐一个第三方的路由组件库
ReactRouter专门提供React路由。
文档地址:reactrouter.com/en/main/sta...
(1)下载路由
jsx
npm i react-router-dom
默认下载6的版本。6这个版本和5版本有差别
(2)搭建路由
在App.jsx这个组件中,引入路由组件
jsx
import React from 'react'
import { Button, ConfigProvider } from "antd"
import { BrowserRouter, Routes, Route } from "react-router-dom"
import Login from "./views/Login"
import Register from "./views/Register"
import Home from "./views/Home"
function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#7cb305'
}
}}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login></Login>}></Route>
<Route path="/reg" element={<Register></Register>}></Route>
<Route path="/home" element={<Home></Home>}></Route>
</Routes>
</BrowserRouter>
</ConfigProvider>
)
}
export default App
在浏览器里面输入对应path路径,就可以进入不同页面
路由器:HashRouter、BrowserRouter
BrowserRouter:代表history模式
HashRouter:代表hash模式,默认路径需要/#/来访问
必须提供路由器,才能实现路由的映射
路由映射:Routes、Route
Routes:包含多个路由映射规则
Route:必须提供path属性和element属性,代表路由和组件之间映射关系
设置默认索引
jsx
<Route index={true} element={<Login></Login>}></Route>
给Route组件添加一个index属性,代表默认索引,/
路径进入默认索引
重定向
jsx
import { BrowserRouter, HashRouter, Routes, Route,Navigate } from "react-router-dom"
<Routes>
<Route path="/login" element={<Login></Login>}></Route>
<Route path="/reg" element={<Register></Register>}></Route>
<Route path="/home" element={<Home></Home>}></Route>
<Route path="/404" element={<NotFind></NotFind>}></Route>
<Route path="*" element={<Navigate to="/404"></Navigate>}></Route>
</Routes>
当前面所有路由都匹配不了的时候,进入 *
来进行匹配。重定向到404路由。
(3)嵌套路由配置
涉及到二级、三级路由。配置规则如下
创建对应页面
views/subs/system/User.jsx
views/subs/system/Role.jsx
放在subs文件夹下面页面子页面,主要用于Home这个页面中嵌套渲染
搭建嵌套路由规则
jsx
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login></Login>}></Route>
<Route path="/reg" element={<Register></Register>}></Route>
<Route path="/home" element={<Home></Home>}>
{/* /home/user /home/ */}
<Route path="user" element={<User></User>}></Route>
<Route path="role" element={<Role></Role>}></Route>
</Route>
<Route path="/404" element={<NotFind></NotFind>}></Route>
<Route path="*" element={<Navigate to="/404"></Navigate>}></Route>
</Routes>
</BrowserRouter>
在Route标签内部在添加其他Route映射,代表指定路由下面子路由。必须先进入/home
才能继续访问子路由。
子路由路径在匹配过程中不要在 /
访问子路由:/home/user /home/role
目前配置的路由属于编程式路由:硬编码在我们项目中。后面还会继续优化我们的路由,做成动态的路由。
(4)路由导航
路由导航组件可以实现路由的跳转。
jsx
import {Link,NavLink} from "react-router-dom"//router-link 进行跳转的标签
进行路由跳转
jsx
import React from 'react'
import styles from "../assets/styles/home.module.less"
import { Outlet,Link,NavLink } from "react-router-dom"
export default function Home() {
return (
<div className={styles.wrapper}>
<div className={styles.menu}>
<ul>
<li><Link to="/home/user">用户管理</Link></li>
<li><Link to="/home/role">角色管理</Link></li>
<li><NavLink to="/home/">主页</NavLink></li>
</ul>
</div>
<div className={styles.content}>
{/* 路由渲染出口,类似于Vue router-view */}
<Outlet></Outlet>
</div>
</div>
)
}
七、hooks编程
在React中开发中,提供两种组件
- 基于面向对象类组件。完整的生命周期、组件内部状态。缺点:基于面向对象开发,上手难度会大一些。有继承,组件的关联性会更大一些。
- 基于函数式编程,开发简单,易于维护扩展。缺点:没有组件内部状态、也没有生命周期函数
如何让函数组件达到类组件的效果。开发直接使用函数组件就可以了
在React16.8这个版本开始,提出了hooks编程。官方设计了一堆函数,用于辅助我们的组件开发。
这种函数配合函数组件开发,称为hooks函数
有了这个特性过后,完全可以用函数组件来进行业务开发了。
特点:hooks函数只能在函数组件中使用,类组件无法使用hooks编程
官方提供:React底层设计的hook函数,可以直接项目中导入使用
第三方:下载第三方包,引入提供hook函数
自定义:后续我们可以根据自己业务,自定义hook满足业务开发
(1)useState函数
useState是官方提供的一个hook函数,主要用于实现组件内部状态管理。
jsx
import {useState} from "react"
const [username,setUsername]= useState("xiaowang")
特点:
- 可以直接useState参数里提供对应值。
- 必须声明一个变量类接受这个参数。
- 还必须提供一个修改变量函数,命名可以自己定。官方要求 set+变量名字(驼峰)
jsx
const [count, setCount] = useState(100)
const [user,setUser] = useState({id:1,name:"xiaowang"})
//类组件
state = {
count:100,
user:{id:1}
}
页面上状态都是分开来管理的。单独维护。
修改函数,setCount、setUser也是异步修改。
如何拿到修改过后结果?需要借助其他hook函数一起解决
扩展:特殊格式,修改的传递一个函数,函数一定要返回修改结果
jsx
setCont(()=>{
return 300
})
hook函数引入过后,一定要放在组件最前面使用。
(2)useMemo函数
在类组件中我们计算属性,通过get来定义一个属性,内部使用变量进行值的处理
jsx
class User extends Component{
state = {
id:1
}
//计算属性
get filterData(){
return this.state.id * 2
}
}
函数组件中没有这个语法。官方提供了一个hook函数用于计算属性
jsx
//用于定义组件内部状态;count变量名字 setCount修改变量方案
const [count, setCount] = useState(100)
const [user,setUser] = useState({id:1,name:"xiaowang"})
// 监听count,计算结果并返回
const computedValue = useMemo(()=>{
return count * 2
},[count])
const computedValue = useMemo(()=>{
return count * 2
},[count,username])
useMemo参数
- 第一个参数:回调函数用于执行计算过程,一定要返回结果
- 第二个参数:指定监控哪些属性,只要这些属性发生变化,计算属性就会执行
(3)useCallback函数
这个函数也是计算属性。功能跟useMemo一摸一样。但是返回结果是一个函数
jsx
//用于定义组件内部状态;count变量名字 setCount修改变量方案
const [count, setCount] = useState(100)
const [user,setUser] = useState({id:1,name:"xiaowang"})
// 监听count,计算结果并返回
const computedValue = useMemo(()=>{
return count * 2
},[count])
const fn = useCallback(()=>{
return count * 10
},[count])
<p>{computedValue}</p>
<p>{fn()}</p>
返回的结果为函数,使用的时候需要调用这个函数
一般组件内部计算属性都用useMemo来实现。
如果组件通信,父组件传递内容给子组件,在某些场景,使用useCallback
(4)useRef函数
用于获取页面中指定节点。
类组件中:
jsx
<button ref={element=>this.btnElement = element}></button>
getMessage = ()=>{
this.btnElement
}
函数组件中获取节点两种方案:
方案一:
jsx
<input ref={element=>Register.inputElement = element} type="text" />
<button onClick={addTask}>添加</button>
const addTask = ()=>{
//Register组件名字
console.log(Register.inputElement);
}
在组件中无法使用this来报错全局变量,可以采用组件名字来存放我们变量
方案二:useRef来操作
jsx
import {useRef} from "react"
const myInput = useRef()
<input ref={myInput} type="password">
console.log(myInput) //{current:input}
这种方式获取我们节点会比方案一更加简单方便。
(5)useEffect函数
useEffect称为副作用。
函数组件没有生命周期,可以利用副作用函数来模拟生命周期。
可以利用副作用函数来模拟各个阶段
阶段一:组件挂载
jsx
//组件挂载
useEffect(() => {
//模拟componentDidMount
console.log("componentDidMount");
//获取节点,发送异步请求
}, [])
当第二个参数为空数组,代表实现componentDidMount
阶段二:组件更新
jsx
//组件更新,指定监控count属性,类似于watch
useEffect(()=>{
console.log("componentDidUpdate");
console.log(count) //得到修改成功结果
},[count])
数组里面填入了哪些属性,代表监控着属性变化。
jsx
//组件更新, 组件中任何一个变量发生变化,这个副作用都要执行
useEffect(()=>{
console.log("componentDidUpdate");
})
阶段三:销毁阶段
jsx
//销毁阶段
useEffect(()=>{
//代码写在这里.创建
//返回一个清除函数
return ()=>{
//在这里写代码
console.log("componentWillUnmount");
}
},[])
销毁用到语法跟挂载是一样的,只是销毁多了一个清除函数。
只有销毁组件,清除函数才会运行
八、搭建后端环境
(1)搭建数据库环境
目前采用Nodejs+mongoDB来设计
接口文档:www.showdoc.com.cn/12791639356...
如果是在本地搭建服务器环境,mongoDB数据库,本地已经启动MongoDB服务
在本地导入数据库需要的数据
后端项目中有一个db文件夹,里面包含了数据库文件,默认js结尾文件。
在navicat这个工具中可以运行脚本,将js文件都执行一遍。数据库就默认创建成功。
(2)启动项目
配置项目App.js文件夹
jsx
app.use(function(req,res,next){
res.setHeader("Access-Control-Allow-Origin","*");
res.setHeader("Access-Control-Allow-Headers","content-type,token,x-requested-with");
res.setHeader('Access-Control-Allow-Methods',"DELETE")
next();
});
关闭了token的身份认证
jsx
// 这句话表示验证token,验证成功代码继续往下执行
// app.use(tokenVerify);
启动项目
你的电脑安装nodemon插件
如果你没有安装过nodemon这个插件,你需要执行
css
npm i nodemon -g
启动命令
arduino
npm run dev
日志: 服务器启动成功,数据库连接成功
jsx
you server is running at 8002
connected WMallTesting to database mongodb://127.0.0.1:27017/WMallTesting
测试一下接口
jsx
http://127.0.0.1:8002/categroy/findCategroy?parentId=0
九、 网络请求代码
前端网络请求发展
(1)原生Ajax代码
ajax这个是浏览器端技术,最早微软提出的在IE浏览器使用。后续各大浏览器都开始支持AJAX技术
到目前位置,各大浏览器都可以使用。
Ajax实现前端发送异步请求到后端,获取数据进行页面局部更新。
同步请求:有些网站你在提交数据的时候,整个页面都在处于等待,一定要等你提交完成后,跳转到其他页面,你才能继续操作页面。
在学习过程中,大家都是接触现成的ajax工具,第三方封装好的工具来开发。
jsx
const obtn = document.getElementById("btn")
obtn.onclick = function(){
sendMessage()
}
function sendMessage(){
//步骤一:
// 这个对象无需导入,直接创建,浏览器默认提供对象
// 前端发送异步请求核心对象
let xmlhttp = new XMLHttpRequest()
//步骤二:
//建立客户端和服务器的连接
xmlhttp.open("GET","http://127.0.0.1:8002/categroy/findCategroy?parentId=0",true)
//发送请求
xmlhttp.send()
//步骤三:
// 请求成功还是失败,前端需要监听状态码.获取后端响应结果
// onreadystatechange事件,持续不断监听状态码
xmlhttp.onreadystatechange = function(){
//进行状态码判断
if(xmlhttp.status >= 200 && xmlhttp.readyState == 4){
const res = xmlhttp.responseText
console.log("后端返回的数据:",res);
}
}
}
我们采用原生的ajax来发送请求,代码太麻烦。重复发送请求。有冗余
考虑封装一下代码,变成一个工具
jsx
const obtn = document.getElementById("btn")
obtn.onclick = function(){
ajax({
url:"http://127.0.0.1:8002/categroy/findCategroy?parentId=0",
success(msg){
console.log(msg)
},
error(msg){
console.log(msg);
}
})
}
function ajax({url,method="GET",async=true,data={},success,error}){
let xmlhttp = new XMLHttpRequest()
xmlhttp.open(method,url,async)
xmlhttp.send(data)
xmlhttp.onreadystatechange = function(){
//进行状态码判断
if(xmlhttp.status >= 200 && xmlhttp.readyState == 4){
const res = xmlhttp.responseText
success(res)
}else{
const errorMsg = xmlhttp.responseText
error(errorMsg)
}
}
}
ajax这个函数封装的工具,以后发送请求调用函数传递参数,不用重复写请求代码。
jquery的ajax请求就是封装底层ajax代码。请求更加方便
(2)Ajax下一步封装
jquery虽然封装了ajax,但是也有缺陷
jsx
ajax({
url:"/student?id=1",
success(msg){ //{id:1,name:"xiaowang",teacherId:2}
ajax({
url:"/teacher",
method:"POST"
data:{teacherId:msg.teacherId},
success(msg2){
ajax({
})
}
})
}
})
回调地狱:在回调函数继续调用函数,实现回调嵌套。回调地狱。
可读性非常差,回调地狱不好进行维护,每一层理清楚代码逻辑
jsx
uni.getUserProfile({
success(){
uni.login({
succcess(code){
uni.request({
url:"",
data:code
})
}
})
}
})
出现一个技术Promise来解决回调地狱问题
Promise是一个数据容器(对象,承诺),在Promise内部执行异步任务,并将结果返回给外部使用。
jsx
const p = new Primise((reslove,reject)=>{
ajax({
url:"/student?id=1",
success(msg){
reslove(msg)
}
})
})
//可以通过promise then获取数据
p.then(res=>{
ajax({
url:"/teacher",
data:{id:res.teacherId}
})
})
//方法2:可以await等待promise结果
/await可以配合promise使用。等待promise结果。
const res = await p
const res = await p2(res.teacherId)
出现了一个工具axios,第三方封装的请求工具
axios = ajax + promise
jsx
import axios from "axios"
const stu = await axios.get("/student")
const teacher = await axios.post(url,stu.teacherId)
axios代码
jsx
class axios{
get(url){
return Promise((resolve,reject)=>{
ajax({
url,
success(msg){
resolve(msg)
}
})
})
}
}
第三方请求工具不止axios这个工具,还有其他很多工具,fetch
十、封装请求
项目默认采用axios来处理网络请求。因为第三方请求工具,需要下载axios的包
(1)下载依赖
css
npm i axios
(2)测试axios请求
jsx
useEffect(()=>{
fetchData()
},[])
const fetchData =async ()=>{
//方案一:
axios.get("http://127.0.0.1:8002/categroy/findCategroy?parentId=0").then(res=>{
console.log(res);
})
//方案二:
// const res = await axios.get("http://127.0.0.1:8002/categroy/findCategroy?parentId=0")
}
开闭原则、单一职责
axios请求最好不要直接和组件产生关联。解耦。
后期更好维护。请求独立出来。组件不直接和axios有关系。那一天需要优化修改请求,我们可以不用修改组件的代码
十一、路由跳转的参数传递
路由跳转
在React中路由跳转有两种方式
- Link组件实现超链接跳转
- useNavigate这个hook函数来实现跳转
jsx
<Link to="/home/productadd"></Link>
基于hook函数来跳转
jsx
import {useNavigate} from "react-router-dom"
//navigate这个名字可以自己定义
const navigate = useNavigate()
const goto = ()=>{
navigate("/home/productadd")
}
const goto = ()=>{
navigate("/home/productadd".{replace:true})
}
默认传递一个路径代表push
跳转,在浏览器记录历史路径。
如果你传递参数,提供了replace:true,替换浏览器地址。不会报错历史记录
路由参数传递
方案一:字符串拼接的方案
组件一:
jsx
navigate(`/home/productupdate?id=${id}`)
组件二:
jsx
import React, { useEffect } from 'react'
import {useLocation,useSearchParams} from "react-router-dom"
export default function ProductUpdate() {
//路由传递过来整个对象
const location = useLocation()
const [searchParams,setSearchParams] = useSearchParams()
useEffect(()=>{
console.log("路由参数为:",location);
console.log("路由参数为:",searchParams.get("id"));
console.log("路由参数为:",searchParams.get("name"));
},[])
return (
<div>ProductUpdate</div>
)
}
如果字符串传递参数,建议使用useSearchParams
来实现页面参数获取。
可以直接根据get函数获取指定key的值
方案二:state对象传递参数
组件一:
jsx
navigate(`/home/productupdate`,{
state:{id:1,name:"xiaowang"
}})
组件一采用对象的方式来传递参数,属性默认用state
组件二:
jsx
export default function ProductUpdate() {
//路由传递过来整个对象
const location = useLocation()
useEffect(()=>{
console.log("路由参数为:",location);
},[])
return (
<div>ProductUpdate</div>
)
}
采用对象的方式来传递,接受参数useLocation这个hook来处理。
方案三:动态路由路径的方式
参数是路由的一部分。将这部分内容作为动态的数据进行传递
修改路由映射路径
jsx
<Route path="productupdate/:pid" element={<ProductUpdate></ProductUpdate>}></Route>
/:pid
代表路由动态的数据。路由跳转的必须要有这个数据。
组件一:
jsx
navigate(`/home/productupdate/${id}`)
组件二:
jsx
import React, { useEffect } from 'react'
import {useParams} from "react-router-dom"
export default function ProductUpdate() {
//路由传递过来整个对象
const location = useLocation()
const params = useParams()
useEffect(()=>{
console.log("路由参数为:",params);
},[])
return (
<div>ProductUpdate</div>
)
}
十二、动态路由
路由设计的时候,目前主要有以下几种模式
- 编程式路由:React的路由配置默认放在App.jsx组件硬编码写入。不好进行动态删减
- 配置式路由:比如Vue中路由,将路由单独提取出来放在一个文件中。页面上router-view来进行渲染。
- 约定式路由:不用手动配置路由,文件系统就是路由映射关系。
实现动态路由。需要将路由提取出来。动态渲染到页面上。
这个过程中实现对路由筛选、对比、路由限制等等
十三、表单的回显
基于Antd的表单来实现回显
js
const [user, setUser] = useState({username:"xiaowang",password:"123"})
<Form
name="normal_login"
className="login-form"
initialValues={{
remember: true,
}}
onFinish={onFinish}
>
<Form.Item
name="username"
rules={[
{
required: true,
message: 'Please input your Username!',
},
]}
>
>
要回显的数据,Form表单元素
(1)Form表单绑定
jsx
const [form] = Form.useForm()
<Form
name="normal_login"
className="login-form"
initialValues={{
remember: true,
}}
onFinish={onFinish}
form={form}
>
<Form></Form>
通过useForm获取form对象,这个对象要和Form表单绑定在一起。antd才能知道你form对象填充数据应该针对页面上哪个Form标签
(2)监听初始化数据并渲染
js
const [user, setUser] = useState({username:"xiaowang3333",password:"123"})
const [form] = Form.useForm()
useEffect(()=>{
// 将user交给form表单对象自己进行回显
form.setFieldsValue(user)
},[user])
form.setFieldsValue这个api目的就是将你提供好的对象,仍给页面绑定form对象Form组件。
根据username属性和Form.item的name属性进行对比。名字一样回显出来
十四、可视化渲染
React中使用echarts图表
(1)下载依赖
js
npm i echartsnpm i echarts-for-react //这个包需要依赖echars
(2)引入依赖
js
import ReactEcharts from "echarts-for-react"
下载的是封装好的组件,这个组件底层默认进行容器初始化。
你只需要提供对应options数据就可以
(3)使用组件
js
import React, { useState } from 'react'
import ReactEcharts from "echarts-for-react"
export default function Salary() {
const getOption = ()=>{
return {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
};
}
return (
<div>
<ReactEcharts
option={getOption()}
style={{height:"350px",width:"50%"}}
className="react_for_echarts"
></ReactEcharts>
</div>
)
}
十五、Redux状态机
全局状态管理,在React官方并没有提供方案。
需要借助于第三方的全局状态管理方案:Redux
(1)Redux概念
第三方状态管理工具,不是React这个框架推出的。也就意味着,Redux这个全局状态管理工具,适合于很多框架。
对于项目开发来说,全局状态管理让组件通信更加方便
Redux的核心运行流程
流程:
- 定义好仓库Store,在React组件中获取数据
- 组件中修改仓库的数据,仓库数据更新过后,组件也能同步进行更新
Store:代表仓库对象,用于存储全局状态
Action:通知对象,组件中发起action通知,让仓库进行数据更新
Reducer:修改数据的唯一方案采用reducer来实现
(2)redux环境搭建
下载依赖
js
yarn add reduxnpm i redux
配置redux代码
src/redux/index.js在这个文件中写入redux的代码。并实现对仓库定义
js
import {legacy_createStore as createStore} from "redux"
/**
* createStore创建一个仓库对象。
* createStore参数是一个函数,这个函数可以对仓库进行初始化
* {state:{},mutations:{}}
*/
const store = createStore(function(state={count:0}){
return state
})
项目启动的时候,让仓库加载,在项目src/index.js引入仓库代码
jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import "./assets/styles/index.css"
import "./redux"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App></App>
);
启动React项目的时候,可以加载redux仓库。仓库创建并初始化数据。
(3)获取仓库数据
store仓库提供了一个api可以获取数据store.getState()
jsx
import {legacy_createStore as createStore} from "redux"
/**
* createStore创建一个仓库对象。
* createStore参数是一个函数,这个函数可以对仓库进行初始化
* {state:{},mutations:{}}
*/
const store = createStore(function(state={count:0}){
return state
})
//模拟组件获取仓库数据,这个代码应该放在React中获取
console.log(store.getState());
(4)修改仓库数据
修改仓库的数据,流程会更加复杂一点。
- 必须提供ation通知对象
- 提供reducer函数来进行修改(修改仓库数据唯一途径利用reducer来进行修改)
Vue仓库
js
const store = createStore(reducer)
//redcuer负责初始化仓库数据,以及更新仓库数据
function reducer(state={count:0},action){
switch(action.type){
case "increment":
state.count += action.payload
return state
case "decrement":
state.count -= action.payload
return state
//第一次初始化,需要进入default
default:
return state
}
}
//设计通知对象,包含如何修改仓库数据。修改仓库的值
//type属性是必须的。其他属性可自定义
const action1 = {
type:"increment",
payload:10
}
const action2 = {
type:"decrement",
payload:5
}
//开始进行仓库数据修改
//通过仓库提供的api,调用dispatch派发一个通知对象给reducer实现数据更新
store.dispatch(action1)
store.dispatch(action2)
console.log(store.getState())
学习路径:
- 先把redux的核心概念学懂
- 优化redux仓库的设计。
- 在组件中引入redux来实现状态管理
(5)reducer拆分
目前在项目开发过程中,定义了一个reducer。在里面可以设计很多个case来进行数据的修改
但是如果state的数据比较多,case也会设计很多个,维护起来并不方便
js
function reducer(state={count:0},action){
switch(action.type){
case "increment":
state.count += action.payload
return state
case "decrement":
state.count -= action.payload
return state
case "updateUsername":
case "updateUserId":
default:
//init
return state
}
}
所有业务的case放在一起,不好维护代码。需要进行拆分。每一个数据都可以拆分为独立reducer,分开管理
创建两个reducer文件
redux/reducers/userReducer.js
js
export default function userReducer(state={user:{id:1,name:"xiaowang"}},action){
switch(action.type){
case "updateUsername":
state.user.name = action.payload;
return state
case "initUser":
state.user = action.payload
return state
default:
return state
}
}
redux/reducers/countReducer.js
js
/**
* 针对仓库中count来设计的reducer
* @param {} state
* @param {*} action
* @returns
*/
export default function reducer(state={count:0},action){
switch(action.type){
case "increment":
state.count += action.payload
return state
case "decrement":
state.count -= action.payload
return state
default:
return state
}
}
需要合并reducer
在redux/reducers/index.js
js
import {combineReducers} from "redux"
import countReducer from "./countReducer"
import userReducer from "./userReducer"
//redux提供的一个工具,可以合并多个reducer
//需要通过命名来区分哪个reducer
export default combineReducers({
countRD:countReducer,
userRD:userReducer
})
合并的过程中,我们会将名字作为命名空间.
在redux/index.js文件中,引入合并后reducer
js
import {legacy_createStore as createStore} from "redux"
import bigReducer from "./reducers" //默认找index.js
/**
* createStore创建一个仓库对象。
* createStore参数是一个函数,这个函数可以对仓库进行初始化
* {state:{},mutations:{}}
*/
const store = createStore(bigReducer)
修改或者输出
js
const action = {
type:"increment",
payload:20
}
store.dispatch(action)
console.log(store.getState().countRD); //获取仓库数据,多了一个命名空间名字
(6)action的拆分合并
action是对象,必须提供type属性,其他属性你可以自定义.
在redux文件夹下面创建actions/countAction.js/userAction.js
countAction.js
js
export const incrementAction = {
type:"increment",
payload:10
}
export const decrementAction = {
type:"decrement",
payload:5
}
userAction.js
js
export const initUserAction = {
type:"initUser",
payload:{id:2,name:"xiaowu"}
}
export const updateUserAction = {
type:"updateUsername",
payload:"xiaofeifei"
}
在以后组件中需要使用,直接引入
js
import {initUserAction,updateUserAction} from "../userAction"
store.dispatch(initUserAction)
(7)actionCreator优化(action)
actionCreator是专门用于创建action对象的一个模块
产生的action内部的数据,可以由actionCreator来定.
jsx
// export const incrementAction = {
// type:"increment",
// payload:10
// }
export const incrementAC = (value)=>{
return {
type:"increment",
payload:value
}
}
// export const decrementAction = {
// type:"decrement",
// payload:5
// }
export const decrementAC = (value)=>{
return {
type:"decrement",
payload:value
}
}
actionCreator(action创建器)就是将产生action的函数,你优先调用这个函数得到action对象.
dispatch来使用这个action对象.
十六、函数组件使用redux
redux的环境已经搭建完成,仓库里面已经存放了 count\user的数据.页面中可以直接用.
将redux和react结合起来使用.redux来管理页面中全局状态
(1)下载依赖
jsx
npm i react-redux
(2)暴露仓库
jsx
import {legacy_createStore as createStore} from "redux"
import bigReducer from "./reducers"
/**
* createStore创建一个仓库对象。
* createStore参数是一个函数,这个函数可以对仓库进行初始化
* {state:{},mutations:{}}
*/
const store = createStore(bigReducer)
export default store
将store仓库暴露出去,在组件中使用这个仓库
(3)组件中引入仓库
在src/index.js
jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import "./assets/styles/index.css"
import { Provider } from "react-redux"
import store from "./redux"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App></App>
</Provider>
);
通过react-redux
这个包引入Provider组件.可以实现给Provider注入store,以后被Provider组件包裹的所有子组件,默认可以使用store仓库
避免了你们在页面中要用仓库,得自己手动import store这个步骤
(4)组件中获取仓库数据
需要用到react-redux
提供一个hook函数
jsx
import React, { useEffect } from 'react'
import {useSelector} from "react-redux"
export default function Register() {
//
const storeData = useSelector(state=>{
return state
})
useEffect(()=>{
console.log(storeData);
},[])
return (
<div>Register</div>
)
}
组件修改仓库得数据
jsx
import React, { useEffect } from 'react'
import {useSelector,useDispatch} from "react-redux"
import {incrementAC,decrementAC} from "../redux/actions/countAction"
export default function Register() {
//state是仓库完整数据
const storeData = useSelector(state=>{
return state.countRD
})
const dispatch = useDispatch()
useEffect(()=>{
console.log(storeData);
},[])
const changeCount = (value)=>{
dispatch(incrementAC(value))
}
return (
<div>
<h2>Register</h2>
<p>{storeData.count}</p>
<button onClick={()=>changeCount(11)}>+</button>
<button>-</button>
</div>
)
}
通过useDispatch
获取dispatch函数.来实现派发通知
reducer 中代码必须要更新
jsx
/**
* 针对仓库中count来设计的reducer
* @param {} state
* @param {*} action
* @returns
*/
import {INCREMENT,DECREMENT} from "../constanst"
export default function reducer(state={count:0},action){
console.log(action);
switch(action.type){
case INCREMENT:
state.count += action.payload
//React一定检测state这个对象地址是否由变化
return {...state}
case DECREMENT:
state.count -= action.payload
return {...state}
default:
return state
}
}
对仓库得数据进行修改,一定返回新得state.进行浅克隆.
React检测仓库中数据地址发生变化,通知页面更新
十七、自定义hooks函数
(1)常见的hook
总结目前学习到hooks函数
-
官方提供的hooks函数:
useState:组件内部状态
useEffect:副作用函数,模拟生命周期
useMemo:计算属性
useCallback:计算属性,返回的函数
useRef:获取DOM节点
-
第三方的插件提供hooks
useNavigate:定义路由跳转的函数
useParams:获取动态路由的参数
useLocation:获取路由传递对象
useSearchParams:可以用于获取路由拼接字符串参数
useDispatch:获取dispatch实现状态机数据修改
useSelector:获取redux状态机的数据
Form.useForm()
还可以自定义hook函数,自己设计hook函数来封装项目中一些业务
类似于Vue中自定义指令。
(2)自定义hook的要求
- 自定义hook函数的名字必须以use+名字设计
- 函数里面不能返回JSX模板,可以返回数据,也可以没有返回
封装一个hook函数来实现网络请求,将数据同步到redux中。
jsx
import { findAllUserApi } from "../apis/userApi"
import { useDispatch } from "react-redux"
import {initUserAC} from "../redux/actions/accountAction"
const useRequest = () => {
const dispatch = useDispatch()
//发送请求
const getUsers = async () => {
const res = await findAllUserApi()
//负责将得到数据更新到redux中
dispatch(initUserAC(res.data.data))
}
const deleteUser = (id) => {
}
return {getUsers,deleteUser}
}
export default useRequest
组件中使用
jsx
import useRequest from '../../../hooks/useRequest'
const {getUsers} = useRequest()
const storeData = useSelector(state=>{
return state.accountRD
})
useEffect(() => {
// fetchData()
getUsers()
console.log(storeData);
},[])
组件中使用自定义hook来进行请求发送,并将数据保存到redux。
组件中直接useSelector来获取状态机的数据。
以后文件导入导出、地图等等都可以采用自定义hook来封装