likes
comments
collection
share

React全家桶项目实战(三)

作者站长头像
站长
· 阅读数 60

React全家桶项目开发

一、全家桶技术

React+函数组件+hooks+ReactRouter+ant design+ redux+echarts+其他技术栈

项目搭建

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

yarn add antd
npm i antd

目前本文档截至使用的版本5.9.0

(2)组件中使用antd

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中,大部分的组件都基于函数组件来开发的。

三、配置项目启动环境

启动项目的命令

"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,或者一些环境变量等等

yarn add @craco/craco

下载完成后,我们需要在启动环境中替换为craco

(2)找到package.json
"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "craco eject"
  },
(3)在项目根目录下面创建文件

在项目根目录下面创建文件craco.config.js

这个文件名字不能写错。默认采用craco来加载项目,读取这个文件。

可以在这个文件中配置webpack相关的内容

module.exports = {

}
(4)配置less环境

antd底层默认采用less来设计的代码

我们自己引入样式也有less来设计

yarn add craco-less

下载完成后,想要让craco启动项目能加载less环境。

打开craco.config.js文件。配置less的加载规则

const CracoLessPlugin = require("craco-less")
module.exports = {
    plugins: [
        { plugin: CracoLessPlugin }
    ]
}
(5)组件中引入less

在assets/styles/antdcomp.module.less文件中

.container{
    width:100px,
        height:100px,
            span{
                color:red
            }
}

引入到组件中

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这个根组件中,配置主题色

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子组件中,配置主题色

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)基本概念

类组件开发特点:

  1. 基于面向对象开发模式,需要一定面向对象基础。
  2. 类组件包含了完整组件内部状态、外部状态、生命周期函数。
  3. 类组件相对来说复杂一点,包含this指向问题。尤其是事件绑定。

函数组件开发特点:

  1. 采用函数式编程,开发难度更低。符合JavaScript开发模式
  2. 函数组件默认没有组件内部状态,我们需要引入hooks解决组件内部状态
  3. 函数组件中没有生命周期函数,我们也需要引入hooks来解决生命周期函数问题
  4. 函数组件中,无需再使用this。开发难度会更低

hooks:实际上封装好的一系列函数,我们可以再函数组件中引入这些hook函数。辅助我们开发。

函数组件基础结构

import React from 'react'

const Login = () => {
    return (
        <div>Login</div>
    )
}
export default Login

用箭头函数来设计,并返回当前这个函数。页面加载你这个函数返回JSX模板。

说明:暴露出去的函数。外部直接调用。并不会实例化这个函数

目前公司中React项目开发,80%的项目都会采用函数组件来设计。采用类组件配合函数组件一起使用

(2)函数组件通信

跟类组件不一样的地方,在于子组件接受外部参数的时候。通过函数组件参数来接受props

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对象。你可以解构出来。

在函数组件中定义函数,推荐用箭头函数

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,用于定义组件内部状态

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来定义组件内部状态特点:

  1. 每个数据都分别采用useState来进行管理。所有数据分类处理
  2. 每个变量定义好了,都会有一个唯一的修改函数,对应修改过程
  3. 如果遇到引用类型数据,修改的时候检测修改对象地址是否新的。否则无法更新
  4. 修改函数,函数名字是可以用户自定义的。比如changecount,但是官方要求我们最好按照set+驼峰命名的方式来设计。可读性和维护性更高
  5. 修改函数,也是异步更新的,无法马上拿到修改过后结果。如果你要修改后,拿到结果。目前还无法解决

(4)useMemo函数

useMemo是React官方提供出的计算属性函数。可以用于页面数据进行数据处理

语法一:

const filterDate = useMemo(()=>{
    return 计算结果
},[监控属性])
const filterDate = useMemo(()=>{
    return 计算结果
},[监控属性1,监控属性2])

指定监控页面上某个属性,当这个值发生允许计算属性。

语法二:

const filterDate = useMemo(()=>{
    return 计算结果
})

监控页面上所有的数据,只有数据变化,计算属性都要执行一次。

