React学习笔记,从入门到砸门

server/2024/9/25 17:20:21/

项目构建命令

npx create-react-app react-basic
npx:node语法
create-react-app:项目模板
react-basic:项目名称

项目结构

image.png

项目打包和本地预览

  1. 项目打包npm run build
  2. 本地预览(模拟服务器运行项目)
    1. 安装本地服务包 npm i -g serve
    2. 指定服务启动路径,如./build:serve -s ./build

打包优化 - CDN配置

  1. craco.config.js
javascript">// 扩展webpack的配置const path = require("path");
// 引入辅助函数
const { whenProd, getPlugin, pluginByName } = require("@craco/craco");module.exports = {// webpack 配置webpack: {// 配置别名alias: {// 约定:使用 @ 表示 src 文件所在路径"@": path.resolve(__dirname, "src"),},// 配置CDNconfigure: (webpackConfig) => {let cdn;whenProd(() => {// key: 不参与打包的包(由dependencies依赖项中的key决定)// value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下webpackConfig.externals = {react: "React","react-dom": "ReactDOM",};// 配置现成的cdn资源地址// 实际开发的时候 用公司自己花钱买的cdn服务器cdn = {js: ["https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js","https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js",],};});// 通过 htmlWebpackPlugin插件 在public/index.html注入cdn资源urlconst { isFound, match } = getPlugin(webpackConfig,pluginByName("HtmlWebpackPlugin"));if (isFound) {// 找到了HtmlWebpackPlugin的插件match.options.cdn = cdn;}return webpackConfig;},},
};
  1. index.html:动态创建script标签,导入js文件
<body><div id="root"></div><% htmlWebpackPlugin.options.cdn.js.forEach(cdnURL=> { %><script src="<%= cdnURL %>"></script><% }) %>
</body>

包体积可视化分析

  1. 安装插件:npm i source-map-explorer
  2. 配置命令指定要分析的js文件:一般为打包后的js文件

package.json:

"scripts": {"analyze": "source-map-explorer 'build/static/js/*.js'"
},

配置路径别名

CRA本身把webpack配置包装到了黑盒里无法直接修改,需要借助一个插件 - craco
配置步骤

  1. 安装craco:npm i -D @craco/craco
  2. 项目根目录下创建配置文件craco.config.js
  3. 配置文件中添加路径解析配置
  4. 包文件中配置启动和打包命令
javascript">const path = require('path')module.exports = {// webpack配置webpack: {// 配置别名alias: {// 约定:使用 @ 表示 src 文件所在路径'@': path.resolve(__dirname, 'src'),},},
}
"scripts": {"start": "craco start","build": "craco build"
},

联系路径配置

VsCode的联想配置,需要我们在项目目录下添加jsconfig.json文件,加入配置之后Vscode会自定读取配置帮助我们自动联想提示
配置步骤

  1. 根目录下新增配置文件 - jsconfig.json
  2. 添加路径提示配置
javascript">{"compilerOptions": {"baseUrl": "./","paths": {"@/*": ["src/*"]}}
}

React调试插件

浏览器插件:React Developer Tools

JSX基础

概念和本质

概念:jsx是javaScript和XML(HTML)的缩写,表示在js代码中编写html模板结构,它是react中编写UI模板的方式
image.png
本质:JSX并不是标准的js语法,它是js的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行
image.png
优势

  1. HTML的声明式模板语法
  2. JS的可编程能力

JSX中使用JS表达式

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

  1. 使用引号传递字符串
  2. 使用JavaScript变量
  3. 函数调用和方法调用
  4. 使用JavaScript对象

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

javascript">const count = 100const getName = () => {return 'Bob'
}
function App() {return (<div className="App">{/* 使用引号传递字符串 */}{ '这是一个字符串' }{/* 使用JavaScript变量 */}{ count }{/* 函数调用 */}{ getName() }{/* 方法调用 */}{ new Date().getDate() }{/* 使用JavaScript对象 */}<div style={ {color:'red',fontSize:'30px'} }>this is div</div></div>);
}export default App;

JSX中实现列表渲染

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

  1. 循环哪个结构在map中就return哪个结构
  2. 结构上需要加上key字段,可以是字符串、number类型
  3. key的作用:React框架内部使用,提升更新性能
javascript">const list = [{id:1,name:'Vue'},{id:2,name:'React'},{id:3,name:'Angle'}
]
function App() {return (<div className="App">{/* 渲染列表 */}<ul>{list.map(item => <li key={item.id}>{item.name}</li>)}</ul></div>);
}export default App;

JSX实现条件渲染

语法:在React中,可以通过逻辑与运算符&&、三元里表达式(?:)实现基础的条件渲染
{flag && <span>this is span</span>}:当flag为false时,不显示;当flag为true时,显示标签
{loading ? <span>loading...</span> : <span>this is span</span>}:当loading为true时显示第一个标签;当loading为false时显示第二个标签

javascript">const isLogin = true
function App() {return (<div className="App">{/* 条件渲染 */}{/* 逻辑与 && */}{isLogin && <div>未登录</div>}{/* 三元运算符 */}{isLogin ? <div>已登录(jack)</div> : <div>用户未登录</div>}</div>);
}export default App;

image.png
复杂的条件渲染:

javascript">const count = 1 // 0  1  2const getCount = () => {if(count === 0){return '当前为0'}else if(count === 1){return '当前为1'}else if(count === 2){return '当前为2'}
}
function App() {return (<div className="App">{/* 复杂的条件渲染 */}{getCount()}</div>);
}export default App;

事件绑定

基础事件绑定

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

