React(18-19)总结

前言

React 是由 Facebook(现 Meta)开发并开源的 JavaScript 前端 UI 库,用于构建用户界面,尤其适用于构建单页应用(SPA)和复杂交互式组件。

  1. 组件化架构

    React 采用组件化开发模式,将 UI 拆分为独立、可复用的组件。每个组件拥有自己的状态(state)和属性(props),便于维护与协作开发。

  2. 声明式编程

    开发者只需描述"UI 应该是什么样子",React 会自动高效地更新 DOM。相比命令式操作,代码更简洁、逻辑更清晰。

  3. 虚拟 DOM(Virtual DOM)

    React 在内存中维护一个轻量级的虚拟 DOM 树。当状态变化时,通过 Diff 算法计算新旧虚拟 DOM 差异,仅更新真实 DOM 中变更的部分,显著提升性能。

  4. 单向数据流

    数据从父组件通过 props 向子组件单向传递,使数据流向清晰、易于调试和追踪问题,避免数据混乱。

  5. JSX 语法支持

    JSX 允许在 JavaScript 中编写类似 HTML 的结构,提升代码可读性,并在编译时转换为标准 JS 函数调用(React.createElement)。

  6. 丰富的生态系统

    搭配 React Router(路由)、Redux/Zustand(状态管理)、Next.js(服务端渲染)等工具,可构建完整前端解决方案。

  7. 跨平台能力

    通过 React Native,可用相同理念开发原生移动应用(iOS/Android),实现"一次学习,多端开发"。

  8. 社区活跃、生态成熟

    拥有庞大的开发者社区和持续更新,文档完善,第三方库丰富,企业级应用广泛(如 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>&nbsp;
        {count}
      &nbsp;<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>&nbsp;
        {count}
      &nbsp;<button onClick={()=>dispatch(inscrement())}>+</button>
      &nbsp;<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
相关推荐
一直在学习的小白~1 小时前
React大模型网站-流式推送markdown转换问题以及开启 rehype-raw,rehype-sanitize,remark-gfm等插件的使用
react.js·chatgpt·文心一言
HIT_Weston1 小时前
50、【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 单/多线程分析(二)
前端·ubuntu·gitlab
我太想进步了C~~1 小时前
Prompt Design(提示词工程)入门级了解
前端·人工智能·算法
crary,记忆1 小时前
如何理解 React的UI渲染
前端·react.js·ui·前端框架
苏打水com1 小时前
Day1-3 夯实基础:HTML 语义化 + CSS 布局实战(对标职场 “页面结构搭建” 核心需求)
前端·css·html·js
m0_740043731 小时前
mapState —— Vuex 语法糖
java·前端·javascript·vue.js
哟哟耶耶1 小时前
WebPage-postcss-px-to-viewport前端适配
前端·javascript·postcss
7澄11 小时前
Java Web 底层解析:Servlet 执行流程、Tomcat 工作原理与自定义 Tomcat 实现
java·前端·servlet·tomcat·自定义tomcat·tomcat执行流程·servlet执行流程
拾忆,想起1 小时前
Dubbo延迟加载全解:从延迟暴露到延迟连接的深度优化
前端·微服务·架构·dubbo·safari