这种写法比较消耗性能。页面很多数据可能都更新。

(5)useCallback函数

这个函数也是计算属性。

语法和useMemo一样。

但是返回值不是结果,而是一个函数,使用的需要调用一下

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指向的节点。

用这种方式更加合理一些。

老方法:

 <input type="text" ref={element=>Register.inputElement = element} />

ref接受一个函数,接受到结果需要用组件名字挂载。

新方法

import {useRef} from "react"


const inputRef = useRef()

//获取文本框值
const getValue = ()=>{
   const value =  inputRef.current.value
}

<input ref={inputRef}>

目前推荐大家使用useRef来获取节点,并得到结果

(7)useEffect副作用

在函数组件中,并没有生命周期函数。

所以React官方提出了useEfffect的函数,副作用。可以模拟生命周期函数

基于目前useEffect的特性,模拟componentDidMount

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

/**
     * setCount修改count的值,如何获取修改结果
     * 类似于Vue watch
     */
useEffect(()=>{
    console.log("componentDidUpdate");
    console.log(count);
},[count])

语法二:

useEffect(() => {
    console.log("componentDidUpdate");
})

表示页面上只要数据发生变化,都要执行一次这个函数。包括props

模拟componentWillUnmount

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…

框架路由开发模式,主要由一下几种:

  1. 编程式路由:需要自己在页面中写代码进行路由编程,包括映射。包括路由引入
  2. 配置式路由:只需要将路由映射写到配置文件中,剩下的就是自动渲染 Vuejs
  3. 约定式路由:无需配置路由,你的项目文件系统就是路由。Login.jsx\Register.jsx Login

Vuejs配置式路由

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)搭建路由

下载对应路由包

yarn add react-router-dom
npm i react-router-dom

路由想要在App.jsx 组件搭建。

打开App.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)路由映射规则

默认索引和重定向

<Routes>
	<Route index element={<Login></Login>}></Route>
</Routes>

默认进来匹配的组件就是Login组件。默认索引。index只能用一次

重定向规则

import {Navigate} from "react-router-dom"

<Routes>
	<Route path="/" element={<Navigate to="/login"></Navigate>}></Route>
</Routes>

我们还可以用重定向解决404的问题

完整页面

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路由实现嵌套

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下面配置路由渲染出口

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组件就是路由渲染出口。

搭建好二级路由后,整体的布局效果如下:

React全家桶项目实战(三)

七、搭建服务器

服务器端代码采用Nodejs+mongodb设计。

每个同学自己把mango的数据文件,导入到本地数据库。

将后端代码发送给我大家,本地启动。

sell-server的服务器代码。

(1)创建数据库

在本地mongodb数据库里面导入我们需要的数据

创建一个数据库WMallTesting

选中这个数据库,右键运行脚本

(2)启动项目

需要你本地电脑安装nodemon插件

npm run dev

启动项目需要看到下面信息才代表成功

[nodemon] starting `babel-node ./app.js`
you server is running at 8002
connected WMallTesting to database mongodb://127.0.0.1:27017/WMallTesting

(3)直接测试接口

在浏览器输入地址

http://127.0.0.1:8002/goods/findGoods

能够得到数据,证明服务器环境成功

后端跨域已经打开。token认证已经关闭。等我们完成登录后,在加上身份认证配置

(4)接口文档

地址:www.showdoc.com.cn/12791639356…

密码:xcb

八、前端请求封装

目前项目开发过程中,默认采用axios来进行请求处理。

(1)下载axios

npm i axios
或者
yarn add axios

(2)封装axios请求工具

在utils\axiosUtils.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文件中,设计如下代码

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

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的表格自带前端分页,如果你要修改为后端分页,请关闭默认分页

<Table
    bordered
    rowKey="_id"
    columns={columns}
    dataSource={data}
    loading={false}
    pagination={false}
    />

当pagination=false就代表关闭默认分页。自己在单独引入pagination组件来实现后端分页。