javascript">function App() {const handleClick = () => {console.log('BUTTON被点击了')}return (<div className="App">{/* 基础事件绑定 */}<button onClick={handleClick}>click事件</button></div>);
}export default App;

使用事件对象参数

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

javascript">function App() {const handleClick1 = (e) => {console.log('button被点击了',e)}return (<div className="App">{/* 使用事件对象参数e */}<button onClick={handleClick1}>click事件</button></div>);
}export default App;

传递自定义参数

语法:事件绑定的位置改造成箭头函数的写法,在执行实际处理业务函数的时候传递实参

javascript">function App() {const handleClick2 = (name) => {console.log('button被点击了',name)}return (<div className="App">{/* 传递自定义参数 */}<button onClick={() => handleClick2('bob')}>click事件</button></div>);
}export default App;

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

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

javascript">function App() {const handleClick3 = (name,e) => {console.log('button被点击了',name,e)}return (<div className="App">{/* 同时传递自定义参数和事件对象 */}<button onClick={(e) => handleClick3('bob',e)}>click事件</button></div>);
}export default App;

组件基础使用

概念:一个组件就是用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以复用多次
语法:在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI,渲染组件只需要把组件当成标签书写即可

javascript">// 定义组件
function Button(){// 组件业务逻辑return <button>click me</button>
}function App() {return (<div className="App">{/* 使用组件方式: */}{/* 1.自闭和 */}<Button/>{/* 2.成对标签 */}<Button></Button></div>);
}export default App;

useState基础使用

:::info
useState是一个React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组建的渲染结果
本质:和普通的JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着变化(数据驱动视图)
:::

  1. useState是一个函数,返回值是一个数组
  2. 数组中的第一个参数是状态变量,第二个参数时set函数用来修改状态变量
  3. useState的参数将作为count的初始值

set函数的作用:

  1. 使用新值更新状态变量
  2. 使用更新后的状态变量重新渲染UI
javascript">import { useState } from "react";function App() {// 1. 调用useState函数创建一个状态变量// count:状态变量// setCount:修改状态变量的方法const [count,setCount] = useState(0)// 2. 点击事件回调const handleClick = () => {// 作用:// 1. 用传入的新值修改count// 2. 使用新的count重新渲染UIsetCount(count+1)}return (<div className="App"><button onClick={handleClick}>{count}</button></div>);
}export default App;

修改状态的规则

  1. 状态不可变:在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新
  2. 修改对象状态:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改
javascript">import { useState } from "react";function App() {let [count,setCount] = useState(0)const handleClick = () => {// 错误写法:直接修改// 1. 值发生了变化// 2. 但无法引发视图更新// count++// console.log(count) // 自增// 正确写法:set函数// 1. 值发生了变化// 2. 视图重新渲染setCount(count + 1)}// 修改对象状态const [form,setForm] = useState({name:'bob'})const changeForm = () => {// 错误写法:直接修改// 1. 值发生了变化// 2. 但无法引发视图更新// form.name = 'tom'// console.log(form) // tom// 正确写法:set函数// 1. 值发生了变化// 2. 视图重新渲染setForm({...form,name:'Tom'})}return (<div className="App"><button onClick={handleClick}>{count}</button><button onClick={changeForm}>修改form-{form.name}</button></div>);
}export default App;

基础样式控制

React组件基础的样式控制有两种方式:

  1. 行内样式 — 不推荐
  2. class类名控制,注意不能使用class,而是使用className — 推荐
.foo{color: blue;
}
javascript">// 导入样式文件
import './index.css'const myStyle = {color:'yellow',fontSize: '50px'
}
function App() {return (<div className="App">{/* 行内样式 */}<span style={{color:'red',fontSize:'40px'}}>this is span</span><span style={myStyle}>this is span</span>{/* class类名控制 */}<span className='foo'>this is class</span></div>);
}export default App;

classnames优化类名控制

classnames是一个简单的JS库,可以非常方便的通过条件动态控制class类名显示
安装npm install classnames
不使用classnames时,动态控制class类名:采用字符串拼接的方式

javascript"><span className={`tab ${item.type === type && 'active'}`}>bob</span>

使用classnames时,动态控制class类名:

javascript"><span className={classNames('tab',{active: item.type === type})}>bob</span>

image.png

表单受控绑定

概念:使用React组件的状态(useState)控制表单的状态
image.png

javascript">import { useState } from 'react';function App() {const [value,setValue] = useState('')return (<div className="App">{/* 通过value属性绑定状态,通过onChange属性绑定状态同步的函数 */}<input value={value} type='text' onChange={(e) => setValue(e.target.value)}/><div>{value}</div></div>);
}export default App;

获取DOM

APIuseRef(null)
使用方法

  1. 使用useRef生成ref对象,绑定到dom标签上

const inputRef = useRef(null)
<input type='text' ref={inputRef}/>

  1. dom可用时,ref.current获取dom,注意:dom在渲染完毕(dom生成)之后才可用

inputRef.current

javascript">// 导入样式文件
import { useRef } from 'react';function App() {// 1. 使用useRef生成ref对象,绑定到dom标签上const inputRef = useRef(null)const showDom = () => {// 2. dom可用时,ref.current获取dom// dom在渲染完毕(dom生成)之后才可用console.dir(inputRef.current)}return (<div className="App"><input type='text' ref={inputRef}/><button onClick={showDom}>获取Dom</button></div>);
}export default App;

组件通信

props说明

  1. props可传递数据类型:

数字、字符串、布尔值、数组、对象、函数、JSX

  1. props是只读对象

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

