React 基本使用

目录

1. 使用create-react-app快速搭建开发环境

create-react-app是一个快速 创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用

1.1 执行命令:

npx create-react-app react-basic

  1. npx Node.js工具命令,查找并执行后续的包命令
  2. create-react-app 核心包(固定写法),用于创建React项目
  3. react-basic React项目的名称(可以自定义)
    在这里插入图片描述

1.2 删除不需要的文件

  • src目录下只需要保留App.js 和 index.js文件就可以
    在这里插入图片描述

App.js:

function App() {
return (


this is App

);
}

export default App;


index.js

import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import App from ‘./App’;
 
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
< App />
);


2. JSX基础-高频场景

2.1 JSX中使用JS表达式

在JSX中可以通过 大括号语法{} 识别 JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等

注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中

1. 使用引号传递字符串:

function App() {
    
    
    return (
        <div className="App">
            {/*1. 使用引号传递字符串*/}
            { '使用引号传递字符串'}
        </div>
    );
}

export default App;

在这里插入图片描述


2. 使用JavaScript变量:

function App() {

    const count = 100;
    return (
        <div className="App">
            {/*使用JavaScript变量*/}
            {count}
        </div>
    );
}

export default App;

在这里插入图片描述


3. 函数调用和方法调用:

function App() {


    function getCount() {
        return 123;
    }

    return (
        <div className="App">
            {/*函数调用*/}
            {getCount()}
            <p/>
            {/*方法调用*/}
            {new Date().getDate()}
        </div>
    );
}

export default App;

在这里插入图片描述


4. 使用JavaScript对象:

function App() {



    return (
        <div className="App">
            {/*使用js对象*/}
           <div style={{color:"red"}}>
               this is div
           </div>
        </div>
    );
}

export default App;

在这里插入图片描述


2.2 JSX中实现列表渲染

语法:在JSX中可以使用原生JS中的map方法遍历渲染列表

function App() {

    const list = [
        {id: 1000, name: "java"},
        {id: 1001, name: "react"},
        {id: 1002, name: "python"},
        {id: 1003, name: "C"}
    ]


    return (
        <div className="App">

            {list.map(item=>
                <p>{item.name}</p>
            )}

        </div>
    );
}

export default App;

在这里插入图片描述


2.3 JSX中实现条件渲染

语法:在React中,可以通过逻辑与运算符&&、三元表达式(?:)实现基础的条件渲染

与运算符&&

function App() {



    return (

        <div>
            {true && <div>this is div</div>}
            {false && <span>this is span</span>}
        </div>

    );
}

export default App;

在这里插入图片描述


三元表达式(?: )

function App() {



    return (

        <div>
            {false ? <div>this is div</div>:<span>this is span</span>}
        </div>

    );
}

export default App;

在这里插入图片描述


3. React中的事件绑定

3.1 React 基础事件绑定

语法:on + 事件名称 = { 事件处理程序 },整体上遵循驼峰命名法

function App() {

function button(){
    console.log("事件被点击了")
}

    return (

        <div>

            <button onClick={button}>按钮</button>

        </div>

    );
}

export default App;

在这里插入图片描述


3.2 使用事件对象参数

语法:在事件回调函数中设置形参e

function App() {

function button(e){
    console.log("事件被点击了",e)
}

    return (

        <div>

            <button onClick={button}>按钮</button>

        </div>

    );
}

export default App;

在这里插入图片描述


3.3 传递自定义参数

语法:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参
注意:不能直接写函数调用,这里事件绑定需要一个函数引用

function App() {

function button(name){
    console.log("事件被点击了",name)
}

    return (

        <div>

            <button onClick={()=>button("mhh")}>按钮</button>

        </div>

    );
}

export default App;

在这里插入图片描述


3.4 同时传递事件对象和自定义参数

语法:在事件绑定的位置传递事件实参e和自定义参数,clickHandler中声明形参,注意顺序对应

function App() {

function clickHandler(e,name){
    console.log("事件被点击了",e,name)
}

    return (

        <div>

            <button onClick={(e)=>clickHandler(e,"mhh")}>按钮</button>

        </div>

    );
}

export default App;

在这里插入图片描述


4.React中的组件

概念:一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次
在这里插入图片描述 组件化开发可以让开发者像搭积木一样构建一个完整的庞大的应用


4.1 自定义组件

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI, 渲染组件只需要把组件当成标签书写 即可。

function App() {

    //定义组件
    function Button() {

        //组件内部逻辑
        return (
            <div>
                <button>按钮1</button>
                <button>按钮2</button>
            </div>
        )
    }

    return (

        <div>
            {/*自闭和*/}
            <Button/>
            
            {/*成对标签*/}
            <Button></Button>

        </div>

    );
}

export default App;

在这里插入图片描述


5. useState 与 useReducer

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量, 从而控制影响组件的渲染结果
本质:和普通JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)
useReducer 是另一种用于管理状态的 Hook,它提供了一种更可预测的状态管理方式,特别适用于复杂的状态逻辑。


5.1 React状态的规则

5.1.1状态不可变

在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新

5.1.2修改对象状态

规则:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改

  • 直接修改原对象,不引发视图变化
  • 调用useState中的set传入新对象用于修改

5.2 useState使用

useState 适用于简单的、独立的状态管理

import {useState} from "react";

function App() {


    // 1. useState是一个函数,返回值是一个数组
    // 2. 数组中的第一个参数是状态变量,第二个参数是set函数用来修改状态变量
    // 3. useState的参数将作为count的初始值
    // 4. 基本类型
    const [count,setCount] = useState(0);

    // 1. useState是一个函数,返回值是一个数组
    // 2. 数组中的第一个参数是状态变量,第二个参数是set函数用来修改状态变量
    // 3. useState的参数将作为user的初始值
    // 4. 对象类型
    const [user,setUser] = useState({
        id:1,
        name:"mhh"
    });

    return (

        <div>
                    {/*
                        点击事件:
                            1.用传入对新值修改count
                            2.重新使用新的count渲染ui
                    */}
            <button onClick={()=>setCount(count+1)}>{count}</button>


            <p/>

            {/*
                        点击事件:
                            1.用传入对新值修改user中的name
                            2.重新使用新的user.name渲染ui
                            3. ...user 表示使用...可以将一个对象或数组展开为另一个对象或数组,并将其属性或元素合并到新对象或数组中。
                    */}
            <button onClick={()=>setUser({
                ...user,
                name:"mhh123"
            })}>{user.id}<p/>{user.name}</button>


        </div>

    );
}

export default App;


在这里插入图片描述


5.3 useReducer 使用

useReducer 是一种更加灵活、可预测的状态管理方式,适用于处理较为复杂的状态逻辑。
请添加图片描述

import { useReducer } from "react";

// App组件
function App() {
    // 使用 useReducer 定义 state 和 dispatch
    const [state, dispatch] = useReducer(reducer, 0);

    // 定义 reducer 函数
    function reducer(state, action) {
        // 根据action的类型进行状态处理
        switch (action.type) {
            case 'INC':
                return state + 1; // 增加 state 的值
            case 'DEC':
                return state - 1; // 减少 state 的值
            default:
                return state;
        }
    }

    // 组件渲染
    return (
        <div>
            {/* 点击按钮调用 dispatch 函数派发对应的 action */}
            <button onClick={() => dispatch({ type: 'DEC' })}>-</button>
            {state} {/* 显示当前的状态值 */}
            <button onClick={() => dispatch({ type: 'INC' })}>+</button>
        </div>
    );
}

export default App;


6.组件的样式处理

6.1 行那样式(不推荐)


function App() {




    return (

      <div style={{color:"red"}}>this is div</div>

    );
}

export default App;

在这里插入图片描述


6.2 class类名控制(推荐)

6.2.1创建index.css

在使用样式渲染的同级目录下创建index.css

.foo{
    color: green;
}

在这里插入图片描述


6.2.2 使用index.css

1.导入指定样式 import ‘./index.css’
2. 使用className指定样式

import './index.css'
function App() {




    return (


      <div className={"foo"}>this is div</div>

    );
}

export default App;

在这里插入图片描述


7.useRef (获取DOM)

在 React 组件中获取/操作 DOM,需要使用 useRef React Hook钩子函数

import {useRef} from "react";

function App() {


    const inputUseRef = useRef();


    async function  click(){
        console.log(inputUseRef.current)
    }
    return (


        <div>
            <input value={123} type={"text"} ref={inputUseRef}/>

            <button onClick={click}>按钮</button>
        </div>
    );
}

export default App;

在这里插入图片描述


8. 组件通信

概念:组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信方法