如果你就需要前端分页,我们需要配置前端分页的一些参数

比如:每页显示多少条,是否显示下拉框、是否可以允许输入页码跳转等等

<Table
    bordered
    rowKey="_id"
    columns={columns}
    dataSource={data}
    loading={false}
    pagination=	{{
        defaultCurrent:1,
        defaultPageSize:6,
        showQuickJumper:true,
        showSizeChanger:true,
        pageSizeOptions:[6,12,18]}}
    />

分页的配置

商品页面数据渲染效果

React全家桶项目实战(三)

九、添加功能

(1) 复制antd的代码

antd提供了完整表单案列。

我们可以直接将案列拷贝我们项目中运行

React全家桶项目实战(三)

需要大家自己改造表单内容,显示文本。删除不需要表单组件

(2)修改提交功能

antd表单支持一键获取所有表单元素的值

前提是每个表单组件都设置name属性

<Form.Item label="商品名字" name="name">
    <Input placeholder="请输入商品名字" />
</Form.Item>

给Form组件添加一个onFinish事件

<Form
    labelCol={{
        span: 6,
    }}
    wrapperCol={{
        span: 18,
    }}
    layout="horizontal"
    style={{
        maxWidth: 600,
    }}
    onFinish={onFinish}
    >

还需要将按钮修改为提交按钮,才能触发onFinish

<Form.Item>
    <Button type="primary" htmlType="submit">
        添加
    </Button>
</Form.Item>

(3)验证规则添加

默认引入模板,没有增加验证规则。

我们可以自己添加rules

<Form.Item
label="商品名字"
name="name"
rules={[
       {
           required: true,
           message: '商品名字不能为空',
       },
       {
           min:3,
           message: '长度最少3个',
       },
       {
           max:10,
           message: '长度最多10个',
       },
       ]}
>

基础的验证,官方提供规则

自定义正则表单式

{
    pattern:/^[a-zA-Z0-9]{5}$/,
    message:"输入内容不合法"
}

效果如下:

React全家桶项目实战(三)

十、路由跳转

在React中V6路由进行跳转主要有两种方案:

  1. Link组件的方式:转化为a标签进行跳转。但是不能直接a标签Link默认已经阻止了a标签默认行为。

    import {Link} from "react-router-dom"
    
  2. 通过hooks函数来进行跳转

    import {useNavigate} from "react-router-dom"
    const navigate = useNavigate()
    
    navigate(路由地址)
    navigate(路由地址,{replace:true})
    

    replace:true代表跳转的时候,不记录历史。默认无法返回到这个页面

十二、路由参数传递

回顾Vue中路由参数传递

this.$router.push("/home?id=1")
this.$router.push({pathname:"/home",query:{id:1}})

在React中V6路由我们要实现路由跳转

(1)方案一

组件一:通过地址栏来传递参数

navigate(`/home/user?id=${values._id}&name=xiaowang`)

组件二获取参数

import React, { useEffect } from 'react'
import {useLocation,useSearchParams} from "react-router-dom"

export default function User() {
  const location = useLocation()
  const [searchParams,setSearchParams] = useSearchParams()
  useEffect(()=>{
    console.log(location);
    console.log(searchParams.get("id"));
    console.log(searchParams.get("name"));
  },[])
  return (
    <div>User</div>
  )
}

如果你的参数在URL地址里面,获取指定的属性,useSearchParams这个hook函数

(2)方案二

组件一:

navigate(`/home/user`,{
    state:{
        id:1,name:"xiaowang"
    }
})

传递参数的时候,采用对象的形式给两外一个组件。

地址栏里面不会显示出参数

组件二:

import {useLocation} from "react-router-dom"
const location = useLocation()

console.log(location.state) //{id:1,name:"xiaowang"}

(3)方案三

动态路由参数,参数是URL的一部分。

组件一:

navigate(`/home/user/${values._id}`)
navigate(`/home/user/200`)

你的参数是URL地址的一部分。不是问号来传递直接/来传递

组件二:

import {useParams} from "react-router-dom"
const params = useParams()
console.log(params)//{myid:200}

还需要再路由映射的时候。添加动态的部分

<Route path="/home/user/:myid">
<Route path="/home/user/:myid/:name">

:myid代表动态的参数。会用myid来作为占位。获取对应值

十四、路由懒加载

Vue中路由懒加载:路由映射的时候,调用component对应函数。import代码才会执行

打包的时候,不会执行import加载

import Login from "./Login.vue"
[
    {path:"/home",component:()=>import("./Login.vue")}
]

import静态加载,项目打包的时候,只要看到import放在代码前面,优先加载import代码

React中官网给出了路由懒加载的方案

(1)再路由器外面进行Suspense包裹

{/* 路由懒加载开启后,路由加载比较慢的时候,开启一个动画 */}
<React.Suspense fallback={<Loading></Loading>}>
    {/* 路由器 */}
    <BrowserRouter>
        {/* 路由映射列表 */}
        <Routes></Routes>
    </BrowserRouter>
</React.Suspense>

fallback当加载比较慢的时候。默认提供页面动画。

(2)lazy来进行组件加载

默认import加载的组件,需要替换为lazy加载。这样才能动态加载

const Register = lazy(()=>import("./views/Register"))

路由映射

<Route path="/register" element={<Register/>}>

十五、路径别名

再React项目中,我们要实现组件引入、外部请求Api的引入。需要用相对路径

import User from "../user.jsx"
import style from "../../assets/style/xxxx.module.css"

必须根据当前文件所在目录。寻找其他目录/相对路径写起来有点麻烦。

设置路径别名。@ 表示指定的路径

需要在craco.config.js文件中进行配置

const CracoLessPlugin = require("craco-less")
const path = require("path")
module.exports = {
    webpack:{
        alias:{
            //__dirname是全局变量.代表获取当前这个文件的绝对路径
            //__dirname=c/desktop/web34/react-mall  + src
            "@":path.resolve(__dirname,"src"),
            "@comp":path.resolve(__dirname,"src/components")
        }
    },
    plugins: [
        { plugin: CracoLessPlugin }
    ]
}

组件中使用

import User from "@/views/User.jsx"

函数定义

function throttled1(fn, delay = 500) {
    let oldtime = Date.now()
    return function (...args) {
        let newtime = Date.now()
        if (newtime - oldtime >= delay) {
            fn.apply(null,args)
            oldtime = Date.now()
        }
    }
}
window.onscroll = throttled1(function(){},1000)

以后再测试局部代码性能的时候,


function play(){
    //
}
let oldTime = Date.now()
play()
let newTime = Date.now()

图片懒加载:图片不再可视区域。没必要马上大请求到后端拿这个图片

<img src="" dataSrc="http://web.woniulab.com/1.jpg"/>

当我页面滚动到一定位置,将data-src设置给src。

十六、动态菜单

动态菜单:根据用户登录的信息,来决定前端如何进行渲染。

十七、动态路由

目前前端的路由采用编程式路由。

直接再App.jsx组件中写死了所有路由。不管用什么身份来登录。

大家访问路由都是一样的。

根据不同身份,控制不同的用户访问

路由需要动态加载出来的。而不是静态内容

(1)、将路由提取出来

将下面路由提取出来。动态生成

<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={<Main></Main>}></Route>
        <Route path="user" element={<User></User>}></Route>
        <Route path="role" element={<Role></Role>}></Route>
        <Route path="shop" element={<Shop></Shop>}></Route>
        <Route path="list" element={<ProductList></ProductList>}></Route>
        <Route path="category" element={<ProductCategory></ProductCategory>}></Route>
        <Route path="salary" element={<Salary></Salary>}></Route>
        <Route path="sale" element={<Sale></Sale>}></Route>
        <Route path="productadd" element={<ProductAdd></ProductAdd>}></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>

需要再src/router/index.js 将路由提取出来