javascript">function Son(props){console.log(props)// props.age = 32  错误代码,TypeError: Cannot assign to read only property 'age' of object '#<Object>'return (<div>this is Son App,{props.name}</div>)
}function App() {const name = 'this is App'return (<div className="App"><Son name={name} age={18} isTrue={true} list={['vue','react']} obj={{name:'bob'}} cb={() => console.log(123)} child={<span>this is span child</span>}/></div>);
}export default App;

image.png

特殊的prop - children

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

// 子组件中:
function Son(props){return (<div>{props.children}</div>)
}// 父组件中:
<Son><span>this is span</span>
</Son>

比如

javascript">function Son(props){// 2. 子组件通过props参数接收数据console.log(props)return (<div>this is Son App,{props.name}{props.children}</div>)
}function App() {const name = 'this is App'return (<div className="App">{/* 1. 在子组件标签上绑定属性 */}<Son name={name}><span>this is span</span></Son></div>);
}export default App;

image.png

父传子

实现步骤

  1. 父组件传递数据 - 在子组件标签上绑定属性
  2. 子组件接收数据 - 子组件通过props参数接收数据
javascript">function Son(props){// 2. 子组件通过props参数接收数据return (<div>this is Son App,{props.name}</div>)
}function App() {const name = 'this is App'return (<div className="App">{/* 1. 在子组件标签上绑定属性 */}<Son name={name}/></div>);
}export default App;

子传父

实现核心:在子组件中调用父组件中的函数并传递参数
语法

// 子组件:
function Son(props){const {onGetSonMsg,onGetSonName} = propsreturn <button onClick={() => onGetSonMsg(sonMsg) }>点击</button>
}// 父组件:
const getSonMsg = (msg) => {// 接收子组件传递的参数console.log(msg) // this is son msg
}
<Son name={name} onGetSonMsg={getSonMsg}/>

比如

javascript">import { useState } from "react"function Son(props){// 2. 子组件通过props参数接收数据const {onGetSonMsg,onGetSonName} = propsconsole.log(props)const name = 'this is Son'const sonMsg = 'this is son msg'return (<div>this is Son<div>{props.name}</div>{/* 3. 触发父组件传递过来的函数,并传递参数 */}<button onClick={() => onGetSonMsg(sonMsg) }>点击</button><button onClick={() => onGetSonName(name)}>点击2</button></div>)
}function App() {const [sonName,setSonName] = useState('')const name = 'this is App'const getSonMsg = (msg) => {// 4. 接收子组件传递的参数console.log(msg) // this is son msg}const getSonName = (sname) => {// 4. 接收子组件传递的参数setSonName(sname)}return (<div className="App">{/* 1. 在子组件标签上绑定属性 */}<Son name={name} onGetSonMsg={getSonMsg} onGetSonName={getSonName}/><div>{sonName}</div></div>);
}export default App;

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

实现思路:A(子组件) —> App(父组件) —> B(子组件)
实现步骤

  1. A组件先通过子传父的方式把数据传给父组件App
  2. App拿到数据后通过父传子的方式再传递给B组件
javascript">import { useState } from "react"function A({onGetName}) {const aName = 'this is A'return (<div>this is A{/* 1.将数据传递父组件App */}<button onClick={() => onGetName(aName)}>send</button></div>)
}function B(props) {const {name} = propsreturn (<div>this is B<div>{name}</div></div>)
}function App() {const [name,setName] = useState('')const getName = (aName) => {setName(aName)}return (<div className="App"><A onGetName={getName}/>{/* 2.将数据传递给子组件B */}<B name={name}/></div>);
}export default App;

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

语法createContextuseContext
实现步骤

  1. 使用createContext方法创建一个上下文对象Ctx
  2. 在顶层组件(App)中通过Ctx.Provider组件提供数据
  3. 在底层组件(B)中通过useContext钩子函数获取数据
import { createContext,useContext } from "react"// 1. 使用createContext创建一个上下文对象
const MsgContext = createContext()function B() {// 3. 在底层组件中通过useContext钩子函数使用数据const msg = useContext(MsgContext)return (<div>this is B<div>{msg}</div></div>)
}function A() {return (<div>this is A<B/></div>)
}function App() {const msg = 'this is App msg'return (<div className="App">{/* 2. 在顶层组件中通过Provider组件提供数据 */}<MsgContext.Provider value={msg}>this is App<A/></MsgContext.Provider></div>);
}export default App;

传递状态(useState):

javascript">import { createContext,useContext,useState } from "react"// 1. 使用createContext创建一个上下文对象
const NameContext = createContext()function B() {// 3. 在底层组件中通过useContext钩子函数使用数据const name = useContext(NameContext)return (<div>this is B<div>{name}</div></div>)
}function A() {return (<div>this is A<B/></div>)
}function App() {const [name,setName] = useState('bob')return (<div className="App">{/* 2. 在顶层组件中通过Provider组件提供数据 */}<NameContext.Provider value={name}>this is App<button onClick={() => setName('tom')}>改变name</button><A/></NameContext.Provider></div>);
}export default App;

useEffect

概念理解和基础使用

概念:useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送Ajax请求,更改Dom等等
语法useEffect(() => {}, [])
参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
参数2是一个数组(可选参数),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次

import { useEffect, useState } from "react";const URL = 'http://geek.itheima.net/v1_0/channels'function App() {// 创建一个状态const [list,setList] = useState([])useEffect(() => {// 获取列表数据async function getList(){const res = await fetch(URL)const jsonRes = await res.json()console.log(jsonRes)setList(jsonRes.data.channels)}getList()},[])return (<div className="App">this is App<ul>{list.map(item => <li key={item.id}>{item.name}</li>)}</ul></div>);
}export default App;

依赖项参数说明

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现