8.1 父传子

  • 父组件传递数据 - 在子组件标签上绑定属性
  • 子组件接收数据 - 子组件通过props参数接收数据

8.1.1 props使用及说明

props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX

props是只读对象
子组件只能读取props中的数据,不能直接进行修改, 父组件的数据只能由父组件修改

function Son(props) {
    console.log(props)
    return <div>{props.appName}</div>
}


function App() {
    const appName = 'this is app name'
    return (
        <div>
            <Son

                appName={appName} age={20} isTrue={false}
                list={['Vue', 'React ']}
                obi={{name: 'jack'}}
                cb={() => console.log(123)}
                child={<span>this is span child</span>}
            />
        </div>
    )
}
export default App

在这里插入图片描述


8.1.2 特殊的prop-chilren

场景:当我们把内容嵌套在组件的标签内部时,组件会自动在名为children的prop属性中接收该内容

function Son(props) {
    return <div>{props.children}</div>
}


function App() {
    const appName = 'this is app name'
    return (
        <div>
            <Son>

                <div>this is div</div>
                <span>this is span</span>
            </Son>
        </div>
    )
}
export default App

在这里插入图片描述


8.2 子传父

核心思路:在子组件中调用父组件中的函数并传递参数

import {useState} from "react";

function Son(props) {

    return <button onClick={() => props.message("this is son message")}>son按钮</button>
}


function App() {
    const [message, setMessage] = useState("")

    const getMessage = (message) => {
        setMessage(message)
    }
    return (
        <div>
            {message}
            <Son message={getMessage}/>
        </div>
    )
}

export default App

请添加图片描述


8.3 使用状态提升实现兄弟组件通信

实现思路:借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递

  1. Son1组件先通过子传父的方式把数据传给父组件App
  2. App拿到数据后通过父传子的方式再传递给Son2组件
import {useState} from "react";

function Son1(props) {

    return <button onClick={() => props.message("this is son1 message")}>son按钮</button>
}

function Son2(props) {

    return <div>
        {props.message}
    </div>
}

function App() {
    const [message, setMessage] = useState("this is App")

    const getMessage = (message) => {
        setMessage(message)
    }
    return (
        <div>

            <Son1 message={getMessage}/>

            <Son2 message={message}/>
        </div>
    )
}

export default App

请添加图片描述


8.4 使用Context机制跨层级组件通信

只要是组件是包含性质就可以使用
实现步骤:

  1. 使用createContext方法创建一个上下文对象Ctx
  2. 在顶层组件(App)中通过 Ctx.Provider 组件提供数据
  3. 在底层组件(B)中通过 useContext 钩子函数获取消费数据
     

一定要注意是在 顶层组件 通过Provider组件提供数据

// App -> A -> B

import { createContext, useContext } from "react"

// 1. createContext方法创建一个上下文对象

const MsgContext = createContext()

function A () {
    const msg = useContext(MsgContext)
    return (
        <div>
            this is A component-->{msg}
            <B />
        </div>
    )
}

function B () {
    // 3. 在底层组件 通过useContext钩子函数使用数据
    const msg = useContext(MsgContext)
    return (
        <div>
            this is B compnent-->{msg}
        </div>
    )
}

function App () {
    const msg = 'this is app msg'
    return (
        <div>
            {/* 2. 在顶层组件 通过Provider组件提供数据 */}
            <MsgContext.Provider value={msg}>
                this is App
                <A />
            </MsgContext.Provider>
        </div>
    )
}

export default App

在这里插入图片描述


9. useEffect 与useMemo 的使用

  • useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作(副作用), 比 如发送AJAX请求,更改DOM等等
  • useMemo是每次重新渲染的时候能够缓存计算的结果

9.1 useEffect

useEffect用于处理副作用逻辑,比如数据获取、订阅事件等。它在每次渲染后执行,并且可以在依赖变化时执行清除操作。

9.1.1 useEffect 基础使用

useEffect(() => {}, [])

  • 参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
  • 参数2是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,
依赖项副作用函数执行时机
没有依赖项组件初始渲染 + 组件更新时执行
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 特性依赖项变化时执行
import {useEffect, useState} from "react";


function App() {

    const [message, setMessage] = useState("");

    useEffect(() => {
        setMessage("use useEffect")
    }, [])
    return (
        <div>
            {message}
        </div>
    )
}

export default App

在这里插入图片描述


9.1.2 useEffect — 清除副作用

在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开 启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用
 
说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行

import { useEffect, useState } from "react"

function Son () {
    // 1. 渲染时开启一个定时器
    useEffect(() => {
        const timer = setInterval(() => {
            console.log('定时器执行中...')
        }, 1000)

        return () => {
            // 清除副作用(组件卸载时)
            clearInterval(timer)
        }
    }, [])
    return <div>this is son</div>
}

function App () {
    // 通过条件渲染模拟组件卸载
    const [show, setShow] = useState(true)
    return (
        <div>
            {show && <Son />}
            <button onClick={() => setShow(false)}>卸载Son组件</button>
        </div>
    )
}

export default App

请添加图片描述


9.2 useMemo

useMemo用于计算和返回一个值,以优化性能,并且它可以在依赖变化时重新计算值

9.2.1 useMemo的基础使用

  • useMemo(()=>{},[])
    第一个参数是一个回调函数,用于执行需要进行记忆的计算操作。这个回调函数返回的值会被记忆,并在依赖项发生变化时进行重新计算。
    第二个参数是一个依赖项数组,它是一个包含了所有可能影响记忆值变化的变量的数组。当依赖项数组中的变量发生变化时,useMemo 将重新执行回调函数以计算新的值。如果不需要依赖项,可以传入一个空数组 [] 来表示不依赖任何变量,这样 useMemo 会在初始化时执行一次回调函数并将其返回值记忆下来,不会再重新计算。

请添加图片描述

import React, {useMemo, useState} from "react";

function App() {
    // 使用useState钩子声明两个状态变量count1和count2,并初始化为0
    let [count1, setCount1] = useState(1);
    let [count2, setCount2] = useState(1);


    // 使用useMemo钩子创建一个记忆值(memoized value),在依赖项发生变化时才重新计算
    const memo = useMemo(() => {

        count1 = count1 * 2 - 1;
        console.log("count1: ", count1); // 当count1发生变化时,打印日志
        return count1;
    }, [count1]); // 仅在count1发生变化时重新计算memo的值

    return (

        <div>

            {memo}  {/*显示memo的值*/}

            <button onClick={() => {
                setCount1(count1 + 1)
            }}>按钮1
            </button>
            {/* 当按钮1被点击时,调用setCount1来更新count1的值 */}
            <button onClick={() => {
                setCount2(count2 + 1)
            }}>按钮2
            </button>
            {/* 当按钮2被点击时,调用setCount2来更新count2的值 */}
        </div>
    );
}

export default App;



10. Redux

redux代码下载链接地址

Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态


10.1 Redux与React - 环境准备

10.1.1安装配套工具命令: npm i @reduxjs/toolkit react-redux

在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit react-redux

  1. Redux Toolkit(RTK): 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
  2. react-redux: 用来 链接 Redux 和 React组件 的中间件

10.1.2 store目录结构设计

  1. 通常集中状态管理的部分都会单独创建一个单独的 store 目录
  2. 应用通常会有很多个子store模块,所以创建一个 modules 目录,在内部编写业务分类的子store
  3. store中的入口文件 index.js 的作用是组合modules中所有的子模块,并导出store

在这里插入图片描述


10.2 Redux与React - 基础使用

10.2.1 使用React Toolkit 创建 counterStore

src/store/modules/counterStore.jsx

import { createSlice } from '@reduxjs/toolkit'

const counterStore = createSlice({
    // 模块名称独一无二
    name: 'counter',
    // 初始数据
    initialState: {
        count: 1
    },
    // 修改数据的同步方法
    reducers: {
        increment (state) {
            state.count++
        },
        decrement(state){
            state.count--
        }
    }
})
// 结构出actionCreater
const { increment,decrement } = counterStore.actions

// 获取reducer函数
const counterReducer = counterStore.reducer

// 导出
export { increment, decrement }
export default counterReducer

在这里插入图片描述


10.2.2 配置store集中管理

src/store/index.jsx

import { configureStore } from '@reduxjs/toolkit'

import counterReducer from './modules/counterStore'

export default configureStore({
    reducer: {
        // 注册子模块
        counter: counterReducer
    }
})

在这里插入图片描述


10.2.3 为React注入store

src/index.js
 
react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,链接正式建立

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
    // 提供store数据
    <Provider store={store}>
        <App />
    </Provider>
)

在这里插入图片描述


