目录
- 1. 使用create-react-app快速搭建开发环境
- 2. JSX基础-高频场景
- 3. React中的事件绑定
- 4.React中的组件
- 5. useState 与 useReducer
- 6.组件的样式处理
- 7.useRef (获取DOM)
- 8. 组件通信
- 9. useEffect 与useMemo 的使用
- 10. Redux
- 11. ReactRouter
- 12. 封装 Request 请求
- 13 React.memo
- 14 React.forwardRef
- 15 React.useImperativeHandle
- 16 TypeScript
1. 使用create-react-app快速搭建开发环境
create-react-app是一个快速 创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用
1.1 执行命令:
npx create-react-app react-basic
- npx Node.js工具命令,查找并执行后续的包命令
- create-react-app 核心包(固定写法),用于创建React项目
- 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 使用状态提升实现兄弟组件通信
实现思路:借助“状态提升”机制,通过父组件进行兄弟组件之间的数据传递
- Son1组件先通过子传父的方式把数据传给父组件App
- 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机制跨层级组件通信
只要是组件是包含性质就可以使用
实现步骤:
- 使用createContext方法创建一个上下文对象Ctx
- 在顶层组件(App)中通过 Ctx.Provider 组件提供数据
- 在底层组件(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 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
作用:
通过集中管理的方式管理应用的状态
10.1 Redux与React - 环境准备
10.1.1安装配套工具命令: npm i @reduxjs/toolkit react-redux
在React中使用redux,官方要求安装俩个其他插件 -
Redux Toolkit
和react-redux
Redux Toolkit(RTK):
官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式react-redux:
用来 链接 Redux 和 React组件 的中间件
10.1.2 store目录结构设计
- 通常集中状态管理的部分都会单独创建一个单独的
store
目录- 应用通常会有很多个子store模块,所以创建一个
modules
目录,在内部编写业务分类的子store- 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>
{/* 在页面中渲染 count 状态 */}
{count}
{/* "-" 按钮,点击时调用 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
npm命令:
npm install json-server
- 使用一个 JSON 文件来定义 API 的数据结构和数据
- 您可以创建一个名为 db.json 的文件,其中包含模拟的数据
- 使用 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 环境准备
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基础使用 中的组件配置
- 使用 children 属性配置路由嵌套关系
- 使用
<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 请求
业务背景: 前端需要和后端拉取接口数据,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.js
、main.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的默认值来自动推导类型,不需要显式标注类型
- 说明:
- flag: 类型为boolean ------- setFlag: 参数类型为boolean
- count: 类型为number-------setCount: 参数类型为number
…
16.3.2 useState-传递泛型参数
- useState本身是一个泛型函数,可以传入具体的自定义等类型
- 说明:
- 限制useState函数参数的初始值必须满足类型为: User | ()=> User
- 限制setUser函数的参数必须满足类型为:User | ()=> User | undefined
- user状态数据具备User类型相关的类型提示
…
16.3.3 useState-初始值为null
- 当我们不知道状态的初始值是什么,将useState的初始值为null是一个常见的做法,可以通过具体类型联合null来做显 式注解
- 说明:
- 限制useState函数参数的初始值可以是 User | null
- 限制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;