依赖项副作用函数执行时机
没有依赖项组件初始渲染 + 组件更新时执行(如状态变化等)
空数组依赖只在初始渲染时执行一次
添加特定依赖项组件初始渲染 + 特性依赖项变化时执行
import { useEffect, useState } from "react";function App() {// 1. 没有依赖项:初始 + 组件更新const [count, setCount] = useState(0)// 改变count会触发,组件发生变化都会触发useEffect(() => {console.log('没有依赖项的副作用函数')})// 2. 传入空数组依赖:初始执行一次// 只在初始执行一次useEffect(() => {console.log('空数组的副作用函数')},[])// 3. 传入特定依赖项:初始 + 依赖项变化时执行const [name,setName] = useState('Tom')// 改变name会触发,同时也会触发第一个useEffectuseEffect(() => {console.log('传入特定依赖项的副作用函数')},[name])return (<div className="App">this is App<button onClick={() => setCount(count + 1)}>+{count}</button><button onClick={() => setName(name + '+')}>update:{name}</button></div>);
}export default App;

清除副作用

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

import { useEffect, useState } from "react";function Son(){// 在初始执行一次// 实现副作用操作逻辑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>this is App{show && <Son/>}<button onClick={() => setShow(false)}>销毁Son组件</button></div>);
}export default App;

自定义Hook实现

概念:zidingyiHook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用
实现步骤

  1. 声明一个以use打头的函数
  2. 在函数体内封装可复用的逻辑
  3. 把组件中要用的状态或函数return出去,以对象或者数组的形式
import {useState } from "react";// 自定义的Hook
function useToggle(){const [show,setShow] = useState(true)function toggle (){setShow(!show)}return {show,toggle}
}function App() {const {show,toggle} = useToggle()return (<div>this is App{show && <h2>显示与隐藏</h2>}<button onClick={toggle}>切换</button></div>);
}export default App;

ReactHooks使用规则

使用规则

  1. 只能在组件中或者其他自定义Hook函数中调用
  2. 只能在组件的顶层调用,不能嵌套在if、for、其他函数中
import {useState } from "react";// 错误的用法1:
// useState()  报错function App() {if(Math.random > 5){// 错误的用法2:// useState()  报错}return (<div>this is App</div>);
}export default App;

Redux

什么是Redux?

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

Redux管理数据流程梳理

Redux把整个数据修改的流程分成了三个核心概念,分别是:state、action、reducer

  • state - 一个对象 存放着我们管理的数据状态
  • action - 一个对象 用来描述你想怎么修改数据
  • reducer - 一个函数 根据action的描述生成一个新的state

image.png

配置工具

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

  • Redux ToolKit (RTK) - 官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
    • 简化store的配置方式
    • 内置immer支持可变式状态修改
    • 内置thunk更好的异步创建
  • react-redux - 用来链接Redux和React组件的中间件

image.png

配置基础环境

  1. 使用CRA快速创建React项目

npx create-react-app react-redux

  1. 安装配置工具

npm i @reduxjs/toolkit react-redux

  1. 启动项目

npm run start

store目录结构设计

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

image.png

Redux使用步骤

使用React Toolkit创建counterStore

  • initialState:仓库,store存放位置
  • reducres:修改状态唯一的方法,同步方法
javascript">import { createSlice } from "@reduxjs/toolkit"const countStore = createSlice({name:'couter',// 初始化stateinitialState:{count:0},// 修改状态的方法 同步方法 支持直接修改reducers:{inscrement(state){state.count++},decrement(state){state.count--},addToNum(state,action){// 传递的参数都在action的payload属性中state.count = action.payload}}
})// 解构出actionCreater函数
const {inscrement,decrement,addToNum} = countStore.actions
// 获取reducer
const reducer = countStore.reducer// 以按需导出的方式导出actionCreater
export {inscrement,decrement,addToNum}
// 已默认导出的方式导出reducer
export default reducer

为React注入store

通过react-redux内置Provider组件将store实例注入应用中
主要代码:

<Provider store={store}><App />
</Provider>

完整代码:

javascript">import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';// 导入store
import store from './store';
import { Provider } from 'react-redux';const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(// 注入store<Provider store={store}><App /></Provider>
);// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

React组件使用store中的数据

通过钩子函数useSelector把store中的数据映射到组件中
主要代码:

const {count} = useSelector(state => state.counter)

完整代码:

import { useSelector } from "react-redux";function App() {// 使用store中的数据const {count} = useSelector(state => state.counter)return (<div className="App"><span>{count}</span></div>);
}export default App;

React组件修改store中的数据

通过hook函数useDispatch生成提交action对象的dispatch函数
主要代码:

// 1.定义reducers:
reducers:{decrement(state){state.count--}
}// 2:
const dispatch = useDispatch()// 3:调用reducers
<button onClick={() => dispatch(decrement())}>-</button>

完整代码:

import { useDispatch, useSelector } from "react-redux";
// 导入创建action对象的方法
import { decrement, inscrement } from "./store/modules/counterStore";function App() {// 使用store中的数据const {count} = useSelector(state => state.counter)// 使用dispatch函数修改storeconst dispatch = useDispatch()return (<div className="App">{/* 掉用dispatch提交action对象:修改store */}<button onClick={() => dispatch(decrement())}>-</button><span>{count}</span><button onClick={() => dispatch(inscrement())}>+</button></div>);
}export default App;

提交action传参

在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传递参数,参数会被传递到action对象payload属性上
主要语法:

// 1.定义reducers:
reducers:{addToNum(state,action){// 传递的参数都在action的payload属性中state.count = action.payload}
}// 2.使用reducers:
const dispatch = useDispatch()// 3.传递参数: 
<button onClick={() => dispatch(addToNum(10))}>add to 10</button>

完整代码:

import { useDispatch, useSelector } from "react-redux";// 导入创建action对象的方法
import { addToNum } from "./store/modules/counterStore";function App() {// 使用store中的数据const {count} = useSelector(state => state.counter) // 使用dispatch函数修改storeconst dispatch = useDispatch()return (<div className="App"><span>{count}</span>{/* 掉用dispatch提交action对象:修改store */}{/* 传递参数 */}<button onClick={() => dispatch(addToNum(10))}>add to 10</button><button onClick={() => dispatch(addToNum(20))}>add to 20</button></div>);
}export default App;

异步操作Store

  1. 创建store的写法保持不变,配置好同步修改状态的方法
  2. 单独封装一个函数,在函数内部return一个函数,在新函数中
    1. 封装异步请求获取数据
    2. 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
  3. 组件中dispatch的写法保持不变

主要代码:

javascript">// 1.定义reducers:
reducers:{setChannels(state,action){state.channelList = action.payload}
}
// 2.在store文件中封装异步请求(channelStore.js):
const fetchChannelList = () => {// dispatch是一个固定参数return async dispatch => {const res = await axios.get('http://geek.itheima.net/v1_0/channels')dispatch(setChannels(res.data.data.channels))}
}// 修改状态:
// 3.使用dispatch函数修改store
const dispatch = useDispatch()// 4.使用useEffect触发异步请求执行:
useEffect(() => {dispatch(fetchChannelList())
}, [dispatch])

完整代码:

javascript">import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";const channelStore = createSlice({name:'channel',initialState:{channelList:[]},reducers:{setChannels(state,action){state.channelList = action.payload}}
})// 异步请求 - 主要代码
const {setChannels} = channelStore.actionsconst fetchChannelList = () => {// dispatch是一个固定参数return async dispatch => {const res = await axios.get('http://geek.itheima.net/v1_0/channels')dispatch(setChannels(res.data.data.channels))}
}
// 同步代码导出setChannels,异步代码导出fetchChannelList
export {fetchChannelList}const reducer = channelStore.reducerexport default reducer
javascript">import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";// 导入创建action对象的方法
import { fetchChannelList } from "./store/modules/channelStore";function App() {// 使用store中的数据const {channelList} = useSelector(state => state.channel) // 使用dispatch函数修改storeconst dispatch = useDispatch()// 使用useEffect触发异步请求执行useEffect(() => {dispatch(fetchChannelList())}, [dispatch])return (<div className="App">{/* 渲染异步获取的数据 */}<ul>{channelList.map(item => <li key={item.id}>{item.name}</li>)}</ul></div>);
}export default App;

Redux调试工具

浏览器插件:Redux DevTools

ReactRouter

什么是前端路由

一个路径path对应一个组件component当我们在浏览器中访问一个path的时候,path对应的组件会在页面中进行渲染

安装react-router-dom

npm i react-router-dom

简单使用

image.png

  1. 创建路由
javascript">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
  1. 绑定路由
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {RouterProvider} from 'react-router-dom'// 1. 导入路由
import router from './router';const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(// 2. 绑定路由<RouterProvider router={router}></RouterProvider>
);// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

路由懒加载

  1. 使用React内置的lazy函数进行导入const Home = lazy(() => import("@/pages/Home"))
  2. 使用React内置的Suspense组件包裹路由中的element选项对应的组件<Suspense><Home /></Suspense>
javascript">import { createBrowserRouter } from "react-router-dom";
import Layout from "@/pages/Layout";
import AuthRoute from "@/components/AuthRoute";
import { lazy, Suspense } from "react";// 路由懒加载
// 1. lazy函数对组件进行导入
const Home = lazy(() => import("@/pages/Home"));const router = createBrowserRouter([{path: "/",element: (<AuthRoute><Layout /></AuthRoute>),children: [{index: true,element: (<Suspense><Home /></Suspense> // 2. 使用suspense组件包裹),},],},
]);export default router;

路由导航跳转

什么是路由导航

路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信

声明式导航

声明式导航是指通过在模块中通过<Link/>组件描述出要跳转到哪里去,<Link/>组件会被渲染为浏览器支持的a链接
语法<Link to="/article">文章</Link>

  • to属性:指定跳转的路由path
  • 传参:字符串拼接参数,如/login?id=1001&name=bob
import {Link} from 'react-router-dom'
function Login(){return (<div>这是登录页{/* 声明式导航 */}<Link to="/article">去文章页</Link></div>)
}export default Login

编程式导航

编程式导航是指通过useNavigate钩子得到导航方法,然后通过调用方式以命令式的形式进行路由跳转
语法

  1. const navigate = useNavigate()
  2. navigate('/article')
import {useNavigate} from 'react-router-dom'
function Login(){const navigate = useNavigate()return (<div>这是登录页{/* 编程式导航 */}<button onClick={() => navigate('/article')}>去文章页</button></div>)
}export default Login

路由导航传参

searchParams传参

主要代码:

// 传递:
navigate('/article?id=1001&name=jack')// 接收:
const [params] = useSearchParams()
let id = params.get('id')
let name = params.get('name')

例如:

import {useNavigate} from 'react-router-dom'
function Login(){const navigate = useNavigate()return (<div>这是登录页{/* searchParams传参 */}<button onClick={() => navigate('/article?id=1001&name=jack')}>去文章页</button></div>)
}export default Login
import { useSearchParams } from "react-router-dom"
function Article(){// 接收searchParams传参const [params] = useSearchParams()let id = params.get('id')let name = params.get('name')return (<div>这是文章页{id}-{name}</div>)
}export default Article

params传参

注意:params传参需要在路由定义时,添加占位参数
image.png
主要代码:

// 路由中定义参数:
const router = createBrowserRouter([{path: '/article/:id',element: <Article />}
])// 传递:
navigate('/article/1001')// 接收:
const params = useParams()
let id = params.id

例如:

javascript">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',element: <Article />}
])export default router
javascript">import {useNavigate} from 'react-router-dom'
function Login(){const navigate = useNavigate()return (<div>这是登录页{/* params传参 */}<button onClick={() => navigate('/article/1001')}>去文章页</button></div>)
}export default Login
javascript">import { useParams } from "react-router-dom"
function Article(){// 接收params传参const params = useParams()let id = params.idreturn (<div>这是文章页{id}</div>)
}export default Article

嵌套路由配置

什么是嵌套路由
在一级路由中又内嵌了其他路由,这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由
实现步骤:

  1. 使用children属性配置路由嵌套关系
  2. 使用<Outlet>组件配置二级路由渲染位置

image.png

const router = createBrowserRouter([{path:'/',element: <Layout/>,children:[{path:'/board',element: <Board/>},{path: '/about',element: <About/>}]}
])
javascript">import {Outlet,Link} from 'react-router-dom'function Layout(){return (<div>这是一级路由组件Layout<Link to="/about">去about页</Link><Link to="/board">去board页</Link>{/* 配置二级路由组件渲染位置 */}<Outlet/></div>)
}export default Layout

默认二级路由配置

场景和配置方式:当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true
下例代码解析:当访问localhost:3000时,会渲染Layout组件,同时也会渲染二级组件Board;访问localhost:3000/board时,会报404错误

javascript">const router = createBrowserRouter([{path:'/',element: <Layout/>,children:[// 设置默认二级路由:一级路由访问时,默认二级路由组件也会渲染{index: true,element: <Board/>},{path: '/about',element: <About/>}]}
])