10.2.4 React组件使用store中的数据

在React组件中使用store中的数据,需要用到一个 钩子函数 - useSelector,它的作用是把store中的数据映射到组件 中,使用样例如下:在这里插入图片描述

  • 这里的counter指的是配置store集中管理中你所配置的key
import { useSelector } from "react-redux";

function App() {
  // 使用 useSelector 钩子从 Redux 存储中选择 count 状态
  const { count } = useSelector((state) => state.counter);

  return (
    <div>
      {/* 在页面中渲染 count 状态 */}
      {count}
    </div>
  );
}

export default App;

这里返回的就是counterReducer里的count初始数据 1
在这里插入图片描述


10.2.5 React组件修改store中的数据

  • useDispatch 钩子用于获取 Redux 的 dispatch 函数,这样我们可以在组件中触发 Redux 动作。
  • useSelector 钩子用于从 Redux 存储中选择 counter 子模块的 count 状态,并将其赋值给 count 变量。
import { useDispatch, useSelector } from "react-redux";
import { decrement, increment } from "./store/modules/counterStore";

function App() {
    // 使用 useDispatch 钩子获取 dispatch 函数
    const dispatch = useDispatch();

    // 使用 useSelector 钩子从 Redux 存储中选择 count 状态
    const { count } = useSelector((state) => state.counter);

    return (
        <div>
            {/* "+" 按钮,点击时调用 increment 动作 */}
            <button onClick={() => { dispatch(increment()) }}>+</button>

            &nbsp;

            {/* 在页面中渲染 count 状态 */}
            {count}

            &nbsp;

            {/* "-" 按钮,点击时调用 decrement 动作 */}
            <button onClick={() => { dispatch(decrement()) }}>-</button>
        </div>
    );
}

export default App;

请添加图片描述


10.3 Redux与React - 提交action传参

10.3.1使用React Toolkit 创建 userStore

src/store/modules/userStore.jsx

import {createSlice} from '@reduxjs/toolkit'

// 创建 user 模块的 slice
const userStore = createSlice({
  // 模块名称独一无二
  name: 'user',
  // 初始数据
  initialState: {
    count: 1,
    userinfo: {
      name: "小白",
      age: 12
    }
  },
  // 修改数据的同步方法
  reducers: {
    // 更新用户信息
    updateUserinfo(state, action) {
      state.userinfo = action.payload;
    },
    // 更新计数
    updateCount(state, {payload}) {
      state.count = payload;
    }
  }
});

// 结构出 actionCreators
const {updateUserinfo, updateCount} = userStore.actions;

// 获取 reducer 函数
const userReducer = userStore.reducer;

// 导出 actionCreators 和 reducer
export {updateUserinfo, updateCount};
export default userReducer;

在这里插入图片描述


10.3.2 配置store集中管理

src/store/index.jsx

import {configureStore} from '@reduxjs/toolkit'

import counterReducer from './modules/counterStore'
import userReducer from "./modules/userStore";

export default configureStore({
    reducer: {
        // 注册子模块
        //counter: counterReducer,
        user: userReducer
    }
})

在这里插入图片描述


10.3.3 为React注入store

src/index.js
 
react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,链接正式建立

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
    // 提供store数据
    <Provider store={store}>
        <App />
    </Provider>
)

在这里插入图片描述


10.3.4 Redux与React - 提交action传参

import { useDispatch, useSelector } from "react-redux";
import { updateUserinfo, updateCount } from "./store/modules/userStore"

function App() {
    // 使用 useDispatch 钩子获取 dispatch 函数
    const dispatch = useDispatch();
    // 使用 useSelector 钩子从 Redux 存储中选择 count 状态和 userinfo 状态
    const { userinfo, count } = useSelector((state) => state.user);

    return (
        <div>
            {/* 显示 userinfo 中的 name 属性 */}
            <p>{userinfo.name}</p>
            {/* 显示 userinfo 中的 age 属性 */}
            <p>{userinfo.age}</p>
            {/* 显示 count 状态 */}
            <p>{count}</p>
            {/* 触发 updateUserinfo 动作 更新用户信息 */}
            <button onClick={() => {
                dispatch(updateUserinfo({
                    name: "小黑",
                    age: 10
                }))
            }}>updateUserInfo</button>
            {/* 触发 updateCount 动作 更新计数 */}
            <button onClick={() => dispatch(updateCount(10))}>updateCount</button>
        </div>
    );
}

export default App;

请添加图片描述


10.4 Redux与React - 异步状态操作

10.4.1 json-server 模拟后端 API 服务器

  • 是一个用于创建 RESTful API 的工具,可以快速搭建一个本地的 JSON 数据服务。它基于 Node.js,并提供了简单的命令行工具和配置选项,使您能够轻松地创建一个模拟的 API 服务器。
  • 官方网站: https://www.npmjs.com/package/json-server

  • 10.4.1.1 安装 son-server

npm命令: npm install json-server

 在这里插入图片描述


  • 10.4.1.2 定义 API 的数据结构和数据
  • 使用一个 JSON 文件来定义 API 的数据结构和数据
  • 您可以创建一个名为 db.json 的文件,其中包含模拟的数据
    在这里插入图片描述

  • 10.4.1.2 定义 API 的数据结构和数据
  • 使用 json-server 启动服务器
  • 命令:json-server db.json
    在这里插入图片描述

10.4.1使用React Toolkit 创建 getUserListStore

src/store/modules/getUserListStore.jsx

import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

// 创建一个Redux Slice
const getUserListStore = createSlice({
    name: 'userList',
    initialState: {
        userList: [{
            "id":0,
            "name":"初始值"
        }]
    },
    reducers: {
        // 设置用户列表
        setUserList(state, action) {
            state.userList = action.payload;
        }
    }
});

// 从Slice中导出actions
const { setUserList } = getUserListStore.actions;

const url = 'http://localhost:3000/data';

// 异步请求获取用户列表数据
const fetchGetUserList = () => {
    return async (dispatch) => {
        // 发起GET请求获取数据
        const res = await axios.get(url);
        // 得到数据之后通过dispatch函数 触发修改
        // 调用setUserList action将获取到的数据设置到state中
        dispatch(setUserList(res.data));
    }
}

// 导出fetchGetUserList方法和reducer
export { fetchGetUserList };
export default getUserListStore.reducer;

在这里插入图片描述


10.4.2 配置store集中管理

src/store/index.jsx

import {configureStore} from '@reduxjs/toolkit'

import counterReducer from './modules/counterStore'
import userReducer from "./modules/userStore";
import getUserList from "./modules/getUserListStore"
export default configureStore({
    reducer: {
        // 注册子模块
        // counter: counterReducer,
        // user: userReducer,
        userList: getUserList
    }
})

在这里插入图片描述


10.4.3 为React注入store

src/index.js
 
react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,链接正式建立

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
    // 提供store数据
    <Provider store={store}>
        <App />
    </Provider>
)

在这里插入图片描述


10.4.4 Redux与React - 异步状态操作

import { useDispatch, useSelector } from "react-redux";
import { fetchGetUserList } from "./store/modules/getUserListStore"

function App() {

    const dispatch = useDispatch();

    const { userList } = useSelector((state) => state.userList);

    return (
        <div>
            {
                userList.map(user=><div key={user.id}>{user.name}</div>)
            }
            <button onClick={()=>dispatch(fetchGetUserList())}>按钮</button>
        </div>
    );
}

export default App;

请添加图片描述


10.5 LocalStorage 浏览器存储数据

现存问题: Redux存入数据之后如果刷新浏览器,数据会丢失(持久化就是防止刷新时丢失数据)
问题原因: Redux是基于浏览器内存的存储方式,刷新时状态恢复为初始值

10.5.1 修改getUserListStore

  • setUserList: 在同步方法中将用户列表保存到本地存储中
    • JSON.stringify() 是一个 JavaScript 方法,用于将 JavaScript 对象或值转换为 JSON 字符串。
  • initialState : 首先从本地存储中获取用户列表数据,如果没有在取默认值
    • JSON.parse() 是一个 JavaScript 方法,用于解析 JSON 格式的字符串,并将其转换为对应的 JavaScript 对象或值
import { createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

// 创建一个 Redux Slice
const getUserListStore = createSlice({
  name: 'userList', // Slice 的名称
  initialState: {
    userList: localStorage.getItem('userList') // 从本地存储中获取用户列表数据
      ? JSON.parse(localStorage.getItem('userList'))
      : [
          {
            id: 0,
            name: '初始值',
          },
        ],
  },
  reducers: {
    // 设置用户列表
    setUserList(state, action) {
      state.userList = action.payload;
      // 将更新后的用户列表保存到本地存储中
      localStorage.setItem('userList', JSON.stringify(action.payload));
    },
  },
});

// 从 Slice 中导出 actions
const { setUserList } = getUserListStore.actions;

const url = 'http://localhost:3000/data';

// 异步请求获取用户列表数据
const fetchGetUserList = () => {
  return async (dispatch) => {
    try {
      // 发起 GET 请求获取数据
      const res = await axios.get(url);
      // 得到数据之后通过 dispatch 函数触发修改
      // 调用 setUserList action 将获取到的数据设置到 state 中
      dispatch(setUserList(res.data));
    } catch (error) {
      // 发生错误时的处理
      console.error(error);
    }
  };
};

// 导出 fetchGetUserList 方法和 reducer
export { fetchGetUserList };
export default getUserListStore.reducer;

在这里插入图片描述


10.5.2 配置store集中管理

src/store/index.jsx

import {configureStore} from '@reduxjs/toolkit'

import counterReducer from './modules/counterStore'
import userReducer from "./modules/userStore";
import getUserList from "./modules/getUserListStore"
export default configureStore({
    reducer: {
        // 注册子模块
        // counter: counterReducer,
        // user: userReducer,
        userList: getUserList
    }
})

在这里插入图片描述


10.5.3 为React注入store

src/index.js
 
react-redux负责把Redux和React 链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中,链接正式建立

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 导入store
import store from './store'
// 导入store提供组件Provider
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
    // 提供store数据
    <Provider store={store}>
        <App />
    </Provider>
)

在这里插入图片描述


10.5.4 Redux与React - 异步状态操作

import { useDispatch, useSelector } from "react-redux";
import { fetchGetUserList } from "./store/modules/getUserListStore"

function App() {

    const dispatch = useDispatch();

    const { userList } = useSelector((state) => state.userList);

    return (
        <div>
            {
                userList.map(user=><div key={user.id}>{user.name}</div>)
            }
            <button onClick={()=>dispatch(fetchGetUserList())}>按钮</button>
        </div>
    );
}

export default App;

请添加图片描述


10.6 Redux调试 - devtools

安装chrome调试工具

  • Redux官方提供了针对于Redux的调试工具,支持实时state信息展示,action提交信息查看等
  • 注意: 安装完重新启动浏览器

请添加图片描述


11. ReactRouter

  • React Router是一个用于在React应用程序中实现路由功能的库。它提供了一组组件和API,用于管理应用程序的URL,并根据URL的变化进行相应的页面渲染和导航。

11.1 ReactRouter 环境准备

          ReactRouter代码下载链接地址

11.1.1安装配套工具命令: npm i react-router-dom

  • 运行npm i react-router-dom是为了安装React Router库的DOM版本。React Router库是按照平台的不同提供了不同版本,而react-router-dom是React Router库的针对Web平台的DOM版本。
  • 使用react-router-dom可以在React应用程序中轻松地实现路由功能。

11.1.2 Router目录结构设计

pages 目录:存放与路由对应的页面组件,通常会包含特定页面的相关逻辑和样式。
routers 目录:存放与路由有关的组件和配置。


11.2 Router基础使用

11.2.1 配置存放与路由对应的页面组件

  • 11.2.1.1 配置 Article 组件
  • src/page/Article/index.jsx

    const Article=()=>{
        return <div>this is article</div>
    }
    
    export default Article;
    

在这里插入图片描述


  • 11.2.1.2 配置 Login 组件
  • src/page/Login/index.jsx

    const Login=()=>{
        return <div>this is login</div>
    }
    
    export default Login;
    
    在这里插入图片描述

11.2.2 配置存放与路由有关的组件和配置

src/router/index.jsx

import Login from "../page/Login";
import Article from "../page/Article";
import {createBrowserRouter} from "react-router-dom";



const router = createBrowserRouter([

    {
        path: "/login",      // 路径
        element: <Login />   // 对应的组件
    },
    {
        path: '/article',
        element: <Article/>
    }
])

export default router;

在这里插入图片描述


11.2.3 配置应用入口文件渲染

src/index.js

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import {RouterProvider} from "react-router-dom";
import router from "./router";


ReactDOM.createRoot(document.getElementById('root')).render(
    // 使用 RouterProvider 提供路由对象给应用程序
    <RouterProvider router={router}/>
)

在这里插入图片描述

请添加图片描述


11.3 ReactRouter - 路由导航

  • 基于当前 11.2 Router基础使用 中的组件配置
  • 路由导航是路由系统中的多个路由之间需要进行路由跳转

11.3.1 声明式导航

  • 声明式导航是指通过在模版中通过 <Link/> 组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这 种方式进行

  • 语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过 字符串拼接的方式拼接参数即可

    在这里插入图片描述

import {Link} from "react-router-dom";

const Login = () => {
    return (
        <div>
            this is login
            <p>
                <Link to={"/article"}>go to article</Link>
            </p>
        </div>
    )
}

export default Login;

在这里插入图片描述

请添加图片描述


11.3.2 编程式导航

  • 编程式导航是指通过 useNavigate 钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在 登录请求完毕之后跳转就可以选择这种方式,更加灵活
  • 语法说明:通过调用navigate方法传入地址path实现跳转
import React from "react";
import { useNavigate } from "react-router-dom";

const Login = () => {
    const navigate = useNavigate();  // 使用 useNavigate Hook 获取导航函数

    return (
        <div>
            this is login
            <p>
                <button onClick={() => { navigate("/article") }}>go to article</button>
                {/* 点击按钮时,调用 navigate 函数跳转到 "/article" 路径 */}
            </p>
        </div>
    )
}

export default Login;

在这里插入图片描述
请添加图片描述


11.4 ReactRouter - 导航传参

  • 基于当前 11.2 Router基础使用 中的组件配置

11.4.1 searchParams 传参

  • searchParams:指的是 URL 中的查询参数部分,即 ? 后面的子字符串。
  • 它表示一个 URLSearchParams 对象,可以用于解析和操作 URL 查询参数。
  • searchParams 提供了一组方法,例如 get()、set()、delete() 等,用于获取、设置和删除查询参数的键值对。
  • 声明式传参
    • 编辑Login组件

      加入传递参数 id 和 name

      import React from "react";
      import {Link} from "react-router-dom";
      
      const Login = () => {
      
          return (
              <div>
                  this is login
                  <p>
                      <Link to={"/article?id=110&name=小白"}>go to article</Link>
                  </p>
              </div>
          )
      }
      
      export default Login;
      
      

    在这里插入图片描述

请添加图片描述


    • 编辑跳转组件

      import React from "react";
      import { useSearchParams } from "react-router-dom";
      
      const Article = () => {
          const [searchParams] = useSearchParams(); // 使用 useSearchParams Hook 获取查询参数对象
      
          const id = searchParams.get('id'); // 从查询参数对象中获取 'id' 参数值
          const name = searchParams.get('name'); // 从查询参数对象中获取 'name' 参数值
      
          return (
              <div>
                  this is article
                  <p>id: {id}</p>
                  <p>name: {name}</p>
              </div>
          );
      };
      
      export default Article;
      

      在这里插入图片描述


  • 编程式传参
    • 编辑Login组件

      加入传递参数 id 和 name

      import React from "react";
      import { useNavigate } from "react-router-dom";
      
      const Login = () => {
          const navigate = useNavigate();  // 使用 useNavigate Hook 获取导航函数
      
          return (
              <div>
                  this is login
                  <p>
                      <button onClick={() => { navigate("/article?id=1&name=mhh") }}>go to article</button>
                      {/* 点击按钮时,调用 navigate 函数跳转到 "/article" 路径 */}
                  </p>
              </div>
          )
      }
      
      export default Login;
      
      
    在这里插入图片描述

    • 编辑跳转组件

      import React from "react";
      import { useSearchParams } from "react-router-dom";
      
      const Article = () => {
          const [searchParams] = useSearchParams(); // 使用 useSearchParams Hook 获取查询参数对象
      
          const id = searchParams.get('id'); // 从查询参数对象中获取 'id' 参数值
          const name = searchParams.get('name'); // 从查询参数对象中获取 'name' 参数值
      
          return (
              <div>
                  this is article
                  <p>id: {id}</p>
                  <p>name: {name}</p>
              </div>
          );
      };
      
      export default Article;
      

      在这里插入图片描述

请添加图片描述