import Login from "../views/Login"
import Register from "../views/Register"
import NotFind from "../views/NotFind"
import Home from "../views/Home"
import User from "../views/subs/User"
import Role from "../views/subs/Role"
import Shop from "../views/subs/Shop"

const routerList = [
    {
        path: "/login",
        element: <Login></Login>
    },
    {
        path: "/register",
        element: <Register></Register>
    },
    {
        path: "/home",
        element: <Home></Home>,
        children: [
            { path: "user", element: <User></User> },
            { path: "role", element: <Role></Role> },
            { path: "shop", element: <Shop></Shop> }
        ]
    },
    {
        path: "*",
        element: <NotFind></NotFind>
    }
]

export default routerList

(2)封装一个组件

动态路由组件,src/components/DynamicRouter.jsx

import React from 'react'
import {useRoutes} from "react-router-dom"
import routerList from '../router'

/**
 * 高阶组件
 * @returns 
 */
function DynamicRouter() {
    //先过滤筛选得到当前用户访问路由.useRoutes加载
    const temp = []
    const element = useRoutes(routerList)
    return element
}
// function useRoutes(array,news = []){
//     for (const item of object) {
//         if(!item.children){
//             news.push(<Route path={} element={}></Route>)
//         }else{

//         }
//     }
// }

export default DynamicRouter

(3)App.jsx使用动态路由

 <BrowserRouter>
          <DynamicRouter></DynamicRouter>
</BrowserRouter>

十八、表单回显

(1)创建一个表单实例

import {Form} from "antd"

const [form] = Form.useForm()

(2)再表单组件上面绑定实例

<Form
    labelCol={{
        span: 6,
    }}
    wrapperCol={{
        span: 18,
    }}
    layout="horizontal"
    style={{
        maxWidth: 600,
    }}
    form={form}
    onFinish={onFinish}
    onFinishFailed={onFinishFailed}
>

(3)调用form的函数赋值

useEffect(()=>{
        form.setFieldsValue({...detail,type:["数码产品","手机"]})
},[detail])

十九、路由守卫

在昨天基础上,添加路由独享守卫

router/RouterAuth.js文件

import React from 'react'
import {Navigate} from "react-router-dom"

/**
 * 高阶组件:可以接受组件作为参数,返回一个新的组件
 * @returns 
 */
export default function RouterAuth({ children }) {
    const token = localStorage.token
    if(token){
        return <>{children}</>
    }else{
        return <Navigate to="/login"></Navigate>
    }
}

在router/index.js文件中配置独享守卫

{
        path: "/home",
        element: <RouterAuth><Home></Home></RouterAuth> ,
        children: [
            { path: "user", element: <User></User> },
            { path: "role", element: <Role></Role> },
            { path: "shop", element: <Shop></Shop> },
            { path: "list", element: <ProductList></ProductList> },
            { path: "productadd", element: <ProductAdd></ProductAdd> },
            { path: "productupdate", element: <ProductUpdate></ProductUpdate> },
        ]
    },

二十、echarts图表

react中使用echarts,下载第三方的组件。封装好了基础代码。直接配置样式

(1)下载依赖

yarn add echarts-for-react 
yarn add echarts

echarts-for-react 组件封装好了基础代码。你只需要提供option就可以了

(2)项目中引入

import React, { useEffect, useState } from 'react'
import ReactEcharts from "echarts-for-react"