image.png

404路由配置

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

  1. 准备一个NotFound组件
  2. 在路由表数组的末尾,以*号作为路由path配置路由
javascript">const router = createBrowserRouter([...code// 配置404页面{path:'*',element: <NotFound/>}
])

Hash和History路由模式

常用的路由模式有两种,history模式和hash模式,ReactRouter分别由createBrowerRouter(history路由)createHashRouter(hash路由)函数负责创建

路由模式url表现底层原理是否需要后端支持
historyurl/loginhistory对象 + pushState事件需要
hashurl/#/login监听hashChange事件不需要
javascript">const router = createHashRouter([{path: '/login',element: <Login />},{path: '/article/:id',element: <Article />},
])
javascript">const router = createBrowserRouter([{path: '/login',element: <Login />},{path: '/article/:id',element: <Article />}
])

useReducer

  1. 定义一个reducer函数(根据不同的action返回不同的新状态)
  2. 在组件中调用useReducer,并传入reducer函数和状态的初始值
  3. 事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
javascript">import { useReducer } from "react";// 1. 定义一个reducer函数(根据不同的action返回不同的新状态)
function reducer(state, action) {// 根据不同的action type 返回新的stateswitch (action.type) {case "INC":return state + 1;case "DEC":return state - 1;case "SET":return action.payload;default:return state;}
}function App() {// 2. 在组件中调用useReducer,并传入reducer函数和状态的初始值const [state, dispatch] = useReducer(reducer, 0);return (<div>this is App{/* 3. 通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI) */}<button onClick={() => dispatch({ type: "DEC" })}>-</button>{state}<button onClick={() => dispatch({ type: "INC" })}>+</button><button onClick={() => dispatch({ type: "SET", payload: 100 })}>update</button></div>);
}export default App;

useMemo - 不常用

作用:在组件每次重新渲染的时候缓存计算的结果
用途:一般用于消耗非常大的计算
说明:使用useMemo做缓存之后可以保证只有count1依赖项发生变化时才会重新计算
语法

javascript">useMemo(() => {// 根据count1返回计算的结果
}, [count1])

解析:当我们点击按钮改变count1和count2时,组件App会重新渲染,故fib(count1)会重新执行

javascript">import { useMemo, useState } from "react";function fib(n) {console.log("计算函数执行了");if (n < 3) {return 1;}return fib(n - 1) + fib(n - 2);
}function App() {const [count1, setCount1] = useState(0);const result = fib(count1);const [count2, setCount2] = useState(0);console.log("组件重新渲染");return (<div>this is App<button onClick={() => setCount1(count1 + 1)}>change count1: {count1}</button><button onClick={() => setCount2(count2 + 1)}>change count2: {count2}</button>{result}</div>);
}export default App;

image.png
解析:当我们点击按钮改变count1和count2时,组件App会重新渲染,但由于使用useMemoAPI,且依赖项为count1,所以仅在count1发生变化时执行fib函数

javascript">import { useMemo, useState } from "react";function fib(n) {console.log("计算函数执行了");if (n < 3) {return 1;}return fib(n - 1) + fib(n - 2);
}function App() {const [count1, setCount1] = useState(0);const result = useMemo(() => {return fib(count1);}, [count1]);const [count2, setCount2] = useState(0);console.log("组件重新渲染");return (<div>this is App<button onClick={() => setCount1(count1 + 1)}>change count1: {count1}</button><button onClick={() => setCount2(count2 + 1)}>change count2: {count2}</button>{result}</div>);
}export default App;

image.png

React.memo

作用:运行组件在Props没有改变的情况下跳过渲染
React组件默认的渲染机制:只要父组件重新渲染子组件就重新渲染
说明:经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
语法