11.4.2 params 传参

  • 是指在路由配置中定义的动态路由参数。
  • 在 React Router 中,使用路由参数可以在 URL 中定义占位符,并在组件中通过 useParams() Hook 或 props.match.params 来获取。
  • 这些参数对应于路由路径中的分段,可以根据不同的 URL 进行动态渲染。
  • 声明式传参
    • 编辑Login组件

      加入传递参数 id 和 name

      import React from "react";
      import {Link} from "react-router-dom";
      
      const Login = () => {
      
          return (
              <div>
                  this is login
                  <p>
                      <Link to={"/article/110/小白"}>go to article</Link>
                  </p>
              </div>
          )
      }
      
      export default Login;
      
      

      在这里插入图片描述


    • 编辑跳转组件

      import React from "react";
      import {useParams} from "react-router-dom";
      
      const Article = () => {
          const params = useParams(); // 使用 useParams Hook 获取查询参数对象
      
          const id = params.id; // 从查询参数对象中获取 'id' 参数值
          const name = params.name; // 从查询参数对象中获取 'name' 参数值
      
          return (
              <div>
                  this is article
                  <p>id: {id}</p>
                  <p>name: {name}</p>
              </div>
          );
      };
      
      export default Article;
      

      在这里插入图片描述


    • 编辑路由配置

      在路由配置中,可以使用冒号 : 加上参数名称来定义动态的路径段

      import Login from "../page/Login";
      import Article from "../page/Article";
      import {createBrowserRouter} from "react-router-dom";
      
      
      const router = createBrowserRouter([
      
          {
              path: "/login",      // 路径
              element: <Login/>   // 对应的组件
          },
          {
              path: '/article/:id/:name',
              element: <Article/>
          }
      ])
      
      export default router;
      
      

      在这里插入图片描述

请添加图片描述


  • 编程式传参
    • 编辑Login组件

      加入传递参数 id 和 name

      import React from "react";
      import { useNavigate } from "react-router-dom";
      
      const Login = () => {
          const navigate = useNavigate();  // 使用 useNavigate Hook 获取导航函数
      
          return (
              <div>
                  this is login
                  <p>
                      <button onClick={() => { navigate("/article/1/mhh") }}>go to article</button>
                      {/* 点击按钮时,调用 navigate 函数跳转到 "/article" 路径 */}
                  </p>
              </div>
          )
      }
      
      export default Login;
      
      

      在这里插入图片描述


    • 编辑跳转组件

      import React from "react";
      import {useParams} from "react-router-dom";
      
      const Article = () => {
          const params = useParams(); // 使用 useParams Hook 获取查询参数对象
      
          const id = params.id; // 从查询参数对象中获取 'id' 参数值
          const name = params.name; // 从查询参数对象中获取 'name' 参数值
      
          return (
              <div>
                  this is article
                  <p>id: {id}</p>
                  <p>name: {name}</p>
              </div>
          );
      };
      
      export default Article;
      

      在这里插入图片描述


    • 编辑路由配置

      在路由配置中,可以使用冒号 : 加上参数名称来定义动态的路径段

      import Login from "../page/Login";
      import Article from "../page/Article";
      import {createBrowserRouter} from "react-router-dom";
      
      
      const router = createBrowserRouter([
      
          {
              path: "/login",      // 路径
              element: <Login/>   // 对应的组件
          },
          {
              path: '/article/:id/:name',
              element: <Article/>
          }
      ])
      
      export default router;
      
      

      在这里插入图片描述

请添加图片描述


11.5 ReactRouter - 嵌套路由配置

  • 在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由

11.5.1 嵌套路由基础使用

  • 基于当前 11.2 Router基础使用 中的组件配置
  1. 使用 children 属性配置路由嵌套关系
  2. 使用 <Outlet/> 组件配置二级路由渲染位置
  • 11.5.1.1 新增 配置 Board 组件

    src/page/Board/index.jsx

    import React from "react";
    
    const Board = () => {
    
        return (
            <div>
                this is board
            </div>
        );
    };
    
    export default Board;
    

在这里插入图片描述


  • 11.5.1.2 使用 children 属性配置路由嵌套关系
    • 编辑路由组件
    import Login from "../page/Login";
    import Article from "../page/Article";
    import {createBrowserRouter} from "react-router-dom";
    import Board from "../page/Board";
    
    
    
    const router = createBrowserRouter([
    
        {
            path: "/login",      // 路径
            element: <Login />,   // 对应的组件
            children:[
                {
                    path: 'board',               // 子路由路径
                    element: <Board />           // 子路由对应的组件
                },
            ]
        },
        {
            path: '/article',
            element: <Article/>
        }
    ])
    
    export default router;
    
    

    在这里插入图片描述


  • 11.5.1.3 使用 <Outlet/> 组件配置二级路由渲染位置
    • 编辑二级路由出口(修改Login组件)
    import React from "react";
    import { Outlet } from "react-router-dom";
    
    const Login = () => {
        return (
            <div>
                This is login
                <Outlet />
                {/* 使用 Outlet 组件来渲染子路由的内容 */}
            </div>
        );
    };
    
    export default Login;
    
    

    在这里插入图片描述

请添加图片描述


11.5.2 ReactRouter - 默认二级路由

  • 当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true
  • 11.5.2.1 编辑路由组件
    • ndex: true:表示该子路由是默认子路由,在父级路由(/login)下默认加载该子路由。
    import React from 'react';
    import { createBrowserRouter } from 'react-router-dom';
    import Login from '../page/Login';
    import Article from '../page/Article';
    import Board from '../page/Board';
    
    // 使用 createBrowserRouter 创建路由配置
    const router = createBrowserRouter([
        {
            path: '/login',                  // 路径
            element: <Login />,              // 对应的组件
            children: [
                {
                    index: true,                  // 设置为默认子路由
                    element: <Board />            // 子路由对应的组件
                },
            ]
        },
        {
            path: '/article',
            element: <Article />
        }
    ]);
    
    export default router;
    
    

    在这里插入图片描述


  • 11.5.2.2 使用 <Outlet/> 组件配置二级路由渲染位置
    • 编辑二级路由出口(修改Login组件)
    import React from "react";
    import { Outlet } from "react-router-dom";
    
    const Login = () => {
        return (
            <div>
                This is login
                <Outlet />
                {/* 使用 Outlet 组件来渲染子路由的内容 */}
            </div>
        );
    };
    
    export default Login;
    
    

    在这里插入图片描述

请添加图片描述


11.6 ReactRouter - 404路由配置

  • 基于当前 11.5 ReactRouter - 嵌套路由配置 中的组件配置

场景:当浏览器输入url的路径在整个路由配置中都找不到对应的 path,为了用户体验,可以使用 404 兜底组件进行 渲染

11.6.1 配置NotFound组件

import React from "react";

const NotFound = () => {

    return (
        <div>
            this is notFound
        </div>
    );
};

export default NotFound;

在这里插入图片描述

11.6.2 配置路由组件

  • 在路由表数组的末尾,以*号作为路由path配置路由
  • 通配符路径:在路由配置数组中添加一个 { path: ‘*’, element: },表示匹配所有未匹配的路径,用于处理 404 页面。
import React from 'react';
import { createBrowserRouter } from 'react-router-dom';
import Login from '../page/Login';
import Article from '../page/Article';
import Board from '../page/Board';
import NotFound from '../page/NotFound';

// 使用 createBrowserRouter 创建路由配置
const router = createBrowserRouter([
  {
    path: '/login',                  // 路径
    element: <Login />,              // 对应的组件
    children: [
      {
        index: true,                  // 设置为默认子路由
        element: <Board />            // 子路由对应的组件
      },
    ]
  },
  {
    path: '/article',
    element: <Article />
  },
  {
    path: '*',
    element: <NotFound />     // 通配符路径,表示匹配所有未匹配的路径
  },
]);

export default router;

在这里插入图片描述

请添加图片描述


12. 封装 Request 请求

        react封装Request 代码下载地址

业务背景: 前端需要和后端拉取接口数据,axios是使用最广的工具插件,针对于项目中的使用,我们需要做一些简单的封装

12.1 封装axios

在项目中URL地址请求可能会有多个,所以这里可以创建多个axios实例

  • 在这里插入图片描述

12.1.1 创建 localhost3000Request.jsx的axios实例

import axios from 'axios';

// 创建了一个名为localhostHttp的axios实例
const localhostHttp = axios.create({
    baseURL: 'http://localhost:3000/', // 设置基本的请求URL为http://localhost:3000/
    timeout: 5000 // 设置超时时间为5000毫秒
});

// 添加请求拦截器
localhostHttp.interceptors.request.use((config) => {
    // 在发送请求之前做一些处理,例如添加token到请求头等操作
    // const token = getToken()
    // if (token) {
    //     config.headers.Authorization = `Bearer ${token}`
    // }
    return config; // 返回经过处理的config
}, (error) => {
    return Promise.reject(error); // 返回一个带有错误信息的Promise对象
});