export default function Salary() {
  const [data, setData] = useState([])
  
  useEffect(() => {
    setTimeout(() => {
      setData([
        { value: 1048, name: 'Search Engine' },
        { value: 735, name: 'Direct' },
        { value: 580, name: 'Email' },
        { value: 484, name: 'Union Ads' },
        { value: 300, name: 'Video Ads' }
      ])
    }, 2000)
  }, [])
  const getOption = () => {
    return {
      title: {
        text: 'Referer of a Website',
        subtext: 'Fake Data',
        left: 'center'
      },
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: 'Access From',
          type: 'pie',
          radius: '50%',
          data: data,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    };
  }
  const getOption2 = () => {
    return {
      title: {
        text: 'Stacked Line'
      },
      tooltip: {
        trigger: 'axis'
      },
      legend: {
        data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
      },
      grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
      },
      toolbox: {
        feature: {
          saveAsImage: {}
        }
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          name: 'Email',
          type: 'line',
          stack: 'Total',
          data: [120, 132, 101, 134, 90, 230, 210]
        },
        {
          name: 'Union Ads',
          type: 'line',
          stack: 'Total',
          data: [220, 182, 191, 234, 290, 330, 310]
        },
        {
          name: 'Video Ads',
          type: 'line',
          stack: 'Total',
          data: [150, 232, 201, 154, 190, 330, 410]
        },
        {
          name: 'Direct',
          type: 'line',
          stack: 'Total',
          data: [320, 332, 301, 334, 390, 330, 320]
        },
        {
          name: 'Search Engine',
          type: 'line',
          stack: 'Total',
          data: [820, 932, 901, 934, 1290, 1330, 1320]
        }
      ]
    }
  }
  return (
    <React.Fragment>
      <ReactEcharts
        option={getOption()}
        style={{ width: "500px", "height": "400px", "boxShadow": "0px 0px 10px rgba(0,0,0,.3)" }}
        className="react_for_echarts"
      >

      </ReactEcharts>
      <ReactEcharts
        option={getOption2()}
        style={{ width: "500px", "height": "400px", "boxShadow": "0px 0px 10px rgba(0,0,0,.3)" }}
        className="react_for_echarts"
      >

      </ReactEcharts>
    </React.Fragment>
  )
}

作业:

  1. 每个一级分类有多少个二级分类。
  2. 每个分类下面有多少个商品(没有商品就不统计)

扩展:项目中引入地图。

<template>
	<div :msg="msg" @click="show">
        <p>
            {{msg}}
    	</p>
        <span v-if="bool">
    	</span>
        <a href="" v-show="bool"></a>
    </div>
</template>

data(){
return{
	msg:"xiaowang",
	bool:false
}
}

AST进行语法抽象.

const template = [
    {
        tagName:"div",
        attrs:[
            {name:"msg",value="xiaowang"}
        ],
        listeners:[
            {eventName:"click",arrow:show}
        ],
        children:[
            {
                tagName:"p"
            },
            {
                tagName:"span",
                state:false
            },
            {
                tagName:"a",
                attrs:[],
                style:{display:"none"}
            }
        ]
    }
]

将抽象出来的JS对象,转化为虚拟dom

(1)template标签

二十一、redux

(1)基本概念

React官网并没有提供状态机。需要我们自己引入第三方的状态机进行状态管理。

Redux是第一个第三方的状态管理工具,React中使用他来对全局状态进行管理。可以实现数据共享。

可以单独使用,也可以结合React来使用。

状态机包含的内容:

React全家桶项目实战(三)

总结:

  1. 定义好状态机。组件可以获取状态机数据。
  2. 修改状态机的数据,达到数据共享。

包含内容:

  1. 仓库对象:需要自己创建,state数据
  2. reducers:修改仓库数据的唯一方案。
  3. action:通知对象,页面发起一个请求,将这个通知对象传递reducers。让reducers按照你的通知对象来修改仓库

(2)开发步骤

单独研究redux的工作流程。redux熟悉了,才会将redux和React组件结合在一起

1)下载redux

yarn add redux
npm i redux

2)在src/redux/index.js

import { legacy_createStore as createStore} from 'redux'


//创建一个reducer
//参数接受一个state,代表仓库默认值
//reducer函数一定要返回state
function reducer(state={count:10}){
    return state
}

//创建仓库,只需要一个函数
const store = createStore(reducer)

//测试仓库中是否由数据
console.log(store.getState());

3)修改仓库数据

import { legacy_createStore as createStore} from 'redux'

const state1 = {
    count:10,
    user:{id:1}
}
//创建一个reducer
//参数接受一个state,代表仓库默认值
//reducer函数一定要返回state
function reducer(state=state1,action){
    switch(action.type){
        case "INCREMENT":
            state.count += action.payload
            return state
        case "DECREMENT":
            state.count -= action.payload
            return state
        default:
            return state
    }
}

