React全家桶项目搭建

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

目前在公司里面开发,基本上主流都采用函数组件开发。配合类组件来一起设计。

函数组件和类组件区别:

  1. 类组件基于面向对象来进行开发。函数组件基于函数式编程来进行开发。函数组件更简单,符合前端常见开发模式。
  2. 函数组件中没有this指向的问题。类组件严格判断this的指向。函数组件开发过程更加方便
  3. 类组件有完整生命周期、有完整组件状态。但是函数组件没有(生命周期、内部状态)

三、下载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中开发中,提供两种组件

  1. 基于面向对象类组件。完整的生命周期、组件内部状态。缺点:基于面向对象开发,上手难度会大一些。有继承,组件的关联性会更大一些。
  2. 基于函数式编程,开发简单,易于维护扩展。缺点:没有组件内部状态、也没有生命周期函数

如何让函数组件达到类组件的效果。开发直接使用函数组件就可以了

在React16.8这个版本开始,提出了hooks编程。官方设计了一堆函数,用于辅助我们的组件开发。

这种函数配合函数组件开发,称为hooks函数

有了这个特性过后,完全可以用函数组件来进行业务开发了。

特点:hooks函数只能在函数组件中使用,类组件无法使用hooks编程

官方提供:React底层设计的hook函数,可以直接项目中导入使用

第三方:下载第三方包,引入提供hook函数

自定义:后续我们可以根据自己业务,自定义hook满足业务开发

(1)useState函数

useState是官方提供的一个hook函数,主要用于实现组件内部状态管理。

jsx 复制代码
import {useState} from "react"
const [username,setUsername]= useState("xiaowang")

特点:

  1. 可以直接useState参数里提供对应值。
  2. 必须声明一个变量类接受这个参数。
  3. 还必须提供一个修改变量函数,命名可以自己定。官方要求 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参数

  1. 第一个参数:回调函数用于执行计算过程,一定要返回结果
  2. 第二个参数:指定监控哪些属性,只要这些属性发生变化,计算属性就会执行

(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中路由跳转有两种方式

  1. Link组件实现超链接跳转
  2. 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>
  )
}

十二、动态路由

路由设计的时候,目前主要有以下几种模式

  1. 编程式路由:React的路由配置默认放在App.jsx组件硬编码写入。不好进行动态删减
  2. 配置式路由:比如Vue中路由,将路由单独提取出来放在一个文件中。页面上router-view来进行渲染。
  3. 约定式路由:不用手动配置路由,文件系统就是路由映射关系。

实现动态路由。需要将路由提取出来。动态渲染到页面上。

这个过程中实现对路由筛选、对比、路由限制等等

十三、表单的回显

基于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的核心运行流程

流程:

  1. 定义好仓库Store,在React组件中获取数据
  2. 组件中修改仓库的数据,仓库数据更新过后,组件也能同步进行更新

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)修改仓库数据

修改仓库的数据,流程会更加复杂一点。

  1. 必须提供ation通知对象
  2. 提供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())

学习路径:

  1. 先把redux的核心概念学懂
  2. 优化redux仓库的设计。
  3. 在组件中引入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函数

  1. 官方提供的hooks函数:

    useState:组件内部状态

    useEffect:副作用函数,模拟生命周期

    useMemo:计算属性

    useCallback:计算属性,返回的函数

    useRef:获取DOM节点

  2. 第三方的插件提供hooks

    useNavigate:定义路由跳转的函数

    useParams:获取动态路由的参数

    useLocation:获取路由传递对象

    useSearchParams:可以用于获取路由拼接字符串参数

    useDispatch:获取dispatch实现状态机数据修改

    useSelector:获取redux状态机的数据

    Form.useForm()

还可以自定义hook函数,自己设计hook函数来封装项目中一些业务

类似于Vue中自定义指令。

(2)自定义hook的要求

  1. 自定义hook函数的名字必须以use+名字设计
  2. 函数里面不能返回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来封装

相关推荐
zqx_719 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己36 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai2 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端