// 添加响应拦截器
localhostHttp.interceptors.response.use(
    (response) => {
        // 对接收到的响应数据进行处理,并返回处理后的数据
        return response.data;
    },
    (error) => {
        // 对接收到的异常响应进行处理,并返回包含错误信息的Promise对象
        return Promise.reject(error);
    }
);

export default localhostHttp; // 导出创建的axios实例


12.1.2 创建 localhost8080Request.jsx的axios实例

import axios from 'axios';

// 创建了一个名为localhostHttp的axios实例
const localhostHttp = axios.create({
    baseURL: 'http://localhost:8080/', // 设置基本的请求URL为http://localhost:8080/
    timeout: 5000 // 设置超时时间为5秒
});

// 添加请求拦截器
localhostHttp.interceptors.request.use((config) => {
    // 请求发起前的拦截处理,可以在此处修改config或者进行其他操作
    // 例如添加token到请求头
    // const token = getToken()
    // if (token) {
    //     config.headers.Authorization = `Bearer ${token}`
    // }
    return config; // 返回经过处理的config
}, (error) => {
    // 如果发生错误,可以在此处对错误进行统一处理
    return Promise.reject(error); // 返回一个带有错误信息的Promise对象
});

// 添加响应拦截器
localhostHttp.interceptors.response.use((response) => {
    // 在接收到响应数据后,可以在此处对响应数据进行处理
    // 例如对响应数据结构进行统一处理,或者只返回数据的特定部分
    return response.data; // 返回经过处理的响应数据
}, (error) => {
    // 在接收到响应后发生的错误进行统一处理
    return Promise.reject(error); // 返回一个带有错误信息的Promise对象
});

export default localhostHttp; // 导出创建的axios实例


12.1.3 将axios实例都放在统一中转模块

// 统一中转模块函数

// 导入localhost3000Request和localhost8080Request模块
import localhost3000Request from "./localhost3000Request";
import localhost8080Request from "./localhost8080Request";

// 导出localhost3000Request和localhost8080Request模块
export {
    localhost3000Request, // 用于与http://localhost:3000/进行通信的请求对象
    localhost8080Request  // 用于与http://localhost:8080/进行通信的请求对象
}


12.2 配置后端请求接口

          后端代码下载链接地址

端口默认: 8080

在这里插入图片描述

package org.example.react;

import cn.hutool.json.JSONUtil;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@CrossOrigin // 解决跨域问题
@RestController // 表明这是一个控制器类,并且每个处理方法的返回值都会被转换为JSON
@RequestMapping("/react") // 映射请求路径,所有方法都在"/react"路径下
public class ReactRequestController {

    // 处理GET请求,没有参数
    @GetMapping("/get")
    public String get() {
        return "java-get"; // 返回字符串 "java-get"
    }

    // 处理带有参数的GET请求
    @GetMapping("/getParam")
    public String getParam(@RequestParam("param") String param) {
        return param; // 返回接收到的参数值
    }

    // 处理带有多个参数的GET请求
    @GetMapping("/getParams")
    public String getParams(@RequestParam("param1") String param1, @RequestParam("param2") String param2) {
        return param1 + param2; // 将接收到的两个参数连接起来并返回
    }

    // 处理POST请求,接收请求体数据并作为Map<String, String>参数
    @PostMapping("/postRequestBody")
    public String postRequestBody(@RequestBody Map<String, String> map) {
        return map.toString(); // 返回接收到的请求体数据的字符串表示
    }

    // 处理带有参数和请求体的POST请求
    @PostMapping("/postParamAndRequestBody")
    public String postParamAndRequestBody(@RequestParam("param") String param, @RequestBody Map<String, String> map) {
        return param + JSONUtil.toJsonStr(map); // 将接收到的参数和请求体数据转换为JSON字符串并返回
    }
}


12.3 将请求模块化

  • 通过将这些请求操作封装在一个单独的模块中,可以更好地组织代码并以模块化的方式处理HTTP请求.
  • 这种模块化的方式能够帮助您更清晰地管理和维护不同的请求操作,同时也可以更方便地在其他地方重复使用这些操作.
    在这里插入图片描述
import { localhost8080Request } from "../../util"; // 导入用于向localhost:8080发送请求的对象

// 发起一个简单的GET请求,向服务器请求数据
export async function get() {
    return localhost8080Request.get(`/react/get`); // 向指定的URL发送GET请求
}

// 发起一个带有参数的GET请求,将参数作为URL查询参数发送给服务器
export async function getParam(param) {
    return localhost8080Request.get(`/react/getParam`, { params: param }); // 向指定的URL发送带参数的GET请求
}

// 发起一个带有多个参数的GET请求,将多个参数作为URL查询参数发送给服务器
export async function getParams(params) {
    return localhost8080Request.get(`/react/getParams`, { params: params }); // 向指定的URL发送带多个参数的GET请求
}

// 发起一个带有请求体的POST请求,将请求体数据发送给服务器
export async function postRequestBody(requestBody) {
    return localhost8080Request.post(`/react/postRequestBody`, requestBody); // 向指定的URL发送带请求体的POST请求
}

// 发起一个带有参数和请求体的POST请求,将参数作为URL查询参数,请求体数据作为请求体发送给服务器
// 查询参数作为第二个参数传递给 post 方法,请求体数据作为第三个参数传递
export async function postParamAndRequestBody(param, requestBody) {
    return localhost8080Request.post(`/react/postParamAndRequestBody`, requestBody, { params: param }); // 向指定的URL发送带参数和请求体的POST请求
}


12.3 使用按钮调用接口测试

  • 通过不同的按钮点击 来调用不同的接口
    在这里插入图片描述
    请添加图片描述
import { get, getParam, getParams, postParamAndRequestBody, postRequestBody } from "./api/modules/reactRequest"; // 导入API请求函数
import { useState } from "react"; // 导入useState钩子

function App() {
    const [data, setData] = useState(); // 使用useState创建名为data的状态变量,并使用setData函数来更新它

    // 定义一个用于发起请求的函数
    const getRequest = (request) => {
        request.then(res => { // 使用Promise的then方法处理请求成功的情况
            setData(res); // 如果请求成功,将返回的数据设置为状态变量data的值
        }).catch((e) => { // 使用catch方法处理请求失败的情况
            console.log(e); // 如果请求失败,打印错误到控制台
        });
    }

    return (
        <div>
            <div>{data}</div> {/* 在页面上显示存储在data状态变量中的数据 */}

            {/* 按钮,点击后发起一个不带参数的GET请求 */}
            <button onClick={() => {
                getRequest(get()); // 调用getRequest函数,发起get()函数返回的GET请求
            }}>按钮get()</button>

            {/* 按钮,点击后发起一个带参数的GET请求 */}
            <button onClick={() => {
                getRequest(getParam({ param: "123" })); // 调用getRequest函数,发起getParam函数带有参数的GET请求
            }}>按钮getParam</button>

            {/* 按钮,点击后发起一个带多个参数的GET请求 */}
            <button onClick={() => {
                getRequest(getParams({ param1: "123", param2: "456" })); // 调用getRequest函数,发起getParams函数带有多个参数的GET请求
            }}>按钮getParams</button>

            {/* 按钮,点击后发起一个带有请求体的POST请求 */}
            <button onClick={() => {
                getRequest(postRequestBody({ "name": "小白", "age": 12 })); // 调用getRequest函数,发起postRequestBody函数带有请求体的POST请求
            }}>按钮postRequestBody</button>

            {/* 按钮,点击后发起一个带参数和请求体的POST请求 */}
            <button onClick={async () => {
                const result = await postParamAndRequestBody({ param: "mhh" }, { name: "小白", age: 12 }); // 调用postParamAndRequestBody函数,发起带参数和请求体的POST请求
                setData(result); // 将返回的结果设置为data状态变量的值
            }}>按钮postParamAndRequestBody</button>

        </div>
    );
}

export default App;


13 React.memo

  • 默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染
  • React.memo作用:允许组件在props没有改变的情况下跳过重新渲染

13.1 React.memo 基本使用

  • 机制:只有props发生变化时才重新渲染 下面的子组件通过 memo 进行包裹之后,返回一个新的组件MemoSon, 只有传给MemoSon的props参数发生变化时才会重新渲染
    请添加图片描述
import React, { useState } from "react";

// 定义了一个子组件 Son
function Son() {
    console.log('子组件Son被重新渲染了')
    return <div>this is Son</div>
}