//创建仓库,只需要一个函数
const store = createStore(reducer)

//测试仓库中是否由数据
//模拟页面去调用仓库数据
console.log("仓库数据",store.getState());

//创建action对象,这个通知对象,告诉reducer如何修改数据
//action是一个对象,必须要有type属性
//payload是用户自己定义,代表载荷(参数)
const action = {
    type:"INCREMENT",
    payload:10
}
const action2 = {
    type:"DECREMENT",
    payload:5
}

//修改仓库的数据
store.dispatch(action)
console.log(store.getState());
store.dispatch(action2)
console.log(store.getState());

总结:

action代表通知对象,本质就是一个普通对象,必须要有type属性,其他属性你自己定义。默认payload

修改仓库,dispatch派发action通知给reducer,reducer进行数据修改。reducer返回修改过后结果。

仓库才能获取最新结果。页面才能更新

(3)文件拆分

将所有action通知对象提取出去。将reducer也提取出去

src/actions/index.js

export const action = {
    type:"INCREMENT",
    payload:10
}
export const action2 = {
    type:"DECREMENT",
    payload:5
}

src/reducers/index.js

const state1 = {
    count:10,
    user:{id:1}
}
//创建一个reducer
//参数接受一个state,代表仓库默认值
//reducer函数一定要返回state
function reducer(state=state1,action){
    switch(action.type){
        case "INCREMENT":
            state.count += action.payload
            return state
        case "DECREMENT":
            state.count -= action.payload
            return state
        default:
            return state
    }
}

export default reducer

redux/index.js引入使用

import { legacy_createStore as createStore} from 'redux'
import reducer from './reducers';
import {action,action2} from "./actions"

//创建仓库,只需要一个函数
const store = createStore(reducer)

//修改仓库的数据
store.dispatch(action)
console.log(store.getState());
store.dispatch(action2)
console.log(store.getState());

(4)reducer的拆分

所有的业务都写在一个reducer中,case多了很难维护。

每个业务都是一个独立reducer,分文件管理

src/redux/reducers/UserReducer.js

const state1 = {
    user:{id:1}
}
function UserReducer(state=state1,action){
    switch(action.type){
        //修改user的case
        case "UPDATEUSER":
            state.user.id = action.payload
            return state
        default:
            return state
    }
}

export default UserReducer

src/redux/reducers/CountReducer.js

const state1 = {
    count:10
}
function CountReducer(state=state1,action){
    switch(action.type){
        //修改count的case
        case "INCREMENT":
            state.count += action.payload
            return state
        case "DECREMENT":
            state.count -= action.payload
            return state
        default:
            return state
    }
}

export default CountReducer

在CreateStore创建仓库的函数只能接受一个reducer。需要在将reducer合并起来

redux/reducers/index.js

import {combineReducers} from "redux"
import UserReducer from "./UserReducer"
import CountReducer from "./CountReducer"

export default combineReducers({
    UserStore:UserReducer,
    CountStore:CountReducer
})

redux/index.js文件中引入合并后reducer

import { legacy_createStore as createStore} from 'redux'
import reducer from './reducers';
import {action,action2,action3} from "./actions"

//创建仓库,只需要一个函数
const store = createStore(reducer)

store里面数据会分类,根据命名空间分类

{
    UserStore:{} //命名空间名字自己设置
    CountStore:{}
}

(5)action的拆分合并

redux/actions/CountAction.js

export const action = {
    type:"INCREMENT",
    payload:10
}
export const action2 = {
    type:"DECREMENT",
    payload:5
}

redux/actions/UserAction.js

export const action3 = {
    type: "UPDATEUSER",
    payload: 100
}

合并所有action

export {action,action2} from "./CountAction"
export {action3} from "./UserAction"

(6)actionCreator创建器

专门用于产生action对象。

实际上本质就是一个函数,返回action

