文章目录
react
1. 介绍
React是Meta公司研发,是一个用于构建Web和原生交互界面的库
2. 开发环境搭建
方式一:使用create-react-app快速搭建开发环境
create-react-app是一个快速 创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用
执行命令:
npx create-react-app my-react-demp
1.npx Node.js
: 工具命令,查找并执行后续的包命令
2.create-react-app
: 核心包(固定写法),用于创建React项目
3. my-react-demp
: React项目的名称(可以自定义)
看主要两个文件
方式二:使用vite快速搭建开发环境
3. JSX
3.1 什么是JSX
概念:JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写UI模版的方式
优势:
1.HTML的声明式模版写法
2.JS的可编程能力
3.2 JSX的本质
JSX并不是标准的JS语法,它是JS的语法扩展
,浏览器本身不能识别,需要通过解析工具做解析
之后才能在浏览器中运行
3.3 JSX基本语法
在JSX中可以通过 大括号语法{ }
识别 JavaScrip中的表达式 ,比如常见的变量、函数调用、方法调用等等
JSX中使用JS表达式
1.使用引号传递字符串
2.使用JavaScript变量
3.函数调用和方法调用
4.使用JavaScript对象
注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中
示例代码:
const userName = "codeSE"
const getName = () => {
return '上课就迟到'
}
const flag = false
function App () {
return (
<div className="App">
<h1>
MyApp
</h1>
<div>
{/* 使用引号传递字符串 */}
{"this is a test app"}
{/* 使用js变量 */}
{userName}
{/* 使用函数和方法调用 */}
{getName()}
{new Date().getTime()}
{/* 使用javascrpipt对象 */}
<div style={{ width: '100px', height: '100px', color: 'red' }}>110</div>
{/* 使用三元运算符 */}
{flag ? '对' : '错'}
</div>
</div>
)
}
export default App
JSX中实现列表渲染
示例代码:
//列表渲染
//map 重复渲染谁就return谁
//注意:key值,需要一个类型为number/string不可重复的key,提高diff性能
const loveList = [
{ id: 1, name: '爱情转移' },
{ id: 2, name: '爱情重点' },
{ id: 3, name: '爱情码头' },
{ id: 4, name: '爱情买卖' },
{ id: 5, name: '爱情终点' },
{ id: 6, name: '爱情舞曲' },
]
function App() {
return (
<div className="App">
<h1>
{loveList.map(item => <p key={item.id}>{item.name}</p>)}
</h1>
</div>
);
}
export default App;
JSX中实现条件渲染
示例代码:
//条件渲染
//技术方案:三元表达式、逻辑&&运算
//复杂的多分支逻辑,使用函数来写分支逻辑,模板只负责调用
const flag = true
function getTag (type) {
if (type === 1) {
return <h1> this is h1</h1>
}
if (type === 2) {
return <h2> this is h2</h2>
}
if (type === 3) {
return <h3> this is h3</h3>
}
}
function App () {
return (
<div className="App">
<h1>
{flag ? (
<div>
<span>条件渲染</span>
</div>
) : null}
</h1>
{false && <span>this is React</span>}
{getTag(1)}
{getTag(2)}
{getTag(3)}
</div>
)
}
export default App
4. react事件绑定
基础语法: on+事件名称={事件处理程序}
,整体上遵循驼峰命名法
传递自定义参数
语法:事件绑定的位置改造成箭头函数的写法
,在执行clickHandler实际处理业务函数的时候传递实参
同时传递事件对象和自定义参数
语法:在事件绑定的位置传递事件实参e和自定义参数,点击处理程序中声明形参,注意顺序对应
示例代码:
import React from 'react'
//1.函数组件创建和渲染
function Hello () {
const clickHandle = (e) => {
console.log('事件触发', e)
//阻止a链接默认行为
e.preventDefault()
}
return <a href='https:baidu.com' onClick={clickHandle}>a链接</a>
}
//传递额外参数
function Hello2 () {
const clickHandle2 = (msg) => {
console.log(msg)
}
return <div href='https:baidu.com' onClick={() => clickHandle2('code se 123')}>额外参数</div>
}
function Hello3 () {
const clickHandle3 = (e, msg) => {
console.log('事件触发:', e, '参数:', msg)
}
return <div onClick={(e) => clickHandle3(e, 'code se 123')}>事件和额外参数</div>
}
//类组件的创建和渲染
class HelloComponent extends React.Component {
//事件回调函数(标准写法,避免this指向问题)
//this执行当前的组件实列对象
clickHander = () => {
console.log('类组件事件触发!!!!')
}
render () {
return <div onClick={this.clickHander}>HelloComponent</div>
}
}
function App () {
return (
<div className="App">
<Hello />
<Hello></Hello>
<Hello2></Hello2>
<Hello3></Hello3>
<HelloComponent />
<HelloComponent></HelloComponent>
</div>
)
}
export default App
5. react组件
在React中,一个组件就是首字母大写的函数
,内部存放了组件的逻辑和视图U,渲染组件只需要把组件当成标签书写
即可
示例代码:
import React from 'react'
//1.函数组件创建和渲染
function Hello () {
return <div>hello react</div>
}
//类组件的创建和渲染
class HelloComponent extends React.Component {
render () {
return <div>HelloComponent</div>
}
}
function App () {
return (
<div className="App">
<Hello />
<Hello></Hello>
<HelloComponent />
<HelloComponent></HelloComponent>
</div>
)
}
export default App
6. useState
useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量
,从而控制影响组件的渲染结果
本质:和普通JS变量不同的是,状态变量一旦发生变化组件的视图U也会跟着变化(数据驱动视图)
7. react 样式处理
示例代码:
//jsx样式控制
//1.行内样式---- 在元素身上绑定一个style即可
//2.类名样式---- 在元素身上绑定一个className即可
import './app.css';
const myStyle = {
color:'yellow',
fontSize:'40px'
}
//动态样式,满足条件才有
const activeFlag = false;
function App() {
return (
<div className="App">
<p style={{color:'red',fontSize:'30px'}}>hi测试v</p>
<p style={ myStyle }>hi测试v</p>
<p className='active'>类名测试</p>
<p className={activeFlag?'active':''}>动态类名测试</p>
</div>
);
}
export default App;
8. 受控组件
受控表单绑定
概念:使用React组件的状态(useState)控制表单的状态
示例代码:
import { useState } from 'react'
//1.通过value属性绑定react状态
//2.绑定onChange事件 通过事件参数e拿到输入框最新的值 反向修改到react状态
function App() {
const [value, setValue] = useState('')
return (
<div className="App">
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
绑定值:{value}
</div>
)
}
export default App
React 获取DOM
示例代码:
9. 组件通信
概念:组件通信就是组件之间的数据传递
,根据组件嵌套关系的不同,有不同的通信方法
父传子
实现步骤:
-
1.父组件传递数据-在子组件标签上
绑定属性
-
2.子组件接收数据-子组件通过
props
参数接收数据 -
示例代码:
//父传子 props
import React from 'react'
//函数组件Son
function FunSon (props) {
//props是一个对象,里面存着通过父组件传来的所有数据
return (<div>函数组件:{props.msg}</div>)
}
//类组件Son
class MySon extends React.Component {
//类组件必须通过this关键字,去获取这里props是固定
render () {
return (
<div>
类组件的:{this.props.msg}
</div>
)
}
}
class App extends React.Component {
//父组件数据
state = {
message: ' code se is good'
}
render () {
return (
<div>
{/* 子组件绑定属性,属性名可以自定义,保持语义化 */}
<FunSon msg={this.state.message} ></FunSon>
<MySon msg={this.state.message}></MySon>
</div >
)
}
}
export default App
父传子-props说明:
1.props可传递任意的数据数字、字符串、布尔值、数组、对象、函数、JSX
2.props是只读对象
子组件只能读取props中的数据
,不能直接进行修改,父组件的数据只能由父组件修改
父传子-特殊的prop children
场景:当我们把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容
示例代码:
function FunSon(props) {
console.log(props)
return <div>这是:{props.children}</div>
}
function App() {
return (
<div>
<FunSon>
<p>codeSE</p>
</FunSon>
</div>
)
}
export default App
子传父
示例代码:
import React from 'react'
//子传父 :子组件调用父组件传过来的函数,并把想传过来的函数当成函数的实参
function FunSon1 (props) {
const { getMeg1 } = props
return (
<div>
<button onClick={() => getMeg1('HHHHH')}>子组件传父1</button>
</div>
)
}
function FunSon2 (props) {
const { getMeg2 } = props
const a = 'BBBBBBBB'
function handle () {
getMeg2(a)
}
return (
<div>
<button onClick={handle}>子组件传父2</button>
</div>
)
}
class App extends React.Component {
//使用函数传给子组件
getMessage1 = (msg) => {
console.log('来自子组件:', msg)
}
getMessage2 = (msg) => {
console.log('来自子组件:', msg)
}
render () {
return (
<div>
<FunSon1
getMeg1={this.getMessage1}
></FunSon1>
<FunSon2
getMeg2={this.getMessage2}>
</FunSon2>
</div >
)
}
}
export default App
使用状态提升实现兄弟组件通信
示例代码:
import React from 'react'
//兄弟组件之间传值
function SonA (props) {
return (
<div>
A:{props.sendMsg}
</div>
)
}
function SonB (props) {
const { getMsg } = props
const b = 'B组件的数据'
return (
<div>
B
<button onClick={() => getMsg(b)}>组件</button>
</div>
)
}
class App extends React.Component {
state = {
message: "A组件初始值"
}
getMsgA = (b) => {
console.log(b)
this.setState({
message: b
})
}
render () {
return (
<div>
<SonA sendMsg={this.state.message}
></SonA>
<SonB getMsg={this.getMsgA}>
</SonB>
</div >
)
}
}
export default App
使用context跨层传递数据
示例代码:
import React, { createContext } from 'react'
//语法固定:提供位置由value提供数据,获取位置{value => <tag> {value}</tag>}做接收
//1. 创建Context对象,提供者和消费者
const { Provider, Consumer } = createContext()
function AComponent () {
return (
<div> this is A
<CComponent></CComponent>
</div>
)
}
function CComponent () {
return (
//3、通过Consumer使数据
<div>
this is C
<Consumer>
{value => <span> {value}</span>}
</Consumer>
</div>
)
}
class App extends React.Component {
state = {
message: 'this is code SE'
}
render () {
return (
//2。使用Provider包裹根组件
<Provider value={this.state.message}>
<div>
<AComponent></AComponent>
</div>
</Provider>
)
}
}
export default App
10. useEffect 的使用
概念:useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作
,比如发送AJAX请求,更改DOM等等
useEffect 依赖项参数说明
useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同
,会有不同的执行表现
示例代码:
import { useState, useEffect } from 'react'
//在修改数据之后,把count值放在页面标题中
//1、导入useEffect函数
//2、在 函数组件中执行, 传入回调 并且定义副作用
//3、当修改状态值更新组件时,副作用也会不断执行
/**
* 依赖项控制副作用的执行时机
* 1.默认状态(无依赖项)
* 组件初始化时执行一次,等到每次数据更新再次执行
*
* 2.添加空数组依赖项
* 组件初始化时执行一次,后面更新不在执行了
*
* 3.依赖特定项
* 组件初始化时执行一次,依赖的特定项发生变化会再次执行
*
* 4.注意事项
* 只要在useEffect回调函数中用到的数据状态就应该出现在依赖项数组中,否则会有bug
*
* 某种意义上:hooks的出现,就是想不用生命周期概念也可以写业务代码
*/
function App () {
const [count, setCount] = useState(0)
const [name, setName] = useState('HH')
useEffect(() => {
//定义副作用
console.log('执行了副作用')
document.title = count
}, [count])
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}+</button>
<button onClick={() => setName('SE')}>{name}+</button>
</div>
)
}
export default App
useEffect——清除副作用
在useEffect中编写的由渲染本身引起的对接组件外部的操作
,社区也经常把它叫做副作用操作
,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用
示例代码:
import { useState, useEffect } from 'react'
/**
* Hooks 进阶
实现步骤
1. useEffect return()=>{}
2.
3.
*/
function Test () {
useEffect(() => {
let timer = setInterval(() => {
console.log('定时器')
}, 1000)
return () => {
//清理动作(组件卸载时)
clearInterval(timer)
}
}, [])
return (
<div>
this is code SE!
</div>
)
}
function App () {
const [flag, setFlag] = useState(true)
return (
<>
{flag ? <Test></Test> : null}
<button onClick={() => setFlag(!flag)}>{flag ? '隐藏组件' : '显示组件'}</button>
</>
)
}
export default App
11. 自定义Hook函数
概念:自定义Hook是以 use 打头的函数
,通过自定义Hook函数可以用来实现逻辑的封装和复用
自定义hook:
import { useState, useEffect } from 'react'
/*封装自定义hook通用思路
1.声明一个以use打头的函数
2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)
3.把组件中用到的状态或者回调return出去(以对象或者数组)
4.在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
*/
export function useLocalStorage (key, value) {
const [message, setMessage] = useState(value)
//只要数据有变化就自动同步到本地存储
useEffect(() => {
window.localStorage.setItem(key, message)
}, [message, key])
return [message, setMessage]
}
使用:
import { useLocalStorage } from './hooks/useLocalStorage'
function App () {
const [message, setMessage] = useLocalStorage('hook-key', 'Code SE')
setTimeout(() => {
setMessage('XLXC')
}, 3000)
return (
<div style={{ height: '1200px' }}>
{message}
</div>
)
}
export default App
React Hooks使用规则
使用规则
1.只能在组件中或者其他自定义Hook函数中调用
2.只能在组件的顶层调用,不能嵌套在if、for、其他函数中
以下写法不允许:
12.Redux—状态管理工具
Redux是React最常用的集中状态管理工具
,类似于Vue中的Pinia(Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态
Redux快速体验
不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器
使用步骤:
1.定义一个 reducer 函数
(根据当前想要做的修改返回一个新的状态)
2.使用createStore方法传入reducer函数 生成-个store实例对象
3.使用store实例的 subscribe
方法 订阅数据的变化(数据一旦变化,可以得到通知)
4.使用store实例的 dispatch方法提交action对象
触发数据变化(告诉reducer你想怎么改数据)
5.使用store实例的 getState
方法 获取最新的状态数据更新到视图中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="decrement">-</button>
<span id="count">0</span>
<button id="increment">+</button>
<script src="https://cdn.jsdelivr.net/npm/redux@latest/dist/redux.js"></script>
<script>
// 1.定义reducer函数
// 作用:根据不同的action对象,返回不同的新的state
// state:管理的数据初始状态
// action:对象 type 标记当前想要做什么样的修改
function reducer (state = { count: 0 }, action) {
//数据不可变:基于原始状态生成一个新的状态
if (action.type == "ADD") {
return { count: state.count + 1 }
}
if (action.type == "DEC") {
return { count: state.count - 1 }
}
return state
}
//2.使用reducer函数生成store实例
const store = Redux.createStore(reducer)
//3.通过store实例的subscribe订阅数据变化
// 回调函数可以在每次state发生变化的时候自动执行
store.subscribe(() => {
console.log("数据变化了:", store.getState())
document.getElementById("count").innerHTML = store.getState().count
})
// 4.通过store实例的dispatch函数提交action更改状态
const inBtn = document.getElementById('increment')
inBtn.addEventListener('click', () => {
// 增
store.dispatch({
type: "ADD"
})
})
const dBtn = document.getElementById('decrement')
dBtn.addEventListener('click', () => {
// 减
store.dispatch({
type: "DEC"
})
})
//5.通过store实例的getState方法获取最新状态更新到视图中
</script>
</body>
</html>
Redux管理数据流程梳理
为了职责清晰,数据流向明确,Redux把整个数据修改的流程分成了三个核心概念
,分别是:state、action和reducer
1.state-一个对象 存放着我们管理的数据状态
2.action-一个对象 用来描述你想怎么改数据
3.reducer–个函数 更具action的描述生成一个新的state
Redux与React-环境准备
配套工具
在React中使用redux,官方要求安装俩个其他插件-ReduxToolkit和react-redux
1.Redux Toolkit(RTK)-官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
2.react-redux-用来链接 Redux和 React组件 的中间件
安装配套工具
npm i @reduxjs/toolkit react-redux
store目录结构设计
1.通常集中状态管理的部分都会单独创建一个单独的store
目录
2.应用通常会有很多个子store模块,所以创建一个modules
目录,在内部编写业务分类的子store
3.store中的入口文件 index.js 的作用是组合modules中所有的子模块,并导出store
步骤一:store/modelus/counterStore.js
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// 初始状态数据
initialState: {
count: 0
},
// 修改数据的同步方法
reducers: {
increment (state) {
state.count++
},
decrement (state) {
state.count--
},
addTonum (state, action) {
state.count = action.payload
}
}
})
// 解构出创建action对象的函数
const { increment, decrement, addTonum } = counterStore.actions
// 获取reducer函数
const reducer = counterStore.reducer
//以按需导出的方式导出actionCreater
export { increment, decrement, addTonum }
//以默认导出的方式导出reducer
export default reducer
步骤二:store/index.js
//导入配置方法
import { configureStore } from "@reduxjs/toolkit"
//导入子模块
import counterReducer from './modules/counterStore'
//根store
const store = configureStore({
//聚合子模块
reducer: {
counter: counterReducer,
}
})
//导出根
export default store
步骤三:为React注入store
react-redux负责把Redux和React链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中链接正式建立
import React from 'react'//React:框架核心包
import ReactDOM from 'react-dom/client'//专门做渲染的包
import store from './store'
import { Provider } from 'react-redux'
//渲染根组件到id为root的节点上
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
//严格模式节点需要去掉 userEffect的执行时机
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
步骤四:React组件使用store中的数据和修改store中的数据
在React组件中使用store中的数据,需要用到一个钩子函数-useSelector
,它的作用是把store中的数据映射到组件使用
React组件中修改store中的数据需要借助另外一个hook函数-useDispatch
,它的作用是生成提交action对象的dispatch函数
使用样例如下:
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
//导入actionCreater
import { increment, decrement } from './store/modules/counterStore'
function App() {
const { count } = useSelector((state) => state.counter)
const dispatch = useDispatch()
return (
<>
<div>来自redux状态的数据:{count}</div>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(increment())}>+</button>
</>
)
}
export default App
提交action传参实现需求:
在reducers的同步修改方法中 添加action对象参数
,在 调用actionCreater的时候传递参数
,参数会被传递到action对象 payload
属性上
1.创建store的写法保持不变,配置好同步修改状态的方法
2.单独封装一个函数,在函数内部return一个新函数,在新函数中
- 2.1 封装异步请求获取数据
- 2.2 调用
同步actionCreater
传入异步数据生成一个action对象,并使用dispatch提交
3.组件中dispatch的写法保持不变
store/channelStore.js
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.actions
//异步请求部分
const fetchChannelList = () => {
return async (dispatch) => {
const res = await axios.get("http://geek.itheima.net/v1_0/channels")
dispatch(setChannels(res.data.data.channels))
}
}
export { fetchChannelList }
const reducer = channelStore.reducer
export default reducer
组件内使用
import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
//导入actionCreater
import { increment, decrement, addTonum } from './store/modules/counterStore'
import { fetchChannelList } from './store/modules/channelStore'
function App() {
const { count } = useSelector((state) => state.counter)
const { channelList } = useSelector((state) => state.channel)
const dispatch = useDispatch()
//使用useEffect触发异步请求执行
useEffect(() => {
dispatch(fetchChannelList())
}, [dispatch])
return (
<>
<div>来自redux状态的数据:{count}</div>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(addTonum(10))}>10</button>
<button onClick={() => dispatch(addTonum(20))}>20</button>
<ul>
{channelList.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</>
)
}
export default App
13. ReactRouter—路由
什么是前端路由
一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染
安装最新的 ReactRouter包
npm i react-router-dom
快速使用:
import React from 'react'//React:框架核心包
import ReactDOM from 'react-dom/client'//专门做渲染的包
import { createBrowserRouter,RouterProvider } from "react-router-dom"//路由模块
//创建router实例对象,并配置路由对应关系
const router = createBrowserRouter([
{
path: '/login',
element: <div>我是登录页面</div>
},
{
path: '/home',
element: <div>我是首页页面</div>
},
])
//渲染根组件到id为root的节点上
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
//.使用路由
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
实际开发使用配置:
- 通常新建一个router文件夹(类似vue)
import { createBrowserRouter } from "react-router-dom"
import Login from "../pages/Login/index.jsx"
import Home from "../pages/Home/index.jsx"
//创建router实例对象,并配置路由对应关系
const router = createBrowserRouter([
{
path: '/login',
element: <Login></Login>
},
{
path: '/home',
element: <Home></Home>
},
{
path: '/hello',
element: <div>hello word</div>
},
])
export default router
- 在入口文件导入使用
ReactRouter-路由导航
什么是路由导航
路由系统中的多个路由之间需要进行路由跳转
,并且在跳转的同时有可能需要传递参数进行通信
声明式导航
声明式导航是指通过在模版中通过
`组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行- 语法说明:通过给组件的
to属性
指定要跳转到路由path
,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接
的方式拼接参数即可
编程式导航
- 编程式导航是指通过
useNavigate
钩子得到导航方法,然后通过调用方法以命令式的形式
进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活 - 语法说明:通过调用navigate方法传入地址path实现跳转
示例代码:
import { Link, useNavigate } from 'react-router-dom'
const Home = () => {
const navigate = useNavigate()
return (
<div>
我是首页页面
{/* 声明式导航 */}
<Link to="/login">去登录</Link>
{/* 编程式导航 */}
<button onClick={() => navigate('/login')}>去登录</button>
</div>
)
}
export default Home
ReactRouter-导航传参
searchParams传参
和params传参
import { Link, useNavigate, useSearchParams } from 'react-router-dom'
const Login = () => {
const navigate = useNavigate()
//searchParams接惨
const [params] = useSearchParams()
console.log(params)
let id = params.get('id')
let name = params.get('name')
return (
<>
<h1> 我是登录页面</h1>
<div>
<button>
<Link to="/home">去首页</Link>
</button>
<button onClick={() => navigate('/home/10086')}>
路由传参params到首页
</button>
<div>
useSearchParams路由参数:{id}——{name}
</div>
</div>
</>
)
}
export default Login
import { Link, useNavigate, useParams } from 'react-router-dom'
const Home = () => {
const navigate = useNavigate()
//Params接惨
let params2 = useParams()
console.log(params2)
let number = params2.number
return (
<>
<h1>我是首页页面</h1>
<div>
{/* 声明式导航 */}
<Link to="/login">去登录</Link>
{/* 编程式导航 */}
<button onClick={() => navigate('/login')}>去登录</button>
<button onClick={() => navigate('/login?name=codeSE&id=110')}>
searchParams路由传参到登录
</button>
<div>Params接惨:{number}</div>
</div>
</>
)
}
export default Home
使用Params时注意在路由配置占位
嵌套路由
嵌套路由配置
实现步骤:
- 1.使用
children
属性配置路由嵌套关系 - 2.使用
<Outlet/>
组件配置二级路由渲染位置
-
默认二级路由
当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true
404路由
场景:当浏览器输入url的路径在整个路由配置中都找不到对应的 path,为了用户体验,可以使用 404兜底组件进行渲染
实现步骤
1.准备一个NotFound组件
2.在路由表数组的末尾,以*号作为路由path配置路由
ReactRouter-两种路由模式
两种路由模式
各个主流框架的路由常用的路由模式有俩种,history模式和hash模式
,ReactRouter分别由 createBrowerRouter 和createHashRoute
r函数负责创建
本节完整代码:
router.js:
import { createBrowserRouter } from "react-router-dom"
import Login from "../pages/Login/index.jsx"
import Home from "../pages/Home/index.jsx"
import Layout from "../pages/Layout/index.jsx"
import About from "../pages/About/index.jsx"
import Board from "../pages/Board/index.jsx"
import NotFound from "../pages/NotFound/index.jsx"
//创建router实例对象,并配置路由对应关系
const router = createBrowserRouter([
{
path: '/login',
element: <Login></Login>
},
{
path: '/home/:number',
element: <Home></Home>
},
{
path: '/',
element: <Layout></Layout>,
children: [
{
index: true, //设置默认二级路由
element: <Board />
},
{
path: "about",
element: <About />
},
]
},
{
path: '*',
element: <NotFound></NotFound>
},
])
export default router
layout.js
import { Link, Outlet } from 'react-router-dom'
const Layout = () => {
return (
<>
<h1>我是一级路由</h1>
<Link to="/">面板</Link>
<Link to="/about">关于</Link>
{/* 配置二级路由的出口 */}
<Outlet></Outlet>
</>
)
}
export default Layout
14. react项目中配置
1.别名路径配置
1.路径解析配置(webpack),把 @/ 解析为 @
2.路径联想配置(VsCode),VsCode 在输入@/时,自动联想出来对应的 src/下的子级目录
路径解析配置
CRA本身把webpack配置包装到了黑盒里无法直接修改,需要借助一个插件craco
配置步骤:
- 1.安装craco
npm i -D @craco/craco
- 2.项目根目录下创建配置文件
craco.config.js
- 3.配置文件中添加路径解析配置
- 4.包文件中配置启动和打包命令
联想路径配置
VsCode的联想配置,需要我们在项目目录下添加jsconfig.json
文件,加入配置之后VsCode会自动读取配置帮助我们自动联想提示
配置步骤:
- 1.根目录下新增配置文件-jsconfig.json
- 2.添加路径提示配置
15. 前端使用Mock数据
在前后端分类的开发模式下,前端可以在没有实际后端接口的支持下先进行接口数据的模拟,进行正常的业务功能开发
json-server
实现数据Mock
json-server是一个node包,可以在不到 30秒内获得零编码的完整的Mock服务
实现步骤
- 1.项目中安装json-server
npm i -D json-server
- 2.准备一个json文件
- 3.添加启动命令
"server":"json-server ./server/data.json --port 8888"
- 4.访问接口进行测试
16. 创建项目目录规范
打包优化-CDN优化
什么是CDN?
CDN是一种内容分发网络服务,当用户请求网站内容时,由离用户最近的服务器
将缓存的资源内容
传递给用户
哪些资源可以放到CDN服务器?
体积较大的非业务IS文件
,比如react、react-dom1.体积较大,需要利用CDN文件在浏览器的缓存特性,加快加载时间2.非业务|S文件,不需要经常做变动,CDN不用频繁更新缓存
项目中怎么做?
1.把需要做CDN缓存的文件排除在打包之外(react、react-dom)2.以CDN的方式重新引入资源(react、react-dom)
17 . react常用的API
useReducer
作用:和useState的作用类似,用来管理相对复杂
的状态数据
useReducer-基础用法:
- 1.定义一个reducer函数(根据不同的action返回不同的新状态
- 2.在组件中调用useReducer,并传入reducer函数和状态的初始值
- 3.事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
示例代码:
import { useReducer } from 'react'
//useReducer使用
//1.定义reducer函数 根据不同的action 返回不同的状态
function reducer(state, action) {
switch (action.type) {
case 'INCREASE':
return state + 1
case 'DECREASE':
return state - 1
case 'SET':
return action.payload
default:
return state
}
}
//2.组件中调用useReducer(reducer,0)=>[state,dispatch]
//3.调用dispatch({type:'INC'})=>通知reducer产生一个新的状态 使用这个新状态更新UI
function App() {
const [state, dispatch] = useReducer(reducer, 0)
return (
<>
<div>react-app</div>
<button onClick={() => dispatch({ type: 'INCREASE' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREASE' })}>-</button>
<button onClick={() => dispatch({ type: 'SET', payload: 100 })}>
更新
</button>
<p>{state}</p>
</>
)
}
export default App
useMemo
作用:在组件每次重新渲染的时候 缓存计算的结果
useMemo-基础语法
说明:使用useMemo做缓存之后可以保证只有count1依赖项发生变化时才会重新计算
示例代码:
import { useMemo, useState } from 'react'
//useMemo使用
//缓存:消耗非常大的计算才考虑使用
function sum(n) {
console.log('计算函数执行了...')
if (n < 3) {
return 1
} else {
return sum(n - 2) + sum(n - 1)
}
}
function App() {
const [count1, setCount1] = useState(0)
//只针对count1变化时调用
// const result = sum(count1)//错
const result = useMemo(() => {
//返回计算结果
return sum(count1)
}, [count1])
const [count2, setCount2] = useState(0)
console.log('组件重新渲染了...')
return (
<>
<div>react-app</div>
<button onClick={() => setCount1(count1 + 1)}>增加:{count1}</button>
<button onClick={() => setCount2(count2 + 1)}>减少:{count2}</button>
<h3>结果:{result}</h3>
</>
)
}
export default App
memo
作用:允许组件在Props没有改变
的情况下跳过渲染
React组件默认的渲染机制:只要父组件重新渲染子组件就会重新渲染
思考:如果Son组件本身并不需要做渲染更新,是不是存在浪费?
React.memo-基础语法
说明:经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
示例代码:
import { memo, useMemo, useState } from 'react'
//1.验证默认的渲染机制子跟着父一起渲染
function Son(n) {
console.log('子组件渲染了...')
return <div>this is son</div>
}
//2.memo进行缓存,只有props发生变化的时候才会重新渲染(context)
const MemoSon = memo(function Son() {
console.log('子组件渲染了...')
return <div>this is son</div>
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件渲染了...')
return (
<>
{/* <Son></Son> */}
<MemoSon></MemoSon>
<button onClick={() => setCount(count + 1)}>增加:{count}</button>
</>
)
}
export default App
React.memo-props的比较机制
机制: 在使用memo缓存组件之后,React会对每一个 prop
使用 Obiect.is
比较新值和老值,返回true,表示没有变化
prop是简单类型
Object.is(3,3)=>true
没有变化
prop是引用类型(对象/数组)
Object([], [])=> false
有变化,React只关心引用是否变化
示例代码:
import { memo, useMemo, useState } from 'react'
//React.memo props比较机制
//1.传递一个简单类型的prop prop变化时组件重新渲染
//2.传递一个引用类型的prop 比较的是新值和旧值的引用是否相等 当父组件的函数重新执行时,实际上形成的是新的数组引用
// 3.保证引用稳定->useMemo 组件渲染的过程中缓存一个值
const MemoSon = memo(function Son({ count }) {
console.log('子组件渲染了...')
return <div>this is son</div>
})
function App() {
const [count, setCount] = useState(0)
console.log('父组件渲染了...')
//不会是使子组件重新渲染
// const num = 100
//会使子组件重新渲染
// const list = [1, 2, 3, 4, 5]
//不会是使子组件重新渲染
const list = useMemo(() => {
return [1, 2, 3, 4, 5]
}, [])
return (
<>
{/* <Son></Son> */}
<MemoSon count={list}></MemoSon>
<button onClick={() => setCount(count + 1)}>增加:{count}</button>
</>
)
}
export default App
useCallback
说明:使用useCallback包裹函数之后,函数可以保证在App重新渲染的时候保持引用稳定
示例代码:
forwardRef
使用ref暴露DOM节点给父组件
forwardRef-场景说明:
父组件通过ref获取到子组件内部的input元素让其聚焦
import { forwardRef, useRef } from 'react'
//子组件
// const Son = function Son() {
// return <input type="text" />
// }
const Son = forwardRef((props, ref) => {
return <input type="text" ref={ref} />
})
//父组件
function App() {
const sonRef = useRef(null)
const showRef = () => {
console.log(sonRef)
sonRef.current.focus()
}
return (
<>
<Son ref={sonRef}></Son>
<button onClick={showRef}>增加</button>
</>
)
}
export default App
uselnperativeHandle
通过ref暴露子组件中的方法
uselnperativeHandlle-场景说明:
通过ref调用子组件内部的focus方法实现聚焦父组件
示例代码:
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 showRef = () => {
console.log(sonRef.current)
sonRef.current.focusHandler()
}
return (
<>
<Son ref={sonRef}></Son>
<button onClick={showRef}>增加</button>
</>
)
}
export default App
18. react 类组件 API
类组件基础结构
类组件就是通过JS中的类来组织组件的代码
1.通过类属性state
定义状态数据
2.通过setState
方法来修改状态数据
3.通过render
来写Ul模版(JSX语法一致
import React, { Component } from 'react'
class Counter extends React.Component {
// 编写组件的逻辑代码
//1.状态变量 2.事件回调 3.UI(JSX)
//1.定义状态变量
state = {
count: 0,
}
//2.定义事件回调修改状态数据
addCount = () => {
// 修改状态数据
this.setState({
count: this.state.count + 1,
})
}
render() {
return <button onClick={this.addCount}>增加=={this.state.count}</button>
}
}
class Son extends Component {
render() {
return <h1>哈哈哈~~~</h1>
}
}
//父组件
function App() {
return (
<>
<Son></Son>
<Counter></Counter>
</>
)
}
export default App
类组件的生命周期函数
概念:组件从创建到销毁的各个阶段自动执行的函数
就是生命周期函数
- 1.componentDidMount:组件挂载完毕自动执行——
异步数据获取
- 2.componentWillUnmount: 组件卸载时自动执行 ——
清理副作用
示例代码:
import { Component, useState } from 'react'
class Son extends Component {
//声明周期函数
//组件渲染完毕执行一次: 发送网络请求
componentDidMount() {
console.log('组件渲染完毕...')
this.timer = setInterval(() => {
console.log('定时器运行中...')
}, 1000)
}
// 组件卸载的时候自动执行: 副作用清理的工作 清除定时器 清除事件绑定
componentWillUnmount() {
console.log('组件卸载了...')
clearInterval(this.timer)
}
render() {
return <h1>哈哈哈~~~</h1>
}
}
//父组件
function App() {
const [flag, setFlag] = useState(true)
return (
<>
<button onClick={() => setFlag(!flag)}>{flag ? '卸载' : '挂载'}</button>
{flag && <Son></Son>}
</>
)
}
export default App
类组件的组件通信
概念:类组件和Hooks编写的组件在组件通信的思想上完全一致
1.父传子:通过prop绑定数据
2.子传父:通过prop绑定*组件中的函数,子组件调用
3.兄弟通信:状态提升,通过父组件做桥接
示例代码:
import { Component } from 'react'
//类组件依赖this
//1.父传子直接通过prop子组件标签身上绑定父组件中的数据即可
//2.子传父在子组件标签身上绑定父组件中的函数,子组件中调用这个函数传递参数
class Son extends Component {
render() {
return (
<>
<div>子组件嘻嘻嘻~~~{this.props.msg}</div>
<button
onClick={() => this.props.onGetSonMessage('this is a son message')}>
子传父
</button>
</>
)
}
}
class Parent extends Component {
state = {
msg: 'this is a parent message',
}
getSonMessage(m) {
console.log(m)
}
render() {
return (
<div>
父组件哈哈哈~~~
<Son msg={this.state.msg} onGetSonMessage={this.getSonMessage} />
</div>
)
}
}
//父组件
function App() {
return (
<>
<Parent></Parent>
</>
)
}
export default App
19. zustand 状态管理
极简的状态管理工具
npm i zustand
1.快速使用
import { create } from 'zustand'
// 1.创建store
// 语法容易出错
//1.函数参数必须返回一个对象 对象内部编写状态数据和方法
//2.set是用来修改数据的专门方法必须调用它来修改数据
// 语法1:参数是函数 需要用到老数据的场景
// 语法2:参数直接是一个对象 set({count:100})
const useStore = create((set) => {
return {
// 状态数据
count: 0,
// 修改状态数据的方法
inc: () => {
set((state) => ({
count: state.count + 1,
}))
// set({ count: 100 })
},
}
})
//2.绑定store到组件
//useStore()=>{ count, inc }
function App() {
const { count, inc } = useStore()
return <button onClick={inc}>{count}</button>
}
export default App
2.zustand-异步支持
对于异步的支持不需要特殊的操作,直接在函数中编写异步逻辑,最后只需要调用set方法传入新状态
即可
import { useEffect } from 'react'
import { create } from 'zustand'
// 1.创建store
// 语法容易出错
//1.函数参数必须返回一个对象 对象内部编写状态数据和方法
//2.set是用来修改数据的专门方法必须调用它来修改数据
// 语法1:参数是函数 需要用到老数据的场景
// 语法2:参数直接是一个对象 set({count:100})
const useStore = create((set) => {
return {
// 状态数据
count: 0,
// 修改状态数据的方法
inc: () => {
set((state) => ({
count: state.count + 1,
}))
// set({ count: 100 })
},
channelList: [],
fetchList: async () => {
const res = await fetch('http://geek.itheima.net/v1_0/channels')
const json = await res.json()
console.log(json)
set({ channelList: json.data.channels })
},
}
})
//2.绑定store到组件
//useStore()=>{ count, inc }
function App() {
const { count, inc, channelList, fetchList } = useStore()
useEffect(() => {
fetchList()
}, [fetchList])
return (
<>
<button onClick={inc}>增加:{count}</button>
{channelList.map((item) => (
<p key={item.id}>{item.name}</p>
))}
</>
)
}
export default App
3. zustand-切片模式
场景:当单个store比较大的时候,可以采用 切片模式 进行模块拆分组合,类似于模块化
import { useEffect } from 'react'
import { create } from 'zustand'
//1.拆分子模块 再组合起来
const createCounterStore = (set) => {
return {
// 状态数据
count: 0,
// 修改状态数据的方法
inc: () => {
set((state) => ({
count: state.count + 1,
}))
},
}
}
const createChannelStore = (set) => {
return {
channelList: [],
fetchList: async () => {
const res = await fetch('http://geek.itheima.net/v1_0/channels')
const json = await res.json()
console.log(json)
set({ channelList: json.data.channels })
},
}
}
//2.组合子模块
const useStore = create((...a) => {
return {
...createCounterStore(...a),
...createChannelStore(...a),
}
})
function App() {
const { count, inc, channelList, fetchList } = useStore()
useEffect(() => {
fetchList()
}, [fetchList])
return (
<>
<button onClick={inc}>增加:{count}</button>
{channelList.map((item) => (
<p key={item.id}>{item.name}</p>
))}
</>
)
}
export default App
20. react+vite+ts
react 部分源码解读
1. 虚拟dom
不管是 react 还是 vue ,都有 虚拟 dom?虛拟 dom 一定快吗?
为什么要用虚拟 dom ?
- 某种程度上,保证性能的一个下限。(对于频繁操作原生dom而言)
- 中间层,vdom—>对象—>任意平台(web,手机端,小程序等)
- jsx -->@babel/preset-react–>jsx-runtime -->vdom-> dom(uniapp、taro)
2.diff算法
react和 vue 的 diff 算法有什么异同?
react 为什么不去优化 diff 算法?
传统 diff O(n^3),React Diff O(n)?怎么来的?还可以优化吗 ?
- 最好时间复杂度、最坏时间复杂度、平均时间复杂度、均摊时间复杂度;
- 《数据结构和算法之美》极客时间
dif 算法并不是近年才有的,早在多年以前就已经有人在研究 diff 算法了,最早复杂度基本是 O(m3n3)然后优化了 30 多年,终于在 2011年把复杂度降低到 O(n^3)这里的n指的是节点总数所以 1000 个节点,要进行1亿次操作;
而今天,站在巨人的肩膀上,我们将探究的diff 算法主要指 react 横空出世之后提出的近代同层比较的 diff 算法,因为是同层嘛,复杂度就到了 O(n)
为什么需要diff,为什么现在对 diff 好像提及的不是那么多?
本质上就是为了性能,性能,性能
为什么传统的是 O(n^3)
在计算机中,比较两棵树的区别;–借鉴字符串的编辑距离,莱温斯坦最短距离算法
比如:字符串"hello"->“hallo”;
react 是如何将 diff 算法的复杂度降下来的?
其实就是在算法复杂度、虚拟 dom 渲染机制、性能中找了一个平衡,react 采用了启发式的算法,做了如下最优假设:
- a.如果节点类型相同,那么以该节点为根节点的 tree 结构,大概率是相同的,所以如果类型不同,可以直接「删除」原节点,「插入」新节点
- b.跨层级移动子 tree 结构的情况比较少见,或者可以培养用户使用习惯来规避这种情况,遇到这种情况同样是采用先「删除」再「插入」的方式,这样就避免了跨层级移动
- c.同一层级的子元素,可以通过 key 来缓存实例,然后根据算法采取「插入」「删除」「移动」的操作,尽量复用,减少性能开销
- d.完全相同的节点,其虚拟 dom 也是完全一致的;