// 使用 React.memo 封装了 Son 组件,使其在 props 没发生变化时不会重新渲染
const MemoSon = React.memo(function Son() {
    console.log('子组件MemoSon被重新渲染了')
    return <div>this is MemoSon</div>
})

function App() {
    const [count, setCount] = useState(0);
    return (
        <>
            <Son/> {/* 直接渲染 Son 组件 */}
            <button onClick={() => setCount(count + 1)}>按钮</button> {/* 点击按钮来更新状态 */}
            <MemoSon/> {/* 直接渲染 MemoSon 组件 */}
        </>
    );
}

export default App;


13.2 React.memo 中 props的比较机制

  • 对于props的比较,进行的是‘浅比较’,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比俩次的引用是否相等,如果不相等就会重新渲染,React并不关心对象中的具体属性
  • prop是简单类型
    Object.is(3, 3) => true 没有变化
  • prop是引用类型(对象 / 数组)
    Object([], []) => false 有变化,React只关心引用是否变化
    请添加图片描述
import React, { useState } from "react";

// 使用 React.memo 封装了 Son 组件,使其在 props 没发生变化时不会重新渲染
const MemoSon1 = React.memo(function Son(props) {
    console.log('子组件MemoSon1被重新渲染了')
    return <div>this is MemoSon</div>
})

const MemoSon2 = React.memo(function Son(props) {
    console.log('子组件MemoSon2被重新渲染了')
    return <div>this is MemoSon</div>
})

function App() {
    const [count, setCount] = useState(0);
    return (
        <>
            <button onClick={() => setCount(count + 1)}>按钮</button> {/* 点击按钮来更新状态 */}
            <MemoSon1 count={3}/> {/* 直接渲染 MemoSon1 组件 */}
            <MemoSon2 count={[]}/> {/* 直接渲染 MemoSon2 组件 */}
        </>
    );
}

export default App;


13.3 React.memo 中 配置自定义比较函数

  • 如上一小节的例子,我们不想通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现
    请添加图片描述
import React, { useState } from "react";

// 使用 React.memo 封装了 Son 组件,使其在 props 没发生变化时不会重新渲染
const MemoSon1 = React.memo(function Son(props) {
    console.log('子组件MemoSon1被重新渲染了')
    return <div>this is MemoSon1</div>
})

// 使用 React.memo 封装了 Son 组件,使用自定义的 props 比较函数 arePropsEqual
const MemoSon2 = React.memo(function Son(props) {
    console.log('子组件MemoSon2被重新渲染了')
    return <div>this is MemoSon2</div>
}, arePropsEqual)

// 自定义比较函数,用于决定组件是否重新渲染
function arePropsEqual(oldProps, newProps) {
    console.log(oldProps, newProps);
    return (
        oldProps.list.length === newProps.list.length &&  // 比较列表长度
        oldProps.list.every((oldItem, index) => {  // 遍历列表中的每个元素并进行比较
            const newItem = newProps.list[index];
            console.log(newItem, oldItem);
            return oldItem === newItem;  // 进行每个元素的比较
        })
    )
}

function App() {
    const [count, setCount] = useState(0);  // 声明状态变量 count
    const [list, setList] = useState([]);  // 声明状态变量 list

    return (
        <>
            <button onClick={() => setCount(count + 1)}>按钮1</button>  {/* 点击按钮来更新状态 count */}
            <button onClick={() => setList([1,2,3])}>按钮2</button>  {/* 点击按钮来更新状态 list */}
            <MemoSon1 count={3}/>  {/* 直接渲染 MemoSon1 组件 */}
            <MemoSon2 list={list}/>  {/* 直接渲染 MemoSon2 组件 */}
        </>
    );
}

export default App;


13.4 React.memo 中 useCallback使用

  • 当给子组件传递一个引用类型prop的时候,即使我们使用了memo 函数依旧无法阻止子组件的渲染,其实传递prop的时候,往往传递一个回调函数更为常见,比如实现子传父,此时如果想要避免子组件渲染,可以使用 useCallback缓存回调函数
  • useCallback(() => {}, [])
    第一个参数是回调函数本身
    第二个参数是依赖数组,用于指定在依赖发生变化时才创建新的回调函数,如果依赖不发生变化,则返回 memoized 过的回调函数。
    请添加图片描述
import React, { useCallback, useState } from "react";

// React.memo 封装了 Son 组件,使其在 props 没发生变化时不会重新渲染
const MemoSon1 = React.memo(function Son(props) {
    console.log('子组件MemoSon1被重新渲染了');
    return <div>this is MemoSon1</div>;
})

const MemoSon2 = React.memo(function Son(props) {
    console.log('子组件MemoSon2被重新渲染了');
    return <div>this is MemoSon2</div>;
})

function App() {
    const [count, setCount] = useState(0); // 使用 useState 定义状态变量 count
    const onGetSon1Message = (message) => {
        console.log(message); // 用于处理来自 MemoSon1 组件的消息
    }

    // 使用 useCallback 来定义一个记忆化的回调函数,用于处理来自 MemoSon2 组件的消息
    const onGetSon2Message = useCallback((message) => {
        console.log(message);
    }, []); // 空依赖数组表示这个回调函数的引用是固定的,因此不会在重新渲染时改变

    return (
        <>
            <button onClick={()=>setCount(count + 1)}>按钮1</button> {/* 点击按钮来更新状态 count */}
            <MemoSon1 onGetSonMessage={onGetSon1Message} /> {/* 直接渲染 MemoSon1 组件,传递处理消息的回调函数 */}
            <MemoSon2 onGetSonMessage={onGetSon2Message}/> {/* 直接渲染 MemoSon2 组件,传递处理消息的回调函数 */}
        </>
    );
}

export default App;


14 React.forwardRef

  • React.forwardRef作用:允许组件使用ref将一个DOM节点暴露给父组件

14.1 React.forwardRef 基本使用

  • React.forwardRef((props, ref) => {})
    请添加图片描述
import React, { useRef } from "react";

// 使用forwardRef函数创建一个MyDiv组件,并将ref参数传递给内部的div元素
const MyDiv = React.forwardRef((props, ref) => {
    return <div ref={ref}>this is MyDiv</div>;
});

function App() {
    // 创建一个ref对象
    const ref = useRef();

    // 当按钮被点击时执行的操作
    const showRef = () => {
        console.log(ref); // 打印ref对象
        console.log(ref.current.innerText); // 打印div当前的innerText

        // 修改div的innerText
        ref.current.innerText = "New value";

        console.log(ref.current.innerText); // 打印修改后的innerText
    };

    // 返回一个组件,包含一个MyDiv和一个按钮,按钮的点击事件绑定了showRef函数
    return (
        <>
            <MyDiv ref={ref} /> {/* MyDiv组件将ref对象传递给内部的div元素 */}
            <button onClick={showRef}>按钮</button> {/* 按钮点击时执行showRef函数 */}
        </>
    );
}

export default App;


15 React.useImperativeHandle

作用:允许组件使用ref将多个内部方法暴露给父组件


15.1 React.useImperativeHandle 基本使用

  • 需要 React.forwardRef 和 React.useImperativeHandle 联合使用
  • React.useImperativeHandle(ref, () => { return {方法名} });
    请添加图片描述
import React, {useRef, useState, forwardRef, useImperativeHandle} from "react";

// 使用forwardRef函数创建一个MyDiv组件,并将ref参数传递给内部的div元素
const MyDiv = forwardRef((props, ref) => {
    // 在MyDiv组件中使用useState来定义一个内部状态变量message,并提供可以更新状态的方法updateMessage
    const [message, setMessage] = useState("this is MyDiv");

    // 定义updateMessage函数,用来更新message状态变量
    const updateMessage = (message) => {
        setMessage(message)
    }

    // 使用useImperativeHandle来向父组件暴露特定的方法
    React.useImperativeHandle(ref, () => ({
        updateMessage // 向外暴露 updateMessage 方法
    }));

    // 返回一个带有ref属性的div元素,显示message的内容
    return <div ref={ref}>{message}</div>;
});

function App() {
    // 创建一个ref对象
    const ref = useRef();

    // 当按钮被点击时执行的操作
    const showRef = () => {
        console.log(ref); // 打印ref对象
        ref.current.updateMessage("this is App"); //使用传过来对updateMessage方法更新message
    };

    // 返回一个组件,包含一个MyDiv和一个按钮,按钮的点击事件绑定了showRef函数
    return (
        <>
            <MyDiv ref={ref}/> {/* MyDiv组件将ref对象传递给内部的div元素 */}
            <button onClick={showRef}>按钮</button>
            {/* 按钮点击时执行showRef函数 */}
        </>
    );
}