javascript">const MemoComponent = memo(function SomeComponent(props) {// ...
})

解析:点击按钮改变count时,App组件会重新渲染,Son组件也会重新被渲染;但Son组件没有必要重新渲染,因为没有变化

javascript">import { useState } from "react";function Son() {console.log("son子组件重新渲染了");return <div>this is son</div>;
}function App() {const [count, setCount] = useState(0);return (<div>this is App<button onClick={() => setCount(count + 1)}>+</button>{count}<Son /></div>);
}export default App;

解析:点击按钮改变count时,App组件会重新渲染,MemoSon组件不会重新渲染,因为通过memo包裹的组件在props没有发生变化时,是不会重新渲染的

javascript">import { memo, useState } from "react";const MemoSon = memo(function Son() {console.log("son子组件重新渲染了");return <div>this is son</div>;
});function App() {const [count, setCount] = useState(0);return (<div>this is App<button onClick={() => setCount(count + 1)}>+</button>{count}<MemoSon /></div>);
}export default App;

React.memo - props的比较机制

机制:在使用memo缓存组件之后,React会对每一个prop使用Object.is比较新值和老值,返回true,表示没有变化
prop是简单类型:Object.is(3,3) => true 没有变化
prop引用类型(对象/数组):Object([],[])=>false 有变化,React只关心引用是否变化
注意:引用类型中,变量其实是该引用类型值在内存中的地址,所以如果地址没有变化,也就代表prop没有变化
解析:传递一个简单类型prop,点击按钮App组件会重新渲染,子组件MemoSon不会重新渲染,因为num没有发生变化

javascript">import { memo, useState } from "react";const MemoSon = memo(function Son({ count }) {console.log("son子组件重新渲染了");return <div>this is son -- {count}</div>;
});function App() {const [count, setCount] = useState(0);console.log("App重新渲染");const num = 10;return (<div>{count}<button onClick={() => setCount(count + 1)}>+</button><MemoSon count={num} /></div>);
}export default App;

解析:传递一个引用类型prop,点击按钮App组件会重新渲染,子组件MemoSon也会重新渲染,看似list变量没有发生变化,实际上在App组件重新渲染时,会在内存中新建一个[0,1,2]值的引用地址,即list的值的引用地址发生了改变,所以导致MemoSon组件重新渲染

javascript">import { memo, useState } from "react";const MemoSon = memo(function Son({ list }) {console.log("son子组件重新渲染了");return <div>this is son -- {list}</div>;
});function App() {const [count, setCount] = useState(0);console.log("App重新渲染");const list = [0, 1, 2];return (<div>{count}<button onClick={() => setCount(count + 1)}>+</button><MemoSon list={list} /></div>);
}export default App;

useCallback

作用:使用useCallback包裹函数之后,函数可以保证在App组件重新渲染的时候保持引用稳定
语法:

javascript">const func = useCallback(() => {// ...
}, []);

解析:点击button后App会重新渲染,当App重新渲染时,由于changeHandler是函数(引用类型),引用地址会发生变化,且作为prop传递给了Input组件,故Input组件也会重新渲染

javascript">import { memo, useState } from "react";const Input = memo(({ onChange }) => {console.log("子组件重新渲染了");return <input onChange={(e) => onChange(e.target.value)} />;
});function App() {console.log("App重新渲染");const [count, setCount] = useState(0);// 当App重新渲染时,由于changeHandler函数(引用类型)引用地址会发生变化,// 且作为prop传递给了Input组件,故Input组件也会重新渲染const changeHandler = (value) => {console.log(value);};return (<div><Input onChange={changeHandler} /><button onClick={() => setCount(count + 1)}>{count}</button></div>);
}export default App;

解决:通过useCallback包裹函数,保持引用稳定

javascript">import { memo, useCallback, useState } from "react";const Input = memo(({ onChange }) => {console.log("子组件重新渲染了");return <input onChange={(e) => onChange(e.target.value)} />;
});function App() {console.log("App重新渲染");const [count, setCount] = useState(0);// 通过useCallback包裹函数保持引用稳定,在App重新渲染之后,// changehandler引用地址保持稳定,不会发生变化const changeHandler = useCallback((value) => {console.log(value);}, []);return (<div><Input onChange={changeHandler} /><button onClick={() => setCount(count + 1)}>{count}</button></div>);
}export default App;

React - forwardRef

作用:在父组件中通过ref获取子组件内部的元素
语法forwardRefapi中的ref是固定写法、位置

const Input = forwardRef((props, ref) => {return <input type="text" ref={ref} />;
});const inputRef = useRef(null);
<Input ref={inputRef} />
inputRef.current.focus()

比如
image.png

import { forwardRef, useRef } from "react";const Input = forwardRef((props, ref) => {return <input type="text" ref={ref} />;
});function App() {const inputRef = useRef(null);const getInput = () => {console.log(inputRef.current.focus());};return (<div><Input ref={inputRef} /><button onClick={getInput}>获取input</button></div>);
}export default App;

React - useImperativeHandle

作用:通过ref调用子组件内部的方法
语法:

const Component = forwardRef((props, ref) => {const func = () => {// ...};useImperativeHandle(ref, () => {return {func,};});return <div></div>
});const sonRef = useRef(null);
<Component ref={sonRef} />
sonRef.current.func();

比如
image.png

import { forwardRef, useImperativeHandle, useRef } from "react";const Son = forwardRef((props, ref) => {const inputRef = useRef(null);const focusHandler = () => {inputRef.current.focus();};useImperativeHandle(ref, () => {return {focusHandler,};});return <input type="text" ref={inputRef} />;
});function App() {const sonRef = useRef(null);const focusHandler = () => {sonRef.current.focusHandler();};return (<div><Son ref={sonRef} /><button onClick={focusHandler}>聚焦</button></div>);
}export default App;

Class类组件(不推荐,官方停止更新,推荐使用Hooks写法)

基础结构

  1. 通过类属性state定义状态数据
  2. 通过setState方法来修改状态数据
  3. 通过render来写UI模板(JSX语法一致)
  4. 在类组件中十分依赖this关键字,如访问state、调用事件等
javascript">class Counter extends Component {// 定义状态变量   类似于    useState()state = {count: 0,};// 事件回调clickHandler = () => {// 修改状态数据this.setState({count: this.state.count + 1,});};// UI模板(JSX)render() {return <button onClick={this.clickHandler}>{this.state.count}</button>;}
}export default Counter

生命周期函数

概念:组件从创建到销毁的各个阶段自动执行的函数就是生命周期函数

  1. componentDidMount:组件挂载完毕自动执行,一般用于异步数据获取等
  2. componentWillUnmount:组件卸载时自动执行,一般用于清除副作用等,如清除定时器
javascript">import { Component, useState } from "react";class Son extends Component {// 挂载componentDidMount() {console.log("son componentDidMount");// 设置定时器this.timer = setInterval(() => {console.log("son timer");}, 1000);}// 卸载componentWillUnmount() {console.log("son componentWillUnmount");// 卸载定时器clearInterval(this.timer);}render() {return <div>this is Son</div>;}
}function App() {const [show, setShow] = useState(true);return (<div>{show && <Son />}<button onClick={() => setShow(false)}>unmount</button></div>);
}export default App;

组件通信

概念:类组件和Hooks编写的组件在组件通信的思想完全一致

  1. 父传子:通过prop绑定数据
  2. 子传父:通过prop绑定父组件中的函数,子组件调用
  3. 兄弟通信:状态提升,通过父组件做桥接
javascript">import { Component } from "react";class Son extends Component {render() {return (<div>this is Son - {this.props.name}<button onClick={() => this.props.onGetSonName("wangwu")}>wangwu</button></div>);}
}class Parent extends Component {state = {name: "zhangsan",};getSonName = (sonName) => {this.setState({name: sonName,});};render() {return (<div>this is parent<Son name={this.state.name} onGetSonName={this.getSonName} /></div>);}
}export default Parent;

http://www.ppmy.cn/server/104332.html

相关文章

水战再起波澜,“怡宝”要下好怎样一盘棋?

不少投资者常把那些刚需性强、永远也不可能淘汰的产业称为“日不落产业”&#xff0c;从细分板块来看&#xff0c;水无疑具有一定代表性。农夫山泉掌门人钟晱晱曾直言&#xff1a;“我选择了一个日不落的产业&#xff0c;你永远要喝水&#xff0c;不可能不喝水。” 多年下来&a…

读懂 GraphRAG:提升LLM企业落地能力,智能问答革命

在企业中单纯的使用LLM并不会产生太好的效果&#xff0c;因为它们不会对有关组织活动的特定领域专有知识进行编码&#xff0c;而这些知识实际上会给信息对话界面带来价值萃取。很多企业尝试通过RAG来优化这个过程&#xff0c;并且越来越多的人在RAG的方向上不断的研究&#xff…

PythonStudio 控件使用常用方式(二十七)TActionList

PythonStudio是一个极强的开发Python的IDE工具&#xff0c;官网地址是&#xff1a;https://glsite.com/ &#xff0c;在官网可以下载最新版的PythonStudio&#xff0c;同时&#xff0c;在使用PythonStudio时&#xff0c;它也能及时为用户升到最新版本。它使用的是Delphi的控件&…

运维大规模K8S集群注意事项

序言 闲来无事&#xff0c;一片混沌&#xff0c;想不清思不断&#xff0c;改变好像来自于各个方面&#xff0c;有的时候是内部的冲突&#xff0c;有的时候是外部的竞争&#xff0c;然而&#xff0c;大部分情况下&#xff0c;一旦错过&#xff0c;就已经没得选了。 尴尬的处境&a…

Apache Spark 的基本概念和在大数据分析中的应用。

Apache Spark 是一个开源的大数据处理引擎&#xff0c;它提供了高效的分布式计算能力和内置的机器学习库&#xff0c;用于处理和分析大规模数据集。Spark 是基于内存的计算框架&#xff0c;可以在大型集群上并行处理数据&#xff0c;并且具有高度可伸缩性和容错性。 Spark 的核…

【机器学习】线性回归

一、什么是回归 分类任务很好理解&#xff0c;比如去银行贷款&#xff0c;银行会根据贷款人的年龄、工资&#xff08;特征&#xff09;去决定贷款&#xff08;标签1&#xff09;和不贷款&#xff08;标签0&#xff09;。而回归任务&#xff0c;是预测允许贷款的额度&#xff08…

网络硬件升级指南:提升性能的策略与实践

随着企业对网络依赖程度的增加&#xff0c;网络性能的提升已成为信息技术部门的首要任务。本文将探讨如何通过升级网络硬件来提高网络性能&#xff0c;包括选择正确的硬件、实施升级策略和考虑未来网络的可扩展性。 一、网络性能的重要性 在数字化时代&#xff0c;网络是企业…

echart改变legend样式及分页

legend: {type: "scroll",orient: horizontal, // 纵向&#xff0c;默认横向不用写pageIconColor: #1b9aee, //翻页下一页的三角按钮颜色pageIconInactiveColor: #7f7f7f, //翻页&#xff08;即翻页到头时&#xff09;// 配置滚动类型的图例pageTextStyle: {color: &…