前言
React 是由 Facebook(现 Meta)开发并开源的 JavaScript 前端 UI 库,用于构建用户界面,尤其适用于构建单页应用(SPA)和复杂交互式组件。
-
组件化架构
React 采用组件化开发模式,将 UI 拆分为独立、可复用的组件。每个组件拥有自己的状态(state)和属性(props),便于维护与协作开发。
-
声明式编程
开发者只需描述"UI 应该是什么样子",React 会自动高效地更新 DOM。相比命令式操作,代码更简洁、逻辑更清晰。
-
虚拟 DOM(Virtual DOM)
React 在内存中维护一个轻量级的虚拟 DOM 树。当状态变化时,通过 Diff 算法计算新旧虚拟 DOM 差异,仅更新真实 DOM 中变更的部分,显著提升性能。
-
单向数据流
数据从父组件通过 props 向子组件单向传递,使数据流向清晰、易于调试和追踪问题,避免数据混乱。
-
JSX 语法支持
JSX 允许在 JavaScript 中编写类似 HTML 的结构,提升代码可读性,并在编译时转换为标准 JS 函数调用(React.createElement)。
-
丰富的生态系统
搭配 React Router(路由)、Redux/Zustand(状态管理)、Next.js(服务端渲染)等工具,可构建完整前端解决方案。
-
跨平台能力
通过 React Native,可用相同理念开发原生移动应用(iOS/Android),实现"一次学习,多端开发"。
-
社区活跃、生态成熟
拥有庞大的开发者社区和持续更新,文档完善,第三方库丰富,企业级应用广泛(如 Facebook、Instagram、Airbnb 等)。
React的安装
1、react的基本使用
bash
npm i react react-dom # react包是核心,提供创建元素、组件等功能;react-dom包提供DOM相关功能
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>react的基本使用</title>
</head>
<body>
<div id="root"></div>
<!-- 从 CDN 加载 UMD 版本 -->
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script>
// 创建React元素
// 参数1:元素名称;参数2:元素属性;参数三:元素的子节点
const title = React.createElement('h1', null, 'hello')
// 获取根元素对应的React元素
// 创建React根元素,需要一个DOM元素作为参数
const root = ReactDOM.createRoot(document.getElementById('root'))
// 将React元素渲染到根元素中
root.render(title)
</script>
</body>
</html>
三个API
1、React.createElement()
- React.createElement(type, [props], [...children])
- 用来创建React元素
- React元素无法修改
2、ReactDOM.createRoot()
- ReactDOM.createRoot(container[, options])
- 用来创建React的根容器,放置React元素
3、root.render()
- root.render(element)
- 当首次调用时,容器节点里面的所有DOM都会被替换,后续的调用会使用React的DOM差分算法(DOM diffing algorithm)进行高效更新
- 不会修改容器节点(只会修改容器的子节点),可以在不覆盖现有子节点的情况下,将组件插入已有的DOM节点中
离线下载react.development.js和react-dom.development.js
https://unpkg.com/react@18.0.0/umd/react.development.js
https://unpkg.com/react-dom@18.0.0/umd/react-dom.development.js
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>react的基本使用</title>
</head>
<body>
<div id="root"></div>
<button id="btn">点击</button>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script>
// 创建React元素
// 参数1:元素名称;(html标签必须小写)
// 参数2:元素属性;
// - class属性需要使用className替代
// - 设置事件时,属性名是驼峰命名法
// 参数3:元素的子节点
// 注意:React元素最终会通过虚拟DOM转换为真实的DOM元素;React元素一旦创建就无法修改
const title = React.createElement('button', {
id: 'btn',
className: 'btn_class',
onClick: ()=>{alert('你已点击')}
}, 'hello')
// 获取根元素对应的React元素
// 创建React根元素,需要一个DOM元素作为参数
const root = ReactDOM.createRoot(document.getElementById('root'))
// 将React元素渲染到根元素中(根元素中所有的内容都会被删除,被React元素所替换)
// 注意:修改React元素后,必须重新对根元素进行渲染
// 当重复调用render()时,React会将两次的渲染结果进行比较,确保只修改发生变化的元素,对DOM做最少的修改
root.render(title)
</script>
</body>
</html>
2、创建React项目(手动)
创建一个reactProject项目
bash
# 进入项目所在目录
npm init -y
# 安装项目依赖
npm install react react-dom react-scripts -S
public目录下的index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>React项目</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
src下的index.js
javascript
import ReactDOM from "react-dom/client";
// 创建一个JSX
const App = <div>
<h1>这是React项目</h1>
<p>ReactReactReact</p>
</div>
// 获取一个根容器
const root = ReactDOM.createRoot(document.getElementById('root'))
// 将App渲染到根容器
root.render(App)
bash
# 运行
# 实时修改实时运行变化
npx react-scripts start # npx react-scripts build 一般项目开发完成后使用这个运行
或者package.json中修改
javascript
"scripts": {
"build": "react-scripts build",
"start": "react-scripts start"
},
直接简化命令运行
bash
npm run start
3、React脚手架的使用
bash
# 创建项目
npx create-react-app my-app
# 安装所有依赖
npm i
# 在项目根目录启动项目
npm run start
JSX
前言
JSX是JavaScript XML的简写,表示在JavaScript代码中写XML(HTML)格式的代码
JSX是React中声明式编程的体现方式
声明式编程,就是以结果为导向的编程
使用JSX将网页结构编写出来,然后React再根据JSX自动生成JS代码
JSX代码,最终都会转换为以调用React.createElement()创建元素的代码,即JSX就是React.createElement()的语法糖
优势:
- 声明式语法更加直观、与HTML结构相同;
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化;
- 它是类型安全的,在编译过程中就能发现错误;
- 使用 JSX 编写模板更加简单快速。
1、JSX的基本使用
离线下载babel
https://unpkg.com/babel-standalone@6.26.0/babel.min.js
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSX的基本使用</title>
</head>
<body>
<div id="root"></div>
<button id="btn">点击</button>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<!-- 设置JS代码被babel处理 -->
<script type="text/babel">
// 创建一个React元素
// 命令式编程
// const button = React.createElement('button', {}, '我是按钮')
// 声明式编程
// 在React中可以通过JSX(JS扩展)来创建React元素,JSX需要被翻译为JS代码,才能被React执行
// 要在React中使用JSX,需要引入babel完成翻译工作
const button = <button>按钮</button>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(button)
</script>
</body>
</html>
2、JSX注意事项
- JSX不是字符串,不能加引号
- JSX中html标签应该小写,React组件应该大写开头
- JSX有且只有一个根标签
- JSX标签必须正确结束(单标签、自结束标签必须写/),推荐使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱
- 在JSX中可以使用{}嵌入表达式(有值的语句就是表达式),如果表达式是空值、布尔值、NaN、undefined这些值,将不会显示
- 在JSX中,属性可以直接在标签中设置,但class需要使用className代替,style中必须使用对象设置
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSX注意事项</title>
</head>
<body>
<div id="root"></div><br/>
<button id="btn">点击</button>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type="text/babel">
// 在JSX中可以使用{}嵌入表达式
// - 有值的语句就是表达式
const name = 'lili'
const div = <div
id='box'
onClick={()=>{alert('你好')}}
className='div_class'
style={{backgroundColor:'red'}}> // style中必须使用对象设置,React 推荐使用内联样式,React 会在指定元素数字后自动添加 px
这是一个div,名字是{name}
<ul>
<li>列表项</li>
</ul>
<input type="text" />
</div>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(div)
</script>
</body>
</html>
3、渲染列表
JSX 允许在模板{}中插入数组,数组会自动展开所有成员
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>react的基本使用</title>
</head>
<body>
<div id="root"></div>
<script src="./react.development.js"></script>
<script src="./react-dom.development.js"></script>
<script src="./babel.min.js"></script>
<script type="text/babel">
const data = ['张三', '李四', '王五']
// 将arr渲染为一个列表
// JSX会自动将数组中的元素在页面显示
// const list = <div>{ arr }</div>
// const arr = []
// for(let i=0; i<data.length; i++){
// arr.push(<li>{data[i]}</li>)
// }
const arr = data.map(item=><li>{item}</li>)
const list = <ul>{arr}</ul>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)
</script>
</body>
</html>
事件处理
1、React事件处理
语法:on + 事件名称 = { 事件处理程序 }
在React中,事件需要通过元素的属性来设置
和原生JS不同,在React中事件的属性需要使用驼峰命名法:onclick -> onClick、onchange-> onChange
属性值不能直接执行代码,而是需要一个回调函数:οnclick="alert(1)" -> onClick={ ()=>{alert(1)} }
App.js
javascript
import React from "react";
/*
JS里添加事件
<button id="btn01">点击</button>
document.getElementById('btn01').onclick = function(){}
document.getElementById('btn01').addEventListener('click', function(){}, false)
*/
function App(){
const clickHandler = (event) => {
// event事件对象
// - React事件中同样会传递事件对象,可以在响应函数中定义参数来接收事件对象
// - React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象
// - 由于对象进行过包装,所以使用过程中无需再去考虑兼容性问题
// alert(event) // [object Object],普通对象
event.preventDefault(); // 取消默认行为
event.stopPropagation(); // 取消冒泡行为
alert('App中的clickHandler函数')
// 在React中,无法通过return false取消默认行为
// return false
}
return (<div onClick={()=>{alert('父元素')}}
style={ {width:200, height:200, margin:"100px auto", backgroundColor: "red"}}>
<button onClick={()=>{alert('子元素')}}>点击</button>
{/* clickHandler不能加括号(),加了就表示要调用函数,但此处是要把这个函数设置为事件的响应函数,只有事件触发了才会去调用函数 */}
<button onClick={clickHandler}>再次点击</button>
{/* 默认行为,超链接点击之后默认就会发生页面跳转 */}
<a href="https://www.baidu.com" onClick={clickHandler}>超链接</a>
</div>);
}
// 导出App
export default App
2、事件绑定this指向
2.1 箭头函数
利用箭头函数自身不绑定this的特点
箭头函数中的this由外部环境决定
App.js
javascript
import React from "react";
class App extends React.Component{
state = {
count: 0
}
// 事件处理程序
onIncrement() {
console.log('事件处理程序中的this:', this)
this.setState({
count: this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={ ()=> {this.onIncrement() }}>+1</button>
</div>
)
}
}
// 导出App
export default App
2.2 Function.prototype.bind()
利用ES6中的bind()方法,将事件处理程序中的this和组件实例绑定一起
App.js
javascript
import React from "react";
class App extends React.Component{
state = {
count: 0
}
constructor() {
super()
this.onIncrement = this.onIncrement.bind(this)
}
// 事件处理程序
onIncrement() {
console.log('事件处理程序中的this:', this)
this.setState({
count: this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={ this.onIncrement }>+1</button>
</div>
)
}
}
// 导出App
export default App
2.3 class实例方法(推荐)
利用箭头函数形式的class实例方法
App.js
javascript
import React from "react";
class App extends React.Component{
state = {
count: 0
}
// 事件处理程序
onIncrement = () => {
console.log('事件处理程序中的this:', this)
this.setState({
count: this.state.count + 1
})
}
render(){
return (
<div>
<h1>计数器:{ this.state.count }</h1>
{/* 不能加(),onIncrement() 会在组件每次渲染时立即执行,而不是等到点击才执行! */}
<button onClick={ this.onIncrement }>+1</button>
</div>
)
}
}
// 导出App
export default App
组件
React 组件是构建 React 应用的基本单元,可以把组件理解为可复用的、独立的 UI 单元,每个组件封装了自己的结构、样式、逻辑和状态。
React 组件是构建应用的基石,组件可以小到一个按钮,也可以大到整个页面。
从技术角度讲,React 组件就是一个返回 React 元素(通常是 JSX)的 JavaScript 函数或类。
组件极简模型:(state, props) => UI
1、React 组件的两种创建方式
1.1 函数组件
函数组件是一个接受 props 并返回 React 元素的 JavaScript 函数
约定1:函数名必须以大写字母开头,React据此区分组件和普通的React元素
约定2:函数组件必须有返回值,表示该组件的结构
App.js
javascript
const App = () => {
return (<div>hello react</div>);
}
// 导出App
export default App
渲染函数组件:使用函数名作为组件标签名
组件标签可以是单标签也可以是双标签
index.js
javascript
import ReactDOM from "react-dom/client";
import App from './App.js'
const root = ReactDOM.createRoot(document.getElementById('root'))
// React组件可以直接通过JSX渲染
root.render(<App/>)
1.2 类组件
类组件使用 ES6 类语法定义,通常用于需要管理状态或使用生命周期方法的情况
App.js
javascript
import React from "react";
// 类组件必须继承React.Component
class App extends React.Component{
// 类组件中,必须添加一个render()方法,且方法的返回值要是一个JSX
render() {
return (<div>这是一个类组件</div>);
}
}
// 导出App
export default App
2、有状态组件和无状态组件
函数组件又叫无状态组件
状态即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件又叫有状态组件
类组件有自己的状态,负责更新UI,让页面动起来
3、组件中的state和setState()
状态即数据,是组件内部的私有数据,只能在组件内部使用
state的值是对象,表示一个组件中可以有多个数据
18之前
3.1 state的基本使用
App.js
javascript
import React from "react";
// state的基本使用
class App extends React.Component{
// constructor() {
// super()
// // 初始化state
// this.state = {
// count: 0
// }
// }
// 简化语法初始化state
state = {
count: 0
}
render(){
return (
<div>
<h1>计数器:{ this.state.count }</h1>
</div>
)
}
}
// 导出App
export default App
3.2 setState()修改状态
状态是可变的
语法:this.setState({ 要修改的数据 })
setState()作用:修改state;更新UI
思想:数据驱动视图
注意:不能直接修改state中的值
App.js
javascript
import React from "react";
class App extends React.Component{
state = {
count: 0
}
render(){
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={()=>{
this.setState({
count: this.state.count + 1
})
}}>+1</button>
</div>
)
}
}
// 导出App
export default App
18之后
3.3 useState()
当React发生变化时,会自动触发组件的重新渲染
在函数组件中,通过钩子函数useState()获取state
useState()该函数会返回一个数组,数组第一个元素是初始值,第二个元素是函数
初始值只用来显示数据,直接修改不会触发组件的重新渲染
函数用来修改state,调用其修改state后会触发组件的重新渲染,并且使用函数中的值作为新的state值
index.js
javascript
import ReactDOM from "react-dom/client";
import { React, useState } from "react";
const App = () => {
const [count, setCount] = useState(0)
return (
<div>
<h1>计数:{count}</h1>
<button onClick={()=>{setCount(count+1)}}>点击</button>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
注意:
- 只有state值发生变化时,组件才会重新渲染
- 当state的值是一个对象时,修改时使用新的对象去替换旧的对象,因为直接修改旧的state对象,由于对象的地址不会改变,所以不会重新渲染
- 当通过setState()去修改一个state时,并不表示修改当前的state,修改的是组件下一次渲染时state值
- setState()是异步渲染
- 当调用setState需要旧state的值时,一定要注意有可能出现计算错误的情况(即当前获取到的值不是最新的值)。为了避免这种情况,可以通过setState()传递回调函数的形式来修改state(setState()的回调函数是异步回调,会把当前同步函数执行一遍,再执行回调,因此获得的是最新的值)
javascript
import ReactDOM from "react-dom/client";
import { React, useState } from "react";
const App = () => {
const [count, setCount] = useState(0)
const [user, setUser] = useState({name: 'lili', age: 19})
const updateUserHandler = () =>{
// // 浅复制
// const newUser = Object.assign({},user)
// newUser.name = 'keke'
// setUser(newUser)
// 简写
setUser({...user, name: 'haily'})
}
const addHandler = () =>{
// setState()中回调函数的返回值将会成为新的state值
// 回调函数执行时,React会将最新的state值作为参数传递(即便是点击很多次也都可以改变)
setTimeout(()=>{
// setCount((prevCount)=>{
// return prevCount + 1
// })
// 简写
setCount(prevCount=>prevCount+1)
}, 1000)
}
return (
<div>
<h1>计数:{count}</h1>
{/* <button onClick={()=>{setCount(count+1)}}>点击</button> */}
<button onClick={addHandler}>点击</button>
<h1>对象显示:{user.name},{user.age}</h1>
<button onClick={updateUserHandler}>点击</button>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
3.4 useReducer
作用:和useState作用类似,用来管理相对复杂的状态数据
步骤:
- 定义一个reducer函数(根据不同的action返回不同的新状态)
- 在组件中调用useReducer,并传入reducer函数和状态的初始值
- 事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)
App.js
javascript
import { useReducer } from "react";
function reducer(state, action){
switch(action.type){
case 'INC':
return state + 1
case 'DEC':
return state - 1
default:
return state
}
}
function App() {
const [state, dispatch] = useReducer(reducer, 0)
return (
<div>
this is App.<br/>
<button onClick={()=>dispatch({type: 'INC'})}>+</button>
{state}
<button onClick={()=>dispatch({type: 'DEC'})}>-</button>
</div>
);
}
export default App;
分派action时传参
javascript
import { useReducer } from "react";
function reducer(state, action){
switch(action.type){
case 'INC':
return state + 1
case 'DEC':
return state - 1
case 'SET':
return action.payload
default:
return state
}
}
function App() {
const [state, dispatch] = useReducer(reducer, 0)
return (
<div>
this is App.<br/>
<button onClick={()=>dispatch({type: 'INC'})}>+</button>
{state}
<button onClick={()=>dispatch({type: 'DEC'})}>-</button>
<button onClick={()=>dispatch({type: 'SET', payload: 100})}>update</button>
</div>
);
}
export default App;
4、组件基础样式
React组件基础样式控制有两种方式:1.行内样式(不推荐);2、class类名控制
React 会在指定元素数字后自动添加 px(除了字符串里面的)
App.js
javascript
import './App.css'
const style = {
color: 'red',
fontSize: 30
}
function App(){
return(
<div>
{/* 行内样式控制 */}
<span style={style}>行内样式span</span><br/><br/>
{/* 通过class类名控制 */}
<span className="span">class类名span</span>
</div>
)
}
// 导出App
export default App
App.css
css
.span{
color: aquamarine;
font-size: 30px;
}
index.js
javascript
import ReactDOM from "react-dom/client";
import { React} from "react";
import App from "./App.js";
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
4、表单处理
4.1 受控组件
HTML的表单元素是可输入的,也就是有自己的可变状态
但React中可变状态通常保存在state中,并且只能通过setState()方法来修改
React将state与表单元素value绑定到一起,由state的值来控制表单元素的值
受控组件:其值受到React控制的表单元素
步骤:
- 在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)
App.js
javascript
import React from "react";
class App extends React.Component{
state = {
txt: ''
}
handleChange = (event) =>{
this.setState({
txt: event.target.value
})
console.log(this.state.txt)
}
render(){
return (
<div>
<input type="text" value={this.state.txt} onChange={this.handleChange}></input>
</div>
)
}
}
// 导出App
export default App
18之后
步骤:
- 通过value属性绑定react状态
- 绑定onChange事件,通过事件参数e拿到输入框最新的值,反向修改到react状态
App.js
javascript
import { useState } from "react"
function App(){
const [value, setValue] = useState('')
return(
<div>
<input
value={value}
onChange={(e)=>{
setValue(e.target.value)
console.log(e.target.value)
}}
type="text"/>
</div>
)
}
// 导出App
export default App
多表单元素优化(使用一个事件处理程序同时处理多个表单元素)
步骤:
- 给表单元素添加name属性,名称和state相同
- 根据表单元素类型获取对应值
- 在change事件处理程序中通过[name]来修改对应的state
App.js
javascript
import React from "react";
class App extends React.Component{
state = {
txt: '',
content: '',
city: '',
isChecked: false
}
handleForm = (event) =>{
// 获取当前DOM对象
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
// 获取name
const name = target.name
this.setState({
[name]: value
})
console.log(value)
}
render(){
return (
<div>
<input name="txt" type="text" value={this.state.txt} onChange={this.handleForm}></input>
<br/>
<textarea name="content" value={this.state.content} onChange={this.handleForm}></textarea>
<br/>
<select name="city" value={this.state.city} onChange={this.handleForm}>
<option value="sh">上海</option>
<option value="bj">北京</option>
<option value="sz">深圳</option>
</select>
<br/>
<input name="isChecked" type="checkbox" checked={this.state.isChecked} onChange={this.handleForm}></input>
</div>
)
}
}
// 导出App
export default App
4.2 非受控组件(不推荐)
借助ref,使用原生DOM方式获取表单元素值
ref作用:获取DOM或组件
App.js
javascript
import React from "react";
class App extends React.Component{
constructor() {
super()
// 创建ref
this.txtRef = React.createRef()
}
getTxt = () => {
console.log('文本框值为:', this.txtRef.current.value)
}
render(){
return (
<div>
<input type="text" ref={this.txtRef}></input>
<button onClick={this.getTxt}>获取文本框的值</button>
</div>
)
}
}
// 导出App
export default App
5、Props
在 React 中,Props(属性)是用于将数据从父组件传递到子组件的机制,Props 是只读的,子组件不能直接修改它们,而是应该由父组件来管理和更新
state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据, 而子组件只能通过 props 来传递数据
5.1 props之函数组件和类组件
props作用:接收传递给组件的数据
步骤:
- 传递数据:给组件标签添加属性
- 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
index.js
javascript
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 函数组件
// 2.接收数据
const App = (props) => {
// props是一个对象
console.log(props)
return (
<div>
<h1>props: {props.name} </h1>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 1.传递数据
root.render(<App name="lili" />)
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 类组件
// 2.接收数据
class App extends React.Component {
render() {
console.log(this.props)
return (
<div>
<h1>props: {this.props.name} </h1>
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 1.传递数据
root.render(<App name="lili" />)
5.2 props特点
可以给组件传递任意类型的数据
props是只读对象,子组件只能读取属性的值,无法修改对象。父组件的数据只能父组件修改
注意:使用类组件时,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取到props
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 类组件
// 2.接收数据
class App extends React.Component {
// constructor() {
// super()
// console.log(this.props) // 输出:undefined
// }
constructor(props) {
// 推荐使用props作为constructor的参数
super(props)
console.log(props)
}
render() {
console.log(this.props)
this.props.fn()
// 修改props值
// this.props.name = '李四' // 报错:Uncaught TypeError: Cannot assign to read only property 'name' of object '
return (
<div>
<h1>props: {this.props.name}, {this.props.age} </h1>
{this.props.tag}
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 1.传递数据,注意:需要传递非字符串的要{}括起来
root.render(<App
name="lili"
age={18}
colors={['red', 'pink']}
fn={()=>{console.log('函数')}}
tag={<p>p标签</p>}
/>)
5.3 children属性
表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
值可以是任意值(文本、React元素、组件、函数)
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
const App = props => {
console.log(props)
return (
<div>
<h1>组件标签的子节点:</h1>
{props.children}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App>子节点</App>)
5.4 props校验
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据不对,可能会导致组件内部报错
关键问题:组件使用者不知道明确的错误原因
props校验:允许在创建组件的时候,就指定props的类型、格式等
作用:捕获使用组件时因props导致的错误,给出明确的错误提示,增加组件的健壮性
步骤:
- 安装prop-types包
- 导入prop-types包
- 使用组件名.propTypes={}给组件的props添加校验规则
- 校验规则通过PropTypes对象来指定
bash
# 安装prop-types
npm i prop-types # yarn add prop-types
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
import PropTypes from 'prop-types'
const App = props => {
const arr = props.colors
console.log(arr)
const lis = arr.map((item, index) => <li key={index}>{item}</li>)
console.log(lis)
return (
<ul>{lis}</ul>
)
}
// 添加props校验
App.propTypes = {
colors: PropTypes.array
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App colors={['red', 'green']}/>)
约束规则:
常见类型:array、bool、func、number、object、string
React元素:element
必填项:isRequired
特定结构的对象:shape({ })
5.5 props默认值
场景:分页组件->每页显示条数
作用:给props设置默认值,在未传入props时生效
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// 注意:函数组件的 defaultProps 是不被支持的
// 解构 + 默认参数(推荐,现代写法)
// const App = ({ pageSize = 10 }) => {
// return (
// <div>
// <h1>此处展示props的默认值:{pageSize}</h1>
// </div>
// );
// };
// 不需要 defaultProps!
class App extends React.Component {
render() {
return (
<div>
<h1>此处展示props的默认值:{this.props.pageSize}</h1>
</div>
);
}
}
// 添加props默认值
App.defaultProps = {
pageSize: 10
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App pageSize={199}/>)
6、组件通讯的三种方式
6.1 父组件->子组件
步骤:
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为state中的数据
- 子组件通过props接收父组件中传递的数据
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 父组件
class Parent extends React.Component{
state = {
name: 'lili' // 1
}
render(){
return(
<div>
父组件:
<Child name={this.state.name}/> // 2
</div>
)
}
}
// 子组件
const Child = (props) =>{
console.log('子组件',props)
return (
<div>
<p>子组件,接收父组件的数据:{props.name}</p> // 3
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 1.传递数据
root.render(<Parent />)
18之后
步骤:
- 父组件传递数据---在子组件标签上绑定属性
- 子组件接收数据---子组件通过props参数接收数据
javascript
function Son(props){
console.log(props)
return(
<div>
son, {props.name}
</div>
)
}
function App(){
const name = 'father'
return(
<div>
<Son name={name}></Son>
</div>
)
}
// 导出App
export default App
6.2 子组件->父组件
利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
步骤:
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为属性的值,传递给子组件
- 子组件通过props调用回调函数
注意:回调函数中this指向问题
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 父组件
class Parent extends React.Component{
// 通过state展示从子组件接收过来的数据
state = {
parentMsg: ''
}
// 1.提供回调函数,用来接收数据
getChildMsg = data => {
console.log('接收子组件传递过来的数据:', data)
this.setState({
parentMsg: data
})
}
render(){
return(
<div>
父组件:{this.state.parentMsg}
<Child getMsg={this.getChildMsg} /> {/* 2.将该函数作为属性的值,传递给子组件 */}
</div>
)
}
}
// 子组件
class Child extends React.Component{
state = {
msg: '莉莉'
}
handleClick = () =>{
// 3.子组件调用父组件传递过来的回调函数
this.props.getMsg(this.state.msg)
}
render(){
return (
<div>
子组件:<button onClick={this.handleClick}>点击,给父组件传递数据</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 1.传递数据
root.render(<Parent />)
18之后
核心思路:在子组件中调用父组件中的函数并传递参数
javascript
import { useState } from "react"
function Son({onGetSonMsg}){
const sonMsg = 'this is son msg.'
return(
<div>
this is son.
<button onClick={()=>{onGetSonMsg(sonMsg)}}>点击</button>
</div>
)
}
function App(){
const [msg, setMsg] = useState('')
const getMsg = (msg) =>{
console.log(msg)
setMsg(msg)
}
return(
<div>
this is father. <br/>{msg}
<Son onGetSonMsg={getMsg}></Son>
</div>
)
}
// 导出App
export default App
6.3 兄弟组件
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
思想:状态提升
公共父组件职责:提供共享状态;提供操作共享状态的方法
通讯的子组件只需通过props接收状态或操作状态的方法
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 父组件
class Counter extends React.Component{
// 提供共享状态
state = {
count: 0
}
// 提供修改状态的方法
onIncrement = () =>{
this.setState({
count: this.state.count + 1
})
}
render(){
return(
<div>
<Child1 count={this.state.count} />
<Child2 onIncrement={this.onIncrement} />
</div>
)
}
}
const Child1 = (props) =>{
return (<h1>计数器:{props.count} </h1>)
}
const Child2 = (props) =>{
return (<button onClick={props.onIncrement}>+1</button>)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 1.传递数据
root.render(<Counter />)
18之后
实现思路:借助"状态提升"机制,通过父组件进行兄弟之间的数据传递
步骤:
- A组件先通过子传父的方式把数据传给父组件
- 父组件拿到数据后通过父传子的方式再传递给B组件
javascript
import { useState } from "react"
function A({onGetAMsg}){
const name = 'this is A.'
return(
<div>
this is A.
<button onClick={()=>onGetAMsg(name)}>点击</button>
</div>
)
}
function B({name}){
return(
<div>
this is B.
{name}
</div>
)
}
function App(){
const [name, setName] = useState('')
const getName = (name) =>{
console.log(name)
setName(name)
}
return(
<div>
this is App.
<br/><br/>
<A onGetAMsg={getName}/>
<br/>
<B name={name}/>
</div>
)
}
// 导出App
export default App
6.4 Context
App组件要传递数据给Child组件,使用Context
Context作用:跨组件传递数据(比如主题、语言)
步骤:
- 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)这两个组件
- 使用Provider组件作为父节点
- 设置value属性,表示要传递的数据
- 使用Consumer组件接收数据
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// import App from './App.js'
// 创建context得到两个组件
const {Provider, Consumer} = React.createContext()
class App extends React.Component{
render(){
return(
<Provider value="pink">
<div>
<Node />
</div>
</Provider>
)
}
}
const Node = props => {
return (
<div>
<SubNode />
</div>
)
}
const SubNode = props => {
return (
<div>
<Child />
</div>
)
}
const Child = () => {
return (
<div>
<Consumer>
{
data => <span>子节点 -- {data}</span>
}
</Consumer>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
18之后
步骤:
- 使用createContext方法创建一个上下文对象Ctx
- 在顶层组件(App)中通过Ctx.Provider组件提供数据
- 在底层组件(B)中通过useContext钩子函数获取消费数据
javascript
import { createContext, useContext } from "react"
// App->A->B
const MsgContext = createContext() // 1
function A(){
return(
<div>
this is A.
<B/>
</div>
)
}
function B(){
const msg = useContext(MsgContext) // 3
return(
<div>
this is B. {msg}
</div>
)
}
function App(){
const msg = 'this is App msg.'
return(
<div>
<MsgContext.Provider value={msg}> {/* 2 */}
this is App.
<A/>
</MsgContext.Provider>
</div>
)
}
// 导出App
export default App
7、组件的生命周期
意义:组件的生命周期有助于理解组件的运行方式、完成复杂的组件功能、分析组件错误原因等
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
钩子函数的作用:为开发人员在不同阶段操作提供了时机
只有类组件才有生命周期

7.1 挂载阶段
执行时机:组件创建时(页面加载时)
执行顺序:constructor()->render()->componentDidMount()
|-------------------|----------------|----------------------------|
| 钩子函数 | 触发时机 | 作用 |
| constructor | 创建组件时,最先执行 | 1.初始化state 2.为事件处理程序绑定this |
| render | 每次组件渲染都会触发 | 渲染UI(注意:不能调用setState()) |
| componentDidMount | 组件挂载(完成DOM渲染)后 | 1.发送网络请求 2.DOM操作 3.异步数据获取 |
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
constructor(props){
super(props)
this.state = {
count: 0
}
console.warn('生命周期钩子函数:constructor')
}
componentDidMount(){
// 进行渲染之后才能获取title值
// 1.进行DOM操作
// 2.发生ajax请求,获取远程数据
const title = document.getElementById('title')
console.log(title)
console.warn('生命周期钩子函数:componentDidMount')
}
render() {
// 报错
// this.setState({
// count:1
// })
console.warn('生命周期钩子函数:render')
return (
<div>
<h1 id="title">计数:</h1>
<button id="btn">点击</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
7.2 更新阶段
执行时机:1、setState();2、forceUpdate();3、组件接收到新的props
以上三者任意一种变化,组件都会重新渲染
执行顺序:render()->componentDidUpdate()
|--------------------|----------------|--------------------------------------------|
| 钩子函数 | 触发时机 | 作用 |
| render | 每次组件渲染都会触发 | 渲染UI(与挂载阶段是同一个render) |
| componentDidUpdate | 组件更新(完成DOM渲染)后 | 1.发生网络请求 2.DOM操作 注意:如果要setState()必须放在if条件中 |
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
constructor(props){
super(props)
this.state = {
count: 0
}
}
handleClick = () =>{
this.setState({
count: this.state.count + 1
})
// 强制更新(即便是没有内容也会重新渲染)
// this.forceUpdate()
}
render() {
console.warn('生命周期钩子函数:render')
return (
<div>
<Counter count={this.state.count}/>
<button onClick={this.handleClick}>点击</button>
</div>
);
}
}
class Counter extends React.Component{
render(){
console.warn('子组件-生命周期钩子函数:render')
return <h1 id="title">统计计数:{this.props.count}</h1>
}
componentDidUpdate(prevProps){
console.log('上一次的props:', prevProps, '当前的props:', this.props)
console.warn('子组件-生命周期钩子函数:componentDidUpdate')
// 注意:如果要调用setState()更新状态,必须放在一个if条件中
// 因为如果直接调用setState()更新状态,会导致递归更新
// 比较更新前后的props是否相同来决定是否重新渲染组件
if(prevProps.count !== this.props.count){
this.setState({ })
}
// 获取DOM
const title = document.getElementById('title')
console.log(title.innerHTML)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
7.3 卸载阶段
执行时机:组件从页面消失
|----------------------|--------------|-------------------|
| 钩子函数 | 触发时机 | 作用 |
| componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) |
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
constructor(props){
super(props)
this.state = {
count: 0
}
}
handleClick = () =>{
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
{
this.state.count > 3 ? <p>计数超过3</p> : <Counter count={this.state.count}/>
}
<button onClick={this.handleClick}>点击</button>
</div>
);
}
}
class Counter extends React.Component{
componentDidMount(){
// 开启定时器
this.timeId = setInterval(() => {
console.log('定时器已开启')
}, 500);
}
render(){
return <h1 id="title">统计计数:{this.props.count}</h1>
}
componentWillUnmount(){
console.warn('生命周期钩子函数:componentWillUnmount')
// 清理定时器
clearInterval(this.timeId)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
7.4 不常用钩子函数

8、组件复用
复用什么:1.state;2.操作state的方法(组件状态逻辑)
两者方式:1.render props模式;2.高阶组件(HOC)
8.1 render props模式
思路:将要复用的state模式和操作state方法封装到一个组件中
如何拿到组件中复用的state:在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)
如何渲染任意的UI:使用函数的返回值作为要渲染的UI内容(需要组件内部实现)
步骤:
- 创建组件,在组件中提供复用的状态逻辑代码(1.状态;2.操作状态的方法)
- 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
- 使用props.render()的返回值作为要渲染的内容
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
import img from './image/星球.png'
// 创建Mouse组件
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
// 鼠标移动事件处理
handleMouseMove = (event) =>{
this.setState({
x: event.clientX,
y: event.clientY
})
}
// 监听鼠标移动
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
render(){
return this.props.render(this.state)
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>render props 模式</h1>
<Mouse render={(mouse)=>{
return <p>鼠标位置:{mouse.x} {mouse.y}</p>
}}/>
<Mouse render={mouse=>{
return <img src={img} alt="星球" style={
{
position: 'absolute',
top: mouse.y - 64,
left: mouse.x - 64
}
}/>
}}></Mouse>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
8.2 children代替render属性
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
import img from './image/星球.png'
// 创建Mouse组件
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
// 鼠标移动事件处理
handleMouseMove = (event) =>{
this.setState({
x: event.clientX,
y: event.clientY
})
}
// 监听鼠标移动
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
render(){
return this.props.children(this.state)
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>children模式</h1>
<Mouse>
{
mouse => {
return (
<p>鼠标位置:{mouse.x} {mouse.y}</p>
)
}
}
</Mouse>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
9、高阶组件HOC
目的:实现状态逻辑复用
采用包装(装饰)模式
高阶组件HOC是一个函数,接收要包装的组件,返回增强后的组件
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件
9.1 HOC的使用
使用步骤:
- 创建一个函数,名称约定以with开头
- 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
- 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
- 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
- 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
import PropType from 'prop-types'
// 创建高阶组件
function withMouse(WrappedComponent){
// 提供复用的状态逻辑
class Mouse extends React.Component{
state = {
x: 0,
y: 0
}
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove', this.handleMouseMove)
}
render(){
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
return Mouse
}
const Position = props => {
return (
<p>
鼠标当前位置:(x: {props.x}, y: {props.y})
</p>
)
}
const MousePosition = withMouse(Position)
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
<MousePosition />
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
9.2 设置displayName
使用高阶组件存在的问题:得到的两个组件名称相同,不利于调试
默认情况下,React使用组件名称作为displayName
displayName的作用:用于设置调试信息(ReactDeveloper Tools信息)
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
import PropType from 'prop-types'
// 创建高阶组件
function withMouse(WrappedComponent){
// 提供复用的状态逻辑
class Mouse extends React.Component{
state = {
x: 0,
y: 0
}
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove', this.handleMouseMove)
}
render(){
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
// 设置displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
return Mouse
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
const Position = props => {
return (
<p>
鼠标当前位置:(x: {props.x}, y: {props.y})
</p>
)
}
const MousePosition = withMouse(Position)
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
<MousePosition />
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
9.3 传递props
问题:props丢失
原因:高阶组件没有往下传递props
解决:渲染WrappedComponent时,将state和this.props一起传递给组件
javascript
import React from "react";
import ReactDOM from "react-dom/client";
// 创建高阶组件
function withMouse(WrappedComponent){
// 提供复用的状态逻辑
class Mouse extends React.Component{
state = {
x: 0,
y: 0
}
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove', this.handleMouseMove)
}
render(){
console.log('Mouse中的props:', this.props)
return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>
}
}
// 设置displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
return Mouse
}
function getDisplayName(WrappedComponent){
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
const Position = props => {
console.log("Position中的props:",props)
return (
<p>
鼠标当前位置:(x: {props.x}, y: {props.y})
</p>
)
}
const MousePosition = withMouse(Position)
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
<MousePosition a='1'/>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
10、Refs
获取原生的DOM对象:
1.可以使用传统的document来对DOM进行操作
2.直接从React获取DOM对象:
- 创建一个存储DOM对象的容器
- 使用useRef()钩子函数
- 在DOM可用时,通过.current拿到DOM对象
注意:
- React中的钩子函数只能用于函数组件或自定义钩子
- 钩子函数只能在函数组件中调用
App.js
javascript
import { useRef } from "react"
function App(){
const inputRef = useRef(null)
const showDom = () =>{
console.dir(inputRef.current)
}
return(
<div>
<input type="text" ref={inputRef}></input>
<button onClick={showDom}>点击</button>
</div>
)
}
// 导出App
export default App
10.1 React.forwardRef(使用ref暴露DOM节点给父组件)
App.js
javascript
import { forwardRef, useRef } from "react";
// const Son = () => {
// return(
// <div>
// this is son.
// <input type="text"/>
// </div>
// )
// }
const Son = forwardRef((props, ref)=>{
return <input type="text" ref={ref}/>
})
// 通过ref获取子组件内部的input元素让其聚焦
function App() {
const sonRef = useRef(null)
const showRef = () => {
console.log(sonRef)
sonRef.current.focus()
}
return (
<>
this is App.<br/>
<Son ref={sonRef}/>
<button onClick={showRef}>focus</button>
</>
);
}
export default App;
10.2 useInperativeHandle(通过ref暴露子组件中的方法)
App.js
javascript
import { forwardRef, useImperativeHandle, useRef } from "react";
const Son = forwardRef((props, ref)=>{
// 实现聚焦逻辑
const inputRef = useRef(null)
const focusHandler = () => {
inputRef.current.focus()
}
// 把聚焦方法暴露出去
useImperativeHandle(ref, ()=>{
return(
focusHandler
)
})
// 返回函数
// useImperativeHandle(ref, () => ({
// focusHandler
// }));
return <input type="text" ref={inputRef}/>
})
// 通过ref获取子组件内部的focus方法实现聚焦
function App() {
const sonRef = useRef(null)
const focusHandler = () => {
console.log(sonRef)
sonRef.current()
}
return (
<>
this is App.<br/>
<Son ref={sonRef}/>
<button onClick={focusHandler}>focus</button>
</>
);
}
export default App;
React原理
1、setState()的说明
setState()是异步更新数据
可以多次调用setState(),只会触发一次重新渲染(考虑到性能)
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
console.log('count:', this.state.count) // 打印的数字总是比页面显示的小一位数,说明setState是异步更新
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>计数:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
1.2 推荐:使用setState((state, props)=>{})语法
参数state:表示最新的state
参数props:表示最新的props
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
// 还是异步更新
// state: 2
this.setState((state, props)=>{
return {
count: state.count + 1
}
})
// state: 3
this.setState((state, props)=>{
console.log("第二个setState:", this.state)
return {
count: state.count + 1
}
})
console.log(this.state.count)
}
render() {
return (
<div>
<h1>计数:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
1.2 第二个参数
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法:setState(update[, callback])
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
this.setState((state, props)=>{
return {
count: state.count + 1
}
}, ()=>{
console.log('状态更新完成:', this.state.count) // 2
console.log(document.getElementById('title').innerHTML)
})
console.log(this.state.count) // 1
}
render() {
return (
<div>
<h1 id="title">计数:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
2、组件更新机制
setState()的作用:1.修改state;2.更新组件(UI)
过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树
3、组件性能优化
3.1 减轻state
减轻state:只存储根组件渲染相关的数据(比如:count/列表数据/loading等)
注意:不用做渲染的数据不要放在state中,比如定时器id等
对于这种需要在多个方法中用到的数据,应该放在this中
3.2 避免不必要的重新渲染
组件更新机制:父组件更新会引起子组件也会被更新
问题:子组件没有任何变化时也会重新渲染
解决:使用钩子函数shouldComponentUpdate(nextProps, nextState)
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate->render)
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
this.setState((state)=>{
return {
count: state.count + 1
}
})
}
shouldComponentUpdate(nextProps, nextState){
// 可以根据this.state更新前的值和nextState更新后的值对比决定是否相同重新渲染组件
console.log('最新的state:', nextState)
console.log("this.state:", this.state)
return true
}
render() {
console.log('组件更新了')
return (
<div>
<h1 id="title">计数:{this.state.count}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
3.3 纯组件(提升性能)
解决父组件更新会引起子组件也会被更新
纯组件内部的对比是shallow compare(浅层对比)
对于值类型来说:比较两个值是否相同(直接赋值即可)
对于引用类型来说:只比较对象的引用(地址)是否相同
注意:state或props中属性值为引用类型时,应该创建新数据,不能直接修改原数据
index.js
javascript
import React from "react";
import ReactDOM from "react-dom/client";
class App extends React.PureComponent {
state = {
obj: {
number: 0
}
}
handleClick = () => {
// 创建新对象(利用扩展运算符获取原来对象的属性)
const newObj = {...this.state.obj, number: Math.floor(Math.random()*3)}
this.setState({
obj: newObj
})
// 如果是数组,不能使用push/unshift直接修改当前数组的方法,应使用concat/slice等这些返回新数组的方法
// // 错误示例:直接修改原始对象中属性的值
// // 纯组件在对比的时候,发现两个对象的地址值是相同的,所以不会重新渲染
// const newObj = this.state.obj
// newObj.number = Math.floor(Math.random()*3)
// this.setState(()=>{
// return ({
// obj: newObj
// })
// })
}
render() {
console.log('组件更新了')
return (
<div>
<h1 id="title">计数:{this.state.obj.number}</h1>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
4、虚拟DOM和Diff算法
React更新视图的思想:只要state变化就重新渲染视图
理想状态:部分更新(虚拟DOM配合Diff算法),只更新变化的地方
虚拟DOM本质就是一个JS对象,用来描述屏幕上看到的内容
优点:
- 降低API复杂度
- 解决兼容问题
- 提升性能(减少DOM的不必要操作)
执行过程:
- 初次渲染render()时,React会根据初始state(Model)和JSX结构,创建一个虚拟DOM对象(树)
- 根据虚拟DOM生成真正的DOM,渲染到页面中
- 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法对比,得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到真实DOM中,重新渲染到页面
比较两次数据时,React回先比较父元素,父元素不同,则直接所有元素全部替换;
父元素一致,再去逐个比较子元素,直到找到所有发生变化的元素为止
当在JSX中显示数组,数组中每个元素都需要设置一个唯一key,否则控制台会红色警告
重新渲染页面时,React会按照顺序依次比较对应的元素
但是当列表最前边插入一个新元素,其余元素内容没有发生变化,但由于新元素插入了开始位置,其余元素的位置全都发生变化,React默认是根据位置比较元素
为了解决这个问题,React为列表设计了一个key属性。key的作用相当于ID,只是无法在页面中查看,再次比较元素时,就会根据key来比较,而不是按照顺序比较
注意:开发一般采用数据的id作为key(这个key在当前列表唯一即可);尽量不要使用元素的索引index作为key,索引会跟着元素顺序的改变而改变,所以使用索引做key和没有key是一样的,唯一不同的是控制台的警告没有了
React Hook
1、userEffect()
userEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送AJAX请求、更改DOM等
语法:useEffect( ()=>{ }, [ ] )
参数1是一个函数(副作用函数),在函数内部可以放置要执行的操作
参数2是一个数组(可选参),在数组放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次
javascript
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>
this is App.
<ul>
{list.map(item=><li key={item.id}>{item.name}</li>)}
</ul>
</div>
)
}
// 导出App
export default App
userEffect依赖项参数说明
userEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现
|---------|-------------------|
| 依赖项 | 副作用函数执行时机 |
| 没有依赖项 | 组件初始渲染+组件更新时执行 |
| 空数组依赖 | 只在初始渲染时执行一次 |
| 添加特定依赖项 | 组件初始渲染+特性依赖项变化时执行 |
userEffect清除副作用
在userEffect中开启了一个定时器,在组件卸载时把这个定时器再清理掉,这个过程就是清除副作用
清除副作用的函数最常见的执行时机是在组件卸载时自动执行
javascript
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>
{show && <Son/>}
<button onClick={()=>setShow(false)}>卸载Son组件</button>
</div>
)
}
// 导出App
export default App
2、自定义Hook函数
自定义Hook是以use开头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用
javascript
import { useState } from "react"
// 封装自定义Hook函数
function useToggle(){
const [show, setShow] = useState(true)
const toggle = () => setShow(!show)
return{
show,
toggle
}
}
function App(){
const {show, toggle} = useToggle()
return(
<div>
{show && <div>你好</div>}
<button onClick={toggle}>卸载div</button>
</div>
)
}
// 导出App
export default App
React Hooks使用规则
- 只能在组件中或者其他自定义Hook函数中调用
- 只能在组件的顶层调用,不能嵌套在if、for、其他函数中
3、useMemo()
作用:在组件每次重新渲染的时候缓存计算的结果,可以保证只有依赖项发生变化时才会重新计算
App.js
javascript
import { useMemo, useState } from "react";
function fib(n){
console.log('计算函数执行了')
if(n<3){
return 1
}
return fib(n-2) + fib(n-1)
}
function App() {
const [count1, setCount1] = useState(0)
const result = useMemo(()=>{
// 只有count1发生变化时,函数才会执行
return fib(count1)
}, [count1])
const [count2, setCount2] = useState(0)
console.log('组件重新渲染了')
return (
<div>
this is App.<br/>
<button onClick={()=>setCount1(count1+1)}>{count1}</button>
<button onClick={()=>setCount2(count2+1)}>{count2}</button>
{result}
</div>
);
}
export default App;
4、React.memo方法
作用:允许组件在Props没有改变的情况下跳过渲染
React组件默认渲染机制:只要父组件重新渲染,子组件也跟着重新渲染
经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染
App.js
javascript
import { memo, useState } from "react";
const MemoSon = memo(function Son(){
console.log('子组件,重新渲染了')
return(
<div>
this is son.
</div>
)
})
function App() {
const [count, setCount] = useState(0)
return (
<div>
this is App.<br/>
<MemoSon/>
<button onClick={()=>setCount(count+1)}>+{count}</button>
</div>
);
}
export default App;
React.memo的props比较机制
在使用memo缓存组件之后,React会对每一个prop使用Object.is比较新值和旧值,返回true则表示没有变化
prop是简单类型
Object.is(8, 8) => true // 没有变化
prop是引用类型(对象/数组)
Object([], []) => false // 有变化,React只关心引用是否变化
App.js
javascript
import { memo, useMemo, useState } from "react";
const MemoSon = memo(function Son({list}){
console.log('子组件,重新渲染了')
return(
<div>
this is son.<br/>
{list}
</div>
)
})
function App() {
const [count, setCount] = useState(0)
// const num = 10
const list = useMemo(()=>{
return [1,2,3]
// 空依赖表示函数在组件渲染完毕后执行一次,不会随依赖项变化而重新执行
}, [])
return (
<div>
this is App.<br/>
<MemoSon count={list}/>
<button onClick={()=>setCount(count+1)}>+{count}</button>
</div>
);
}
export default App;
5、useCallBack()
作用:在组件多次重新渲染的时候缓存函数 使用useCallBack包裹函数之后,函数可以保证在App重新渲染的时候保持引用稳定
App.js
javascript
import { memo, useCallback, useState } from "react";
const Input = memo(function Input({onChange}){
console.log('子组件,重新渲染了')
return(
<div>
this is son.<br/>
<input type="text" onChange={(e)=>onChange(e.target.value)}/>
</div>
)
})
function App() {
// 传给子组件的函数
const changeHandler = useCallback((value) => console.log(value), [])
const [count, setCount] = useState(0)
return (
<div>
this is App.<br/>
<Input onChange={changeHandler}/>
<button onClick={()=>setCount(count+1)}>+{count}</button>
</div>
);
}
export default App;
Redux
前言
Redux是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行
作用:通过集中管理的方式管理应用的状态
1、Redux基本使用
使用纯Redux实现计数器
使用步骤:
- 定义一个reducer函数(根据当前想要做的修改返回一个新的状态)
- 使用createStore方法传入reducer函数生成一个store实例对象
- 使用store实例的subscribe方法订阅数据的变化(数据一旦变化,可以得到通知)
- 使用store实例的dispatch方法提交action对象触发数据变化(告诉reduce想要怎么修改数据)
- 使用store实例的getState方法获取最新的状态数据更新到视图中
bash
# 安装 Redux Toolkit(简化 Redux 使用)和 React 绑定
npm install @reduxjs/toolkit react-redux
React Toolkit(RTK),编写Redux逻辑的方式,是一套工具的集合集
react-redux,用来衔接Redux和React的中间件
使用React Toolkit创建counterStore
store文件夹中modules文件夹里的counterStore.js
javascript
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState:{
count: 0
},
// 修改状态的方法(同步方法,支持直接修改)
reducers:{
inscrement(state){
state.count++
},
decrement(state){
state.count--
}
}
})
// 解构出来actionCreater函数
const {inscrement, decrement} = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 以按需导出的方式导出actionCreater
export {inscrement, decrement}
// 以默认导出的方式导出reducer
export default reducer
store文件夹中的index.js
javascript
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from './modules/counterStore'
const store = configureStore({
reducer:{
counter: counterReducer
}
})
export default store
为React注入store
react-redux负责把Redux和React链接起来,内置Provider组件通过store参数把创建好的store实例注入到应用中,链接正式建立
React组件中使用store中的数据
useSelector,作用是把store中的数据映射到组件中
src文件夹中的index.js
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './store'
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
reportWebVitals();
src文件夹中的App.js
javascript
import { useSelector } from "react-redux";
function App() {
const {count} = useSelector(state=>state.counter)
return (
<div>
{count}
</div>
);
}
export default App;
React组件中修改store中的数据
useDispatch,作用是生成提交action对象的dispatch
src文件夹中的App.js
javascript
import { useDispatch, useSelector } from "react-redux";
import {inscrement, decrement} from './store/modules/counterStore'
function App() {
const {count} = useSelector(state=>state.counter)
const dispatch = useDispatch()
return (
<div>
<button onClick={()=>dispatch(decrement())}>-</button>
{count}
<button onClick={()=>dispatch(inscrement())}>+</button>
</div>
);
}
export default App;
2、提交action传参
在reducers的同步修改方法中添加action对象参数,在调用actionCreater的时候传递参数,参数会传递到action对象payload属性上
store文件夹中modules文件夹里的counterStore.js
javascript
import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
name: 'counter',
// 初始化state
initialState:{
count: 0
},
// 修改状态的方法(同步方法,支持直接修改)
reducers:{
inscrement(state){
state.count++
},
decrement(state){
state.count--
},
addToNum(state, action){
state.count = action.payload
}
}
})
// 解构出来actionCreater函数
const {inscrement, decrement, addToNum} = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 以按需导出的方式导出actionCreater
export {inscrement, decrement, addToNum}
// 以默认导出的方式导出reducer
export default reducer
src文件夹中的App.js
javascript
import { useDispatch, useSelector } from "react-redux";
import {inscrement, decrement, addToNum} from './store/modules/counterStore'
function App() {
const {count} = useSelector(state=>state.counter)
const dispatch = useDispatch()
return (
<div>
<button onClick={()=>dispatch(decrement())}>-</button>
{count}
<button onClick={()=>dispatch(inscrement())}>+</button>
<button onClick={()=>dispatch(addToNum(10))}>add</button>
</div>
);
}
export default App;
3、管理异步状态操作
4、Redux调试-devtools
zustand(极简的状态管理工具)
1、安装
bash
npm install zustand
javascript
import {create} from 'zustand'
// 1.创建store
// 注意:
// - 函数参数必须返回一个对象,对象内部包含状态数据和方法
// - set是用来修改数据的专门方法,必须调用它来修改数据
const useStore = create((set)=>{
return {
count: 9,
inc:()=>{
set((state)=>({count: state.count+1}))
}
}
})
// 2.绑定store到组件
function App() {
const {count, inc} = useStore()
return (
<>
this is App.<br/>
<button onClick={inc}>{count}</button>
</>
);
}
export default App;
2、异步支持
直接在函数中编写异步逻辑,最后只需要调用set方法传入新状态即可
3、切片模式
当单个store比较大时,可以采用切片模式进行模块拆分组合,类似模块化
javascript
import { useEffect } from 'react'
import { create } from 'zustand'
const URL = 'http://geek.itheima.net/v1_0/channels'
// 拆分子模块,再组合起来
const createCounterStore = (set) => {
return {
count: 9,
inc:()=>{
set((state)=>({count: state.count+1}))
},
}
}
const createChannelStore = (set) => {
return{
channelList: [],
fetchGetList: async () => {
const res = await fetch(URL)
const jsonRes = await res.json()
console.log(jsonRes)
set({
channelList: jsonRes.data.channels
})
}
}
}
const useStore = create((...a)=>{
return{
...createCounterStore(...a),
...createChannelStore(...a)
}
})
function App() {
// 组件使用
const {count, inc, fetchGetList, channelList} = useStore()
useEffect(()=>{
fetchGetList()
}, [fetchGetList])
return (
<>
this is App.<br/>
<button onClick={inc}>{count}</button>
<ul>
{
channelList.map((item)=><li key={item.id}>{item.name}</li>)
}
</ul>
</>
);
}
export default App;
React路由
前言
前端路由的功能:让用户从一个视图(页面)导航到另外一个视图
前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
使用React路由来说,就是配置路径和组件(配对)
1、路由的基本使用
bash
yarn add react-router-dom # npm i react-router-dom
index.js
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {createBrowserRouter, RouterProvider} from 'react-router-dom'
const router = createBrowserRouter([
{
path: '/login',
element: <div>登录页</div>
},
{
path: '/article',
element: <div>文章页</div>
}
])
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
reportWebVitals();
2、抽象路由模块
src下的page文件夹中的Article文件夹中的index.js
javascript
const Article = () => {
return(
<div>文章页</div>
)
}
export default Article
src下的page文件夹中的Login文件夹中的index.js
javascript
const Login = () => {
return(
<div>登录页</div>
)
}
export default Login
src下的router文件夹中的index.js
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
src下的index.js
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import reportWebVitals from './reportWebVitals';
import { RouterProvider } from 'react-router-dom'
import router from './router';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
reportWebVitals();
3、路由导航
路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信
3.1 声明式导航
声明式导航通过<Link/>组件描述出要跳转到哪里去,通过组件to属性指定要跳转的路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数
src下的page文件夹中的Login文件夹中的index.js
javascript
import { Link } from "react-router-dom"
const Login = () => {
return(
<div>
登录页
<br/>
<Link to="/article">跳转到文章页</Link>
</div>
)
}
export default Login
3.2 编程式导航
编程式导航通过'useNavigate'钩子得到导航方法,然后通过调用方法以命令式形式进行路由跳转,通过调用navigate方法传入path实现跳转
src下的page文件夹中的Login文件夹中的index.js
javascript
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return(
<div>
登录页
<br/>
<Link to="/article">声明式跳转到文章页</Link>
<button onClick={()=>navigate('/article')}>编程式跳转到文章页</button>
</div>
)
}
export default Login
4、导航跳转传参
4.1 searchParams传参
src下的page文件夹中的Login文件夹中的index.js
javascript
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return(
<div>
登录页
<br/>
<Link to="/article">声明式跳转到文章页</Link>
<button onClick={()=>navigate('/article?id=10&name="lili"')}>searchParams传参</button>
</div>
)
}
export default Login
src下的page文件夹中的Article文件夹中的index.js
javascript
import { useSearchParams } from "react-router-dom"
const Article = () => {
const [params] = useSearchParams()
const id = params.get('id')
const name = params.get('name')
return(
<div>
文章页
<p>searchParams传参里的参数:{id}, {name}</p>
</div>
)
}
export default Article
4.2 params传参
src下的page文件夹中的Login文件夹中的index.js
javascript
import { Link, useNavigate } from "react-router-dom"
const Login = () => {
const navigate = useNavigate()
return(
<div>
登录页
<br/>
<Link to="/article">声明式跳转到文章页</Link>
<button onClick={()=>navigate('/article?id=10&name="lili"')}>searchParams传参</button>
<button onClick={()=>navigate('/article/21')}>params传参</button>
</div>
)
}
export default Login
src下的router文件夹中的index.js
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
src下的page文件夹中的Article文件夹中的index.js
javascript
import { useParams, useSearchParams } from "react-router-dom"
const Article = () => {
// const [params] = useSearchParams()
// const id = params.get('id')
// const name = params.get('name')
const params = useParams()
const id = params.id
return(
<div>
文章页
{/* <p>searchParams传参里的参数:{id}, {name}</p> */}
<p>params传参里的参数:{id}</p>
</div>
)
}
export default Article
5、嵌套路由
嵌套路由:在一级路由中又内嵌其他路由。嵌套至一级路由内的路由又称为二级路由
步骤:
- 使用children属性配置路由嵌套关系
- 使用'<Outlet/>'组件配置二级路由渲染位置
src下page文件夹中Board文件夹中的index.js
javascript
const Board = () => {
return(
<div>
面板页
</div>
)
}
export default Board
src下page文件夹中About文件夹中的index.js
javascript
const About = () => {
return(
<div>
关于页
</div>
)
}
export default About
src下page文件夹中Layout文件夹中的index.js
javascript
import { Link, Outlet } from "react-router-dom"
const Layout = () => {
return(
<div>
一级路由layout组件<br/>
<Link to="/board">面板</Link><br/>
<Link to="/about">关于</Link>
{/* 配置二级路由的出口 */}
<Outlet/>
</div>
)
}
export default Layout
src下router文件夹中的index.js
javascript
import Layout from "../page/Layout";
import Board from "../page/Board";
import About from "../page/About"
import {createBrowserRouter} from 'react-router-dom'
const router = createBrowserRouter([
{
// 一级路由
path: '/',
element: <Layout/>,
// 二级路由
children:[
{
path: 'board',
element: <Board/>
},
{
path: 'about',
element: <About/>
}
]
}
])
export default router
6、默认二级路由配置
当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true
src下router文件夹中的index.js
javascript
import Layout from "../page/Layout";
import Board from "../page/Board";
import About from "../page/About"
import {createBrowserRouter} from 'react-router-dom'
const router = createBrowserRouter([
{
// 一级路由
path: '/',
element: <Layout/>,
// 二级路由
children:[
{
// 设置默认二级路由
index: true,
element: <Board/>
},
{
path: 'about',
element: <About/>
}
]
}
])
export default router
src下page文件夹中Layout文件夹中的index.js
javascript
import { Link, Outlet } from "react-router-dom"
const Layout = () => {
return(
<div>
一级路由layout组件<br/>
<Link to="/">面板</Link><br/>
<Link to="/about">关于</Link>
{/* 配置二级路由的出口 */}
<Outlet/>
</div>
)
}
export default Layout
7、404路由
当浏览器输入url的路径在整个路由配置中都找不到对应的path,可以使用404兜底组件进行渲染
步骤:
- 准备一个NotFound组件
- 在路由表数组的末尾,以*号作为路由path配置路由
src下page文件夹中NotFound文件夹中的index.js
javascript
const NotFount = () => {
return(
<div>404页面</div>
)
}
export default NotFount
src下router文件夹中的index.js
javascript
import {createBrowserRouter} from 'react-router-dom'
import NotFount from "../page/NotFound";
const router = createBrowserRouter([
{
path: '*',
element: <NotFount/>
}
])
export default router
8、两种路由模式
history模式和hash模式,ReactRouter分别由createBrowerRouter和createHashRouter函数负责创建
|---------|-------------|-----------------------|----------|
| 路由模式 | url表现 | 底层原理 | 是否需要后端支持 |
| history | url/login | history对象+pushState事件 | 需要 |
| hash | url/#/login | 监听hashChange事件 | 不需要 |
9、配置@别名路径
通过@代替src路径,方便开发过程中的路径查找访问
bash
npm i @craco/craco -D
步骤:
- 针对路径转换,修改webpack别名路径配置craco
- 针对联想提示,修改VSCode配置jsconfig.json
craco.config.js
javascript
// 扩展webpack配置
const path = require('path')
module.exports = {
webpack:{
alias:{
'@': path.resolve(__dirname, 'src')
}
}
}
package.json
javascript
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
src下router文件夹中的index.js
javascript
import Layout from "@/page/Layout";
import Board from "@/page/Board";
import About from "@/page/About"
import {createBrowserRouter, createHashRouter} from 'react-router-dom'
import NotFount from "@/page/NotFound";
const router = createHashRouter([
{
// 一级路由
path: '/',
element: <Layout/>,
// 二级路由
children:[
{
// 设置默认二级路由
index: true,
element: <Board/>
},
{
path: 'about',
element: <About/>
}
]
},
{
path: '*',
element: <NotFount/>
}
])
export default router
jsconfig.json
javascript
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
}
}
额外知识
1、安装SCSS
SCSS是一种后缀名为.scss的预编译CSS语言,支持一些原生CSS不支持的高级用法,比如变量使用、嵌套语法等
bash
npm i sass -D
2、安装Ant Design
Ant Design是由蚂蚁金服出品的社区使用最广的React PC组件库,可以快速开发PC管理后台项目
bash
npm i antd --save
# 兼容React 19
npm install @ant-design/v5-patch-for-react-19 --save