export default App;


16 TypeScript

React和TypeScript集合使用的重点集中在 和存储数据/状态有关的Hook函数 以及 组件接口 的位置,这些地方最需要数据类型校验


16.1 使用 Vite 快速搭建开发环境

Vite: 是一个框架无关的前端工具链工具,可以帮助我们快速创建一个 react+ts 的工程化环境出来,我们可以基于它做语法学习

兼容性注意: Vite 需要 Node.js 版本 18+ 或 20+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本


16.1.1 使用Vite创建项目

npm create vite@latest react-typescript – --template react-ts

  • 它使用npm命令行工具
  • 使用vite@latest作为项目脚手架
  • 创建一个React项目
  • 使用TypeScript作为项目的主要语言
  • 使用vite的react-ts模板来创建项目

16.1.2安装依赖运行项目

  • 安装依赖: npm i
  • 运行项目: npm run dev
    在这里插入图片描述

16.2 删除不需要的文件

  • src目录下只需要保留App.jsmain.js vite-env.d.ts 文件就可以
    在这里插入图片描述
    在这里插入图片描述

App.tsx



function App() {

  return (
    <>
     thi is App

    </>
  )
}

export default App


main.tsx

import ReactDOM from 'react-dom/client'
import App from './App.tsx'

ReactDOM.createRoot(document.getElementById('root')!).render(
    <App />
)

vite-env.d.ts

/// <reference types="vite/client" />


16.3 TypeScript 与 useState

16.3.1 useState-自动推导

  • 通常React会根据传入useState的默认值来自动推导类型,不需要显式标注类型
  • 说明:
  1. flag: 类型为boolean ------- setFlag: 参数类型为boolean
  2. count: 类型为number-------setCount: 参数类型为number

在这里插入图片描述


16.3.2 useState-传递泛型参数

  • useState本身是一个泛型函数,可以传入具体的自定义等类型
  • 说明:
  1. 限制useState函数参数的初始值必须满足类型为: User | ()=> User
  2. 限制setUser函数的参数必须满足类型为:User | ()=> User | undefined
  3. user状态数据具备User类型相关的类型提示

在这里插入图片描述


16.3.3 useState-初始值为null

  • 当我们不知道状态的初始值是什么,将useState的初始值为null是一个常见的做法,可以通过具体类型联合null来做显 式注解
  • 说明:
  1. 限制useState函数参数的初始值可以是 User | null
  2. 限制setUser函数的参数类型可以是 User | null

在这里插入图片描述


16.4 TypeScript 与 props

16.4.1 props 基础使用

  • 为组件prop添加类型,本质是给函数的参数做类型注解,可以使用type对象类型或者interface接口来做注解
    在这里插入图片描述
import React from "react";


// type User = {
//     name: string,
//     address?: string,
//     age?: number
// }


// 定义一个名为User的接口,表示用户对象的类型
interface User {
    name: string,     // 必选属性,表示用户的姓名
    address?: string, // 可选属性,表示用户的地址
    age?: number      // 可选属性,表示用户的年龄
}

// 创建一个名为Son的函数组件,接收一个参数props,props的类型是User
function Son(props: User) {
    // 从props中解构出name, age, address三个属性
    const {name, age, address} = props;

    // 返回一个包含用户信息的React元素
    return (
        <>
            <div>name: {name}</div>
            <div>age: {age}</div>
            <div>address: {address}</div>
            <div>this is Son</div>
        </>
    )
}

// 创建一个名为App的函数组件
function App() {
    // 返回一个包含Son组件的React元素
    return (
        <>
            <Son name={"小白"}/> {/* 调用Son组件,并传入name属性 */}
        </>
    )
}

export default App;


16.4.2 props 类型(React.ReactNode)

  • 如果需要支持多种不同类型数据的传入,需要通过一个内置的ReactNode类型来做注解
  • 说明: 注解之后,children可以是多种类型,包括:React.ReactElement 、string、number、 React.ReactFragment 、React.ReactPortal 、boolean、 null 、undefined
    在这里插入图片描述
import React from "react";

// 定义一个名为User的接口,表示用户对象的类型
interface User {
    name: string | null,            // 必选属性,表示用户的姓名
    children: React.ReactNode, // 必选属性,表示React元素或组件
    children1: React.ReactNode, // 必选属性,表示React元素或组件
}

// 创建一个名为Son的函数组件,接收一个参数props,props的类型是User
function Son(props: User) {
    // 从props中解构出name, children, children1三个属性
    const {name, children, children1} = props;

    // 返回一个包含用户信息的React元素
    return (
        <>
            <div>name: {name}</div>
            <div>children: {children}</div>
            <div>children1: {children1}</div>
            <div>this is Son</div>
        </>
    )
}

// 创建一个名为App的函数组件
function App() {
    // 返回一个包含Son组件的React元素
    return (
        <>
            <Son name={"小白"} children={<div>this is div</div>} children1={"字符串"}/> {/* 调用Son组件,并传入name, children, children1属性 */}
        </>
    )
}

export default App;


16.4.3 为事件prop添加类型

  • 组件经常执行类型为函数的prop实现子传父,这类prop重点在于函数参数类型的注解
  • 说明:
        1. 在组件内部调用时需要遵守类型的约束,参数传递需要满足要求
        2. 绑定prop时如果绑定内联函数直接可以推断出参数类型,否则需要单独注解匹配的参数类型
    请添加图片描述
import React from "react";

// 定义Props类型,包括两个可选属性,一个用于接收消息,另一个用于返回消息
type Props = {
    onGetMsg?: (msg: string) => void,     // 用于接收消息的回调函数
    returnMsg?: (msg: string) => string,  // 用于返回消息的函数
}

// 创建一个名为Son的函数组件,接收一个参数props,其类型为Props
function Son(props: Props) {
    const {onGetMsg, returnMsg} = props;

    // 定义一个点击事件处理函数,当按钮被点击时触发onGetMsg和returnMsg
    const clickHandler = () => {
        onGetMsg?.('this is Son Message'); // 调用onGetMsg并传入字符串参数
        const returnedMessage = returnMsg?.("this is Son + "); // 调用returnMsg并获取返回值
        console.log(returnedMessage); // 打印返回的消息
    }

    return (
        <>
            <button onClick={clickHandler}>按钮</button> {/* 点击按钮会触发clickHandler函数 */}
        </>
    )
}

// 创建一个名为App的函数组件
function App() {
    // 定义一个returnMsg函数,用于处理返回消息
    const returnMsg = (msg: string) => {
        return msg + "this is app"; // 处理收到的消息并附加额外的内容
    }

    return (
        <>
            <Son onGetMsg={(msg) => console.log(msg)}/> {/* 调用Son组件,并传入onGetMsg属性 */}
            <Son returnMsg={returnMsg}/> {/* 调用Son组件,并传入returnMsg属性 */}
        </>
    )
}

export default App;


16.5 TypeScript 与 useRef


16.5.1 获取dom

  • 获取dom的场景,可以直接把要获取的dom元素的类型当成泛型参数传递给useRef,可以推导出.current属性的类型
    请添加图片描述
import React, { useRef } from "react";

function App() {
    const ref = useRef<HTMLDivElement>(); // 创建一个对 div 元素的引用

    return (
        <>
            <div ref={ref}>this is div</div> {/* 将引用赋给 div 元素的 ref 属性 */}


            <button onClick={() => {
                ref.current.innerText = "this is app";
            }}>按钮</button> {/* 点击按钮时更新 div 元素的文本内容 */}
        </>
    )
}

export default App;

16.5.2 引用稳定的存储器

  • 把useRef当成引用稳定的存储器使用的场景可以通过泛型传入联合类型来做
  • 当做为可变存储容器使用的时候,可以通过泛型参数指定容器存入的数据类型, 在还为存入实际内容时通常把null作为初始值,所以依旧可以通过联合类型做指定
    请添加图片描述
import React, { useRef } from "react";

function App() {
    const dataRef = useRef<string | null>(null); // 创建一个对持久性数据的引用,初始值为 null

    const updateData = () => {
        console.log(dataRef.current); // 打印当前 dataRef 中存储的数据(初始值为 null)
        dataRef.current = "this is app"; // 在点击按钮时,更新 dataRef 中存储的数据为 "this is app"
        console.log(dataRef.current); // 打印更新后的 dataRef 中存储的数据
    };

    return (
        <div>
            <button onClick={updateData}>按钮</button> {/* 点击按钮时触发 updateData 函数 */}
        </div>
    );
}

export default App;



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟浩浩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值