页面中调用,调用函数传递参数

/**
 * ActionCreator
 * @param {} value 
 * @returns 
 */
export const action = (value) => {
    return {
        type: "INCREMENT",
        payload: value
    }
}

二十二、React中引入Redux

需要下载一个依赖包,React-redux,将React和Redux连接起来。你可以在项目中引入Redux管理组件状态

(1)下载依赖

npm i react-redux

等会使用这个包建立React组件和Redux的联系

(2)store仓库中进行暴露

import { legacy_createStore as createStore} from 'redux'
import reducer from './reducers';

//创建仓库,只需要一个函数
const store = createStore(reducer)


export default store

在React项目中引入store,直接使用store。也可以全局挂载

(3)在src/index.js引入仓库

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import "./index.css"
import store from "@/redux"
import { Provider } from "react-redux"


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>

);

被Provider组件包裹的所有子组件,以后默认都可以操作仓库

Vuejs

import store from ""
new Vue({
    store
})

(4)函数组件获取仓库

import React, { useEffect } from 'react'
import { useSelector, useDispatch } from "react-redux"

export default function Main() {
  /**
   * 获取仓库数据
   */
  const data = useSelector(state => {
    return state.CountStore
  })
  const dispatch = useDispatch()
  useEffect(() => {
    console.log(data);
  }, [])
  return (
    <div>
      <h3>Main</h3>
      <p>{data.count}</p>
    </div>
  )
}
Object.keys(obj).forEach(key=>{
    Object.defineProperty(obj,key,{
        get(){
            //
        },
        set(){
            //
        }
    })
})
obj.newProperty = "xiaowang"

import Vue from "vue"
//动态个对象新增属性
Vue.set(user,"newProperty","xiaowang")
this.$set()

export default {
    data(){
        return {
            msg:"xiaowang",
            user:{id:1},
            array:[1,2,3]
        }
    }
}

this.msg = xiaowang
this.user = {...this.user,newProperty:"xiaowang"}
//强制更新。
this.$forceUpdate()

this.array.push()

二十三、自定义hook函数

(1)常见的hook

官方的hook函数

useState:组件内部数据

useMemo:组件计算属性

useCallback:组件的计算属性。返回函数

useEffect:副作用函数

useRef:获取节点

第三方的:

useNaviate:路由跳转

useLocation:获取路由参数对象

useParams:动态路由参数获取

useParamsSearch:获取路径中字符串

useSelector:获取仓库的数据

useDispacth:获取dispatch函数,修改仓库的数据

const [form] = Form.useForm()

(2)自定义hook

自定义指令,你可以将某些业务封装起来。直接用指令就表示

自定义hook将部分JS逻辑封装起来。项目中直接引入

要求:

  1. 自定义hook,不会直接返回JSX模板。根据业务要求返回对应数据,或者结果
  2. 自定义hook必须以use开头来命名

(3)开发 步骤

在src/hooks/index.js

//专门用于发送异步请求
//直接useRequest发送请求,并将数据保存状态机\
import {findAllUser,deleteUserApi} from "@/apis/userApi"
import {useDispatch} from "react-redux"
import {initUsers} from "@/redux/actions"
const useRequest = () => {
    const dispatch =  useDispatch()
    const getUsers =async () => {
        const res = await findAllUser()
        dispatch(initUsers(res.data.data))
    }
    const deleteUser = () => {

    }
    return {getUsers,deleteUser}
}


export default useRequest

当前这个hook封装的目的就是获取结果。默认存放状态机里面

页面使用这个hook默认可以发送请求,并直接保存状态机

组件中

import useRequest from "@/hooks/useRequest"
export default function Main() {
  const dispatch = useDispatch()
  const {getUsers} = useRequest()
  
  const fetchData = ()=>{
      getUsers()
  }
}

页面上也要获取到请求结果,可以传递callback

 getUsers((value)=>{
     console.log(value)
     if(value.code){
        message.success()
     }
 })
转载自:https://juejin.cn/post/7281208218175569920
评论
请登录