jieye の 数字花园

Search

Search IconIcon to open search

第1章:React入门

Last updated Jul 21, 2022

# 第1章:React入门

# 1.1. React简介

1.1.1. 官网

  1. 英文官网: https://reactjs.org/
  2. 中文官网: https://react.docschina.org/

1.1.2. 介绍描述

  1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)
  2. 由Facebook开源

1.1.3. React的特点

  1. 声明式编码
  2. 组件化编码
  3. React Native 编写原生应用
  4. 高效(优秀的Diffing算法)

1.1.4. React高效的原因

  1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  2. DOM Diffing算法, 最小化页面重绘。

# 1.2. React的基本使用

# 1.2.1. 效果

image-20210113103850309

# 1.2.2. 相关js库

  1. react.js:React核心库。
  2. react-dom.js:提供操作DOM的react扩展库。
  3. babel.min.js:解析JSX语法代码转为JS代码的库。

# 1.2.3. 创建虚拟DOM的两种方式

  1. 纯JS方式(一般不用)
  2. JSX方式

# 1.2.4. 虚拟DOM与真实DOM

  1. React提供了一些API来创建一种 “特别” 的一般js对象

image-20210113103942744

创建的就是一个简单的虚拟DOM对象

  1. 虚拟DOM对象最终都会被React转换为真实的DOM
  2. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。

# 1.3. React JSX

# 1.3.1. 效果

image-20210113104020994

# 1.3.2. JSX

  1. 全称: JavaScript XML

  2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是 image-20210113104045723方法的语法糖

  3. 作用: 用来简化创建虚拟DOM

    1. 写法:var ele = <h1>Hello JSX!</h1>

    2. 注意1:它不是字符串, 也不是HTML/XML标签

    3. 注意2:它最终产生的就是一个JS对象

  4. 标签名任意: HTML标签或其它标签

  5. 标签属性任意: HTML标签属性或其它

  6. 基本语法规则

    1. 遇到 <开头的代码, 以标签的语法解析: html同名标签转换为html同名元素, 其它标签需要特别解析

    2. 遇到以 { 开头的代码,以JS语法解析: 标签中的js表达式必须用{ }包含

  7. babel.js的作用

    1. 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行

    2. 只要用了JSX,都要加上type=“text/babel”, 声明需要babel来处理

# 1.3.3. 渲染虚拟DOM(元素)

  1. 语法: ReactDOM.render(virtualDOM, containerDOM)

  2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

  3. 参数说明

    1. 参数一: 纯js或jsx创建的虚拟dom对象

    2. 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

# 1.3.4. JSX练习

需求: 动态展示如下列表

image-20210113105711179

# 1.4. 模块与组件、模块化与组件化的理解

# 1.4.1. 模块

  1. 理解:向外提供特定功能的js程序, 一般就是一个js文件
  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
  3. 作用:复用js, 简化js的编写, 提高js运行效率

# 1.4.2. 组件

  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
  2. 为什么要用组件: 一个界面的功能更复杂
  3. 作用:复用编码, 简化项目编码, 提高运行效率

# 1.4.3. 模块化

当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

# 1.4.4. 组件化

当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

image-20210113105809103

# 第2章:React面向组件编程

# 2.1. 基本理解和使用

# 2.1.1. 使用React开发者工具调试

# 2.1.2. 效果

image-20210113105843606

# 2.1.3. 注意

  1. 组件名必须首字母大写
  2. 虚拟DOM元素只能有一个根元素
  3. 虚拟DOM元素必须有结束标签

# 2.1.4. 渲染类组件标签的基本流程

  1. React内部会创建组件实例对象
  2. 调用render()得到虚拟DOM, 并解析为真实DOM
  3. 插入到指定的页面元素内部

# 2.2. 组件三大核心属性1: state

# 2.2.1. 效果

需求: 定义一个展示天气信息的组件

1. 默认展示天气炎热 凉爽

2. 点击文字切换天气

# 2.2.2. 理解

  1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
  2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

# 2.2.3. 强烈注意

  1. 组件中render方法中的this为组件实例对象

  2. 组件自定义的方法中this为undefined,如何解决?

    a) 强制绑定this: 通过函数对象的bind()

    b) 箭头函数

  3. 状态数据,不能直接修改或更新

# 2.3. 组件三大核心属性2: props

# 2.3.1. 效果

需求: 自定义用来显示一个人员信息的组件

1. 姓名必须指定,且为字符串类型;

2. 性别为字符串类型,如果性别没有指定,默认为男

3. 年龄为字符串类型,且为数字类型,默认值为**18

# 2.3.2. 理解

  1. 每个组件对象都会有props(properties的简写)属性
  2. 组件标签的所有属性都保存在props中

# 2.3.3. 作用

  1. 通过标签属性从组件外向组件内传递变化的数据
  2. 注意: 组件内部不要修改props数据

# 2.3.4. 编码操作

  1. 内部读取某个属性值

this.props.name

  1. 对props中的属性值进行类型限制和必要性限制

    image-20210113112413459

  2. 扩展属性: 将对象的所有属性通过props传递

image-20210113112430463

  1. 默认属性值:

image-20210113112443390

  1. 组件类的构造函数

# 2.4. 组件三大核心属性3: refs与事件处理

# 2.4.1. 效果

需求: 自定义组件, 功能说明如下:

1. 点击按钮, 提示第一个输入框中的值

2. 当第2个输入框失去焦点时, 提示这个输入框中的值

# 2.4.2. 理解

组件内的标签可以定义ref属性来标识自己

# 2.4.3. 编码

image-20210113112907110

# 2.4.4. 事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)

    1. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件

    2. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)

  2. 通过event.target得到发生事件的DOM元素对象

# 2.5. 收集表单数据

# 2.5.1. 效果

需求: 定义一个包含表单的组件

输入用户名密码后, 点击登录提示输入信息

# 2.5.2. 理解

包含表单的组件分类

  1. 受控组件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    class Login extends React.Component{
    
    			//初始化状态
    			state = {
    				username:'', //用户名
    				password:'' //密码
    			}
    
    			//保存用户名到状态中
    			saveUsername = (event)=>{
    				this.setState({username:event.target.value})
    			}
    
    			//保存密码到状态中
    			savePassword = (event)=>{
    				this.setState({password:event.target.value})
    			}
    
    			//表单提交的回调
    			handleSubmit = (event)=>{
    				event.preventDefault() //阻止表单提交
    				const {username,password} = this.state
    				alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
    			}
    
    			render(){
    				return(
    					<form onSubmit={this.handleSubmit}>
    						用户名<input onChange={this.saveUsername} type="text" name="username"/>
    						密码<input onChange={this.savePassword} type="password" name="password"/>
    						<button>登录</button>
    					</form>
    				)
    			}
    		}
    
  2. 非受控组件

# 2.6. 组件的生命周期

# 2.6.1. 效果

需求*:*定义组件实现以下功能:

1. 让指定的文本做显示 / 隐藏的渐变动画

2. 从完全可见,到彻底消失,耗时**2S

3. 点击“不活了”按钮从界面中卸载组件

# 2.6.2. 理解

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

# 2.6.3. 生命周期流程图(旧)

image-20210113113031023

生命周期的三个阶段(旧)

1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

  1. constructor()
  2. componentWillMount()
  3. render()
  4. componentDidMount()

2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

  1. shouldComponentUpdate()
  2. componentWillUpdate()
  3. render()
  4. componentDidUpdate()

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()

# 2.6.4. 生命周期流程图(新)

image-20210113113130146

生命周期的三个阶段(新)

1. 初始化阶段: 由ReactDOM.render()触发—初次渲染

  1. constructor()

2. getDerivedStateFromProps

  1. render()
  2. componentDidMount()

2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

  1. getDerivedStateFromProps
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate()

3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  1. componentWillUnmount()

# 2.6.5. 重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

# 2.6.6. 即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

# 2.7. 虚拟DOM与DOM Diffing算法

# 2.7.1. 效果

需求:验证虚拟DOM Diffing算法的存在

# 2.7.2. 基本原理图

image-20210113114017515

# 第3章:React应用(基于React脚手架)

# 3.1. 使用create-react-app创建react应用

# 3.1.1. react脚手架

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
  2. 包含了所有需要的配置(语法检查、jsx编译、devServer…)
  3. 下载好了所有相关的依赖
  4. 可以直接运行一个简单效果
  5. react提供了一个用于创建react项目的脚手架库: create-react-app
  6. 项目的整体技术架构为: react + webpack + es6 + eslint
  7. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

# 3.1.2. 创建项目并启动

第一步,全局安装:npm i -g create-react-app

第二步,切换到想创项目的目录,使用命令:create-react-app hello-react

第三步,进入项目文件夹:cd hello-react

第四步,启动项目:npm start

# 3.1.3. react脚手架项目结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public ---- 静态资源文件夹

​            favicon.icon ------ 网站页签图标

​            index.html --------** **主页面

​            logo192.png ------- logo图

​            logo512.png ------- logo图

​            manifest.json ----- 应用加壳的配置文件

​            robots.txt -------- 爬虫协议文件

src ---- 源码文件夹

​            App.css -------- App组件的样式

​            App.js --------- App组件

​            App.test.js ---- 用于给App做测试

​            index.css ------ 样式

​            index.js -------入口文件

​            logo.svg ------- logo图

​            reportWebVitals.js

​                    --- 页面性能分析文件(需要web-vitals库的支持)

​            setupTests.js

​                    ---- 组件单元测试的文件(需要jest-dom库的支持)

# 3.1.4. 功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件

  2. 实现静态组件: 使用组件实现静态页面效果

  3. 实现动态组件

    ​ 3.1 动态显示初始化数据

    ​ 3.1.1 数据类型

    ​ 3.1.2 数据名称

    ​ 3.1.2 保存在哪个组件?

    3.2 交互(从绑定事件监听开始)

# 3.2. 组件的组合使用-TodoList

功能: 组件化实现此功能

1. 显示所有todo列表

2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本

# 第7章:redux

# 7.1. redux理解

# 1.求和案例_redux精简版

  1. 去除Count组件自身的状态
  2. src下建立:

​ -redux

​ -store.js

​ -count_reducer.js

  1. store.js:

​ 1.引入redux中的createStore函数,创建一个store

​ 2.createStore调用时要传入一个为其服务的reducer

​ 3.记得暴露store对象

  1. count_reducer.js:

​ 1. reducer的本质是一个函数,接收:preState,action,返回加工后的状态

​ 2. reducer有两个作用:初始化状态,加工状态

​ 3. reducer被第一次调用时,是store自动触发的,

​ 传递的preState是undefined,

​ 传递的action是:{type:’@@REDUX/INIT_a.2.b.4}

  1. 在index.js中监测store中状态的改变,一旦发生改变重新渲染

​ 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。

# 2.求和案例_redux完整版

新增文件:

  1. count_action.js 专门用于创建action对象
  2. constant.js 放置容易写错的type值

# 3.求和案例_redux异步action版

  1. 明确:延迟的动作不想交给组件自身,想交给action
  2. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
  3. 具体编码:

​ 1. yarn add redux-thunk,并配置在store中

​ 2. 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。

​ 3. 异步任务有结果后,分发一个同步的action去真正操作数据。

  1. 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。

# 4.求和案例_react-redux基本使用

  1. 明确两个概念:

​ 1.UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。

​ 2.容器组件:负责和redux通信,将结果交给UI组件。

  1. 如何创建一个容器组件————靠react-redux 的 connect函数

​ connectmapStateToProps,mapDispatchToPropsUI组件

​ -mapStateToProps:映射状态,返回值是一个对象

​ -mapDispatchToProps:映射操作状态的方法,返回值是一个对象

  1. 备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
  2. 备注2:mapDispatchToProps,也可以是一个对象

# 5.求和案例_react-redux优化

  1. 容器组件和UI组件整合一个文件
  2. 无需自己给容器组件传递store,给包裹一个即可。
  3. 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。
  4. mapDispatchToProps也可以简单的写成一个对象
  5. 一个组件要和redux“打交道”要经过哪几步?

​ 1. 定义好UI组件—不暴露

​ 2. 引入connect生成一个容器组件,并暴露,写法如下:

​ connect

​ state => {key:value}, //映射状态

​ {key:xxxxxAction} //映射操作状态的方法

​ UI组件

​ 4. 在UI组件中通过this.props.xxxxxxx读取和操作状态

# 6.求和案例_react-redux数据共享版

  1. 定义一个Pserson组件,和Count组件通过redux共享数据。
  2. 为Person组件编写:reducer、action,配置constant常量。
  3. 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,

​ 合并后的总状态是一个对象!!!

  1. 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”。

# 7.求和案例_react-redux开发者工具的使用

  1. yarn add redux-devtools-extension
  2. store中进行配置

​ import {composeWithDevTools} from ‘redux-devtools-extension’

​ const store = createStoreallReducer,composeWithDevToolsapplyMiddlewarethunk

# 8.求和案例_react-redux最终版

  1. 所有变量名字要规范,尽量触发对象的简写形式。
  2. reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer

# 7.1.1. 学习文档

  1. 英文文档: https://redux.js.org/
  2. 中文文档: http://www.redux.org.cn/
  3. Github: https://github.com/reactjs/redux

# 7.1.2. redux是什么

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件共享的状态。

# 7.1.3. 什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

# 7.1.4. redux工作流程

image-20210113121422231

# 7.2. redux的三个核心概念

# 7.2.1. action

  1. 动作的对象
  2. 包含2个属性
  1. 例子:{ type: ‘ADD_STUDENT’,data:{name: ’tom’,age:18} }

# 7.2.2. reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数.

# 7.2.3. store

  1. 将state、action、reducer联系在一起的对象

  2. 如何得到此对象?

    1. import {createStore} from ‘redux’

    2. import reducer from ‘./reducers’

    3. const store = createStore(reducer)

  3. 此对象的功能?

    1. getState(): 得到state

    2. dispatch(action): 分发action, 触发reducer调用, 产生新的state

    3. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

# 7.3. redux的核心API

# 7.3.1. createstore()

作用:创建包含指定reducer的store对象

# 7.3.2. store对象

  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

    1. state

    2. reducer

  3. 核心方法:

    1. getState()

    2. dispatch(action)

    3. subscribe(listener)

  4. 具体编码:

    1. store.getState()

    2. store.dispatch({type:‘INCREMENT’, number})

    3. store.subscribe(render)

# 7.3.3. applyMiddleware()

作用:应用上基于redux的中间件(插件库)

# 7.3.4. combineReducers()

作用:合并多个reducer函数

# 7.4. 使用redux编写应用

# 7.5. redux异步编程

# 7.5.1理解:

  1. redux默认是不能进行异步处理的,
  2. 某些时候应用中需要在redux****中执行异步任务(ajax, 定时器)

# 7.5.2. 使用异步中间件

npm install –save redux-thunk

# 7.6. react-redux

# 7.6.1. 理解

  1. 一个react插件库
  2. 专门用来简化react应用中使用redux

# 7.6.2. react-Redux将所有组件分成两大类

  1. UI组件

    1. 只负责 UI 的呈现,不带有任何业务逻辑

    2. 通过props接收数据(一般数据和函数)

    3. 不使用任何 Redux 的 API

    4. 一般保存在components文件夹下

  2. 容器组件

    1. 负责管理数据和业务逻辑,不负责UI的呈现

    2. 使用 Redux 的 API

    3. 一般保存在containers文件夹下

# 7.6.3. 相关API

  1. Provider:让所有组件都可以得到state数据

image-20210113121631846

  1. connect:用于包装 UI 组件生成容器组件

image-20210113121655155

  1. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性

image-20210113121704856

  1. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

# 7.7. 使用上redux调试工具

# 7.7.1. 安装chrome浏览器插件

image-20210113121721787

# 7.7.2. 下载工具依赖包

1
npm install --save-dev redux-devtools-extension

# 7.8. 纯函数和高阶函数

# 7.8.1. 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

  2. 必须遵守以下一些约束

    1. 不得改写参数数据

    2. 不会产生任何副作用,例如网络请求,输入和输出设备

    3. 不能调用Date.now()或者Math.random()等不纯的方法

  3. redux的reducer函数必须是一个纯函数

# 7.8.2. 高阶函数

  1. 理解: 一类特别的函数

    1. 情况1: 参数是函数

    2. 情况2: 返回是函数

  2. 常见的高阶函数:

    1. 定时器设置函数

    2. 数组的forEach()/map()/filter()/reduce()/find()/bind()

    3. promise

    4. react-redux中的connect函数

  3. 作用: 能实现更加动态, 更加可扩展的功能

# 脚手架配置代理总结

# 方法一

在package.json中追加如下

1
"proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

# 方法二

  1. 第一步:创建代理配置文件

在src下创建配置文件:src/setupProxy.js

  1. 编写setupProxy.js配置具体代理规则:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const proxy = require('http-proxy-middleware')
   
   module.exports = function(app) {
     app.use(
       proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
         target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
         changeOrigin: true, //控制服务器接收到的请求头中host字段的值
         /*
         	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
         	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
         	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
         */
         pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
       }),
       proxy('/api2', { 
         target: 'http://localhost:5001',
         changeOrigin: true,
         pathRewrite: {'^/api2': ''}
       })
     )
   }

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

# 注意点

# 一、todoList案例相关知识点

1.拆分组件、实现静态组件,注意:className、style的写法

2.动态初始化列表,如何确定将数据放在哪个组件的state中?

​ ——某个组件使用:放在其自身的state中

​ ——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)

3.关于父子之间通信:

​ 1.【父组件】给【子组件】传递数据:通过props传递

​ 2.【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数

4.注意defaultChecked 和 checked的区别,类似的还有:defaultValue 和 value

5.状态在哪里,操作状态的方法就在哪里

# 二、github搜索案例相关知识点

1.设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。

2.ES6小知识点:解构赋值+重命名

​ let obj = {a:{b:1}}

​ const {a} = obj; //传统解构赋值

​ const {a:{b}} = obj; //连续解构赋值

​ const {a:{b:value}} = obj; //连续解构赋值+重命名

3.消息订阅与发布机制

​ 1.先订阅,再发布(理解:有一种隔空对话的感觉)

​ 2.适用于任意组件间通信

​ 3.要在组件的componentWillUnmount中取消订阅

4.fetch发送请求(关注分离的设计思想)

​ try {

​ const response= await fetch(/api1/search/users2?q=${keyWord})

​ const data = await response.json()

​ console.log(data);

​ } catch (error) {

​ console.log(‘请求出错’,error);

​ }

​ ## 三、路由的基本使用

1.明确好界面中的导航区、展示区

2.导航区的a标签改为Link标签

<Link to="/xxxxx">Demo</Link>

3.展示区写Route标签进行路径的匹配

<Route path='/xxxx' component={Demo}/>

4.<App>的最外侧包裹了一个<BrowserRouter><HashRouter>

# 四、路由组件与一般组件

1.写法不同:

​ 一般组件:<Demo/>

​ 路由组件:<Route path="/demo" component={Demo}/>

2.存放位置不同:

​ 一般组件:components

​ 路由组件:pages

3.接收到的props不同:

​ 一般组件:写组件标签时传递了什么,就能收到什么

​ 路由组件:接收到三个固定的属性

​ history:

​ go: ƒ go(n)

​ goBack: ƒ goBack()

​ goForward: ƒ goForward()

​ push: ƒ push(path, state)

​ replace: ƒ replace(path, state)

​ location:

​ pathname: “/about”

​ search: ""

​ state: undefined

​ match:

​ params: {}

​ path: “/about”

​ url: “/about”

​ 1.NavLink可以实现路由链接的高亮,通过activeClassName指定样式名

# 六、Switch的使用

​ 1.通常情况下,path和component是一一对应的关系。

​ 2.Switch可以提高路由匹配效率(单一匹配)。

# 七、解决多级路径刷新页面样式丢失的问题

​ 1.public/index.html 中 引入样式时不写 ./ 写 / (常用)

​ 2.public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)

​ 3.使用HashRouter

# 八、路由的严格匹配与模糊匹配

​ 1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

​ 2.开启严格匹配:<Route exact={true} path="/about" component={About}/>

​ 3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

# 九、Redirect的使用

​ 1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

​ 2.具体编码:

1
2
3
4
5
<Switch>
       <Route path="/about" component={About}/>
       <Route path="/home" component={Home}/>
       <Redirect to="/about"/>
</Switch>

# 十、嵌套路由

​ 1.注册子路由时要写上父路由的path值

​ 2.路由的匹配是按照注册路由的顺序进行的

# 十二、编程式路由导航

​ 借助this.prosp.history对象上的API对操作路由跳转、前进、后退

​ -this.prosp.history.push()

​ -this.prosp.history.replace()

​ -this.prosp.history.goBack()

​ -this.prosp.history.goForward()

​ -this.prosp.history.go()

# 十三、BrowserRouter与HashRouter的区别

1.底层原理不一样:

​ BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。

​ HashRouter使用的是URL的哈希值。

2.path表现形式不一样

​ BrowserRouter的路径中没有#,例如:localhost:3000/demo/test

​ HashRouter的路径包含#,例如:localhost:3000/#/demo/test

3.刷新后对路由state参数的影响

​ (1).BrowserRouter没有任何影响,因为state保存在history对象中。

​ (2).HashRouter刷新后会导致路由state参数的丢失!!!

4.备注:HashRouter可以用于解决一些路径错误相关的问题。

# 十四、antd的按需引入+自定主题

1.安装依赖:yarn add react-app-rewired customize-cra babel-plugin-import less less-loader

2.修改package.json

1
2
3
4
5
6
"scripts": {
	"start": "react-app-rewired start",
	"build": "react-app-rewired build",
	"test": "react-app-rewired test",
	"eject": "react-scripts eject"
},

3.根目录创建config-overrides.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//配置具体的修改规则
const { override, fixBabelImports,
addLessLoader} = require('customize-cra');
module.exports = override(
	fixBabelImports('import', {
		libraryName: 'antd',
		libraryDirectory: 'es',
		style: true,
	}),
	addLessLoader({
		lessOptions:{
			javascriptEnabled: true,
			modifyVars: { '@primary-color': 
'green' },
		}
	}),
);

​ 4.备注:不用在组件里亲自引入样式了,即:import ‘antd/dist/antd.css’应该删掉

# 特别点

# 1. setState

# setState更新状态的2种写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
	(1). setState(stateChange, [callback])------对象式的setState
            1.stateChange为状态改变对象(该对象可以体现出状态的更改)
            2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
					
	(2). setState(updater, [callback])------函数式的setState
            1.updater为返回stateChange对象的函数。
            2.updater可以接收到state和props。
            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
		1.对象式的setState是函数式的setState的简写方式(语法糖)
		2.使用原则:
				(1).如果新状态不依赖于原状态 ===> 使用对象方式
				(2).如果新状态依赖于原状态 ===> 使用函数方式
				(3).如果需要在setState()执行后获取最新的状态数据, 
					要在第二个callback函数中读取

# 2. lazyLoad

# 路由组件的lazyLoad

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
	//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
	const Login = lazy(()=>import('@/pages/Login'))
	
	//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
	<Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>

# 4. Fragment

# 使用

<Fragment><Fragment>
<></>

# 作用

可以不用必须有一个真实的DOM根标签了


# 5. Context

# 理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

# 使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
1) 创建Context容器对象
	const XxxContext = React.createContext()  
	
2) 渲染子组时外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据
	<xxxContext.Provider value={数据}>
		子组件
    </xxxContext.Provider>
    
3) 后代组件读取数据

	//第一种方式:仅适用于类组件 
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据
	  
	//第二种方式: 函数组件与类组件都可以
	  <xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	        要显示的内容
	      )
	    }
	  </xxxContext.Consumer>

# 注意

在应用开发中一般不用context, 一般都用它的封装react插件

# 6. 组件优化

# Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

# 效率高的做法

只有当组件的state或props数据发生改变时才重新render()

# 原因

Component中的shouldComponentUpdate()总是返回true

# 解决

办法1: 
	重写shouldComponentUpdate()方法
	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
	使用PureComponent
	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
	注意: 
		只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
		不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

# 7. render props

# 如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

# children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

# render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

# 8. 错误边界

# 理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

# 特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

# 使用方式:

getDerivedStateFromError配合componentDidCatch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}

# 9. 组件通信方式总结

# 组件间的关系:

# 几种通信方式:

	1.props:
		(1).children props
		(2).render props
	2.消息订阅-发布:
		pubs-sub、event等等
	3.集中式管理:
		redux、dva等等
	4.conText:
		生产者-消费者模式

# 比较好的搭配方式:

	父子组件:props
	兄弟组件:消息订阅-发布、集中式管理
	祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

# Hooks

# React Hook/Hooks是什么?

  1. Hook是React 16.8.0版本增加的新特性/新语法
  2. 可以让你在函数组件中使用 state 以及其他的 React 特性

# State Hook

  1. State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作

  2. 语法: const [xxx, setXxx] = React.useState(initValue)

  3. useState()说明:

    • 参数: 第一次初始化指定的值在内部作缓存
    • 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
    • setXxx()2种写法:
      • setXxx(newValue) 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
      • setXxx(value => newValue) 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

# Effect Hook

  1. Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

  2. React中的副作用操作:

    • 发ajax请求数据获取
    • 设置订阅 / 启动定时器
    • 手动更改真实DOM
  3. 语法和说明:

    1
    2
    3
    4
    5
    6
    
    x f(() => { 
    	// 在此可以执行任何带副作用操作
    	return () => { // 在组件卸载前执行
    	// 在此做一些收尾工作, 比如清除定时器/取消订阅等
    	}
    }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
    
  4. 可以把 useEffect Hook看做如下三个函数的组合

    1
    2
    3
    
    componentDidMount()
    componentDidUpdate()
    componentWillUnmount() 
    

# Ref Hook

  1. Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
  2. 语法: const refContainer = useRef()
  3. 作用:保存标签对象,功能与React.createRef()一样

# React Router 6

# 1 简介

  1. React Router 以三个不同的包发布到 npm 上,它们分别为:
    1. react-router: 路由的核心库,提供了很多的:组件、钩子。
    2. react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
    3. react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。
  2. 与React Router 5.x 版本相比,改变了什么?
    1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。

    2. 语法的变化:component={About} 变为 element={<About/>}等。

    3. 新增多个hook:useParamsuseNavigateuseMatch等。

    4. 官方明确推荐函数式组件了!!!

      ……

# 2 Component

# 1. <BrowserRouter>

  1. 说明:<BrowserRouter> 用于包裹整个应用。

  2. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    
    ReactDOM.render(
      <BrowserRouter>
        {/* 整体结构(通常为App组件) */}
      </BrowserRouter>,root
    );
    

# 2. <HashRouter>

  1. 说明:作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。
  2. 备注:6.x版本中<HashRouter><BrowserRouter> 的用法与 5.x 相同。

# 3. <Routes/> 与 <Route/>

  1. v6版本中移出了先前的<Switch>,引入了新的替代者:<Routes>

  2. <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>

  3. <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。

  4. <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。

  5. 当URL发生变化时,<Routes> 都会查看其所有子 <Route> 元素以找到最佳匹配并呈现组件 。

  6. <Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。

  7. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    <Routes>
        /*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/
        <Route path="/login" element={<Login />}></Route>
    
       /*用于定义嵌套路由,home是一级路由,对应的路径/home*/
        <Route path="home" element={<Home />}>
           /*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/
          <Route path="test1" element={<Test/>}></Route>
          <Route path="test2" element={<Test2/>}></Route>
       </Route>
    
       //Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx
        <Route path="users">
           <Route path="xxx" element={<Demo />} />
        </Route>
    </Routes>
    
  1. 作用: 修改URL,且不发送网络请求(路由链接)。

  2. 注意: 外侧需要用<BrowserRouter><HashRouter>包裹。

  3. 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    import { Link } from "react-router-dom";
    
    function Test() {
      return (
        <div>
         <Link to="/路径">按钮</Link>
        </div>
      );
    }
    
  1. 作用: 与<Link>组件类似,且可实现导航的“高亮”效果。

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    // 注意: NavLink默认类名是active,下面是指定自定义的class
    
    //自定义样式
    <NavLink
        to="login"
        className={({ isActive }) => {
            console.log('home', isActive)
            return isActive ? 'base one' : 'base'
        }}
    >login</NavLink>
    
    /*
     默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
     当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
    */
    <NavLink to="home" end >home</NavLink>
    

# 6. <Navigate>

  1. 作用:只要<Navigate>组件被渲染,就会修改路径,切换视图。

  2. replace属性用于控制跳转模式(push 或 replace,默认是push)。

  3. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    import React,{useState} from 'react'
    import {Navigate} from 'react-router-dom'
    
    export default function Home() {
     const [sum,setSum] = useState(1)
     return (
       <div>
         <h3>我是Home的内容</h3>
         {/* 根据sum的值决定是否切换视图 */}
         {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
         <button onClick={()=>setSum(2)}>点我将sum变为2</button>
       </div>
     )
    }
    

# 7. <Outlet>

  1. <Route>产生嵌套时,渲染其对应的后续子路由。

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    
    //根据路由表生成对应的路由规则
    const element = useRoutes([
      {
        path:'/about',
        element:<About/>
      },
      {
        path:'/home',
        element:<Home/>,
        children:[
          {
            path:'news',
            element:<News/>
          },
          {
            path:'message',
            element:<Message/>,
          }
        ]
      }
    ])
    
    //Home.js
    import React from 'react'
    import {NavLink,Outlet} from 'react-router-dom'
    
    export default function Home() {
     return (
       <div>
         <h2>Home组件内容</h2>
         <div>
           <ul className="nav nav-tabs">
             <li>
               <NavLink className="list-group-item" to="news">News</NavLink>
             </li>
             <li>
               <NavLink className="list-group-item" to="message">Message</NavLink>
             </li>
           </ul>
           {/* 指定路由组件呈现的位置 */}
           <Outlet />
         </div>
       </div>
     )
    }
    

# 3 Hooks

# 1. useRoutes()

  1. 作用:根据路由表,动态创建<Routes><Route>

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    //路由表配置:src/routes/index.js
    import About from '../pages/About'
    import Home from '../pages/Home'
    import {Navigate} from 'react-router-dom'
    
    export default [
     {
       path:'/about',
       element:<About/>
     },
     {
       path:'/home',
       element:<Home/>
     },
     {
       path:'/',
       element:<Navigate to="/about"/>
     }
    ]
    
    //App.jsx
    import React from 'react'
    import {NavLink,useRoutes} from 'react-router-dom'
    import routes from './routes'
    
    export default function App() {
     //根据路由表生成对应的路由规则
     const element = useRoutes(routes)
     return (
       <div>
         ......
          {/* 注册路由 */}
          {element}
         ......
       </div>
     )
    }
    

# 2. useNavigate()

  1. 作用:返回一个函数用来实现编程式导航。

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    import React from 'react'
    import {useNavigate} from 'react-router-dom'
    
    export default function Demo() {
      const navigate = useNavigate()
      const handle = () => {
        //第一种使用方式:指定具体的路径
        navigate('/login', {
          replace: false,
          state: {a:1, b:2}
        }) 
        //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
        navigate(-1)
      }
    
      return (
        <div>
          <button onClick={handle}>按钮</button>
        </div>
      )
    }
    

# 3. useParams()

  1. 作用:回当前匹配路由的params参数,类似于5.x中的match.params

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    import React from 'react';
    import { Routes, Route, useParams } from 'react-router-dom';
    import User from './pages/User.jsx'
    
    function ProfilePage() {
      // 获取URL中携带过来的params参数
      let { id } = useParams();
    }
    
    function App() {
      return (
        <Routes>
          <Route path="users/:id" element={<User />}/>
        </Routes>
      );
    }
    

# 4. useSearchParams()

  1. 作用:用于读取和修改当前位置的 URL 中的查询字符串。

  2. 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。

  3. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    import React from 'react'
    import {useSearchParams} from 'react-router-dom'
    
    export default function Detail() {
     const [search,setSearch] = useSearchParams()
     const id = search.get('id')
     const title = search.get('title')
     const content = search.get('content')
     return (
       <ul>
         <li>
           <button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
         </li>
         <li>消息编号{id}</li>
         <li>消息标题{title}</li>
         <li>消息内容{content}</li>
       </ul>
     )
    }
    

# 5. useLocation()

  1. 作用:获取当前 location 信息,对标5.x中的路由组件的location属性。

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    import React from 'react'
    import {useLocation} from 'react-router-dom'
    
    export default function Detail() {
     const x = useLocation()
     console.log('@',x)
      // x就是location对象: 
     /*
       {
          hash: "",
          key: "ah9nv6sz",
          pathname: "/login",
          search: "?name=zs&age=18",
          state: {a: 1, b: 2}
        }
     */
     return (
       <ul>
         <li>消息编号{id}</li>
         <li>消息标题{title}</li>
         <li>消息内容{content}</li>
       </ul>
     )
    }
    

# 6. useMatch()

  1. 作用:返回当前匹配信息,对标5.x中的路由组件的match属性。

  2. 示例代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    <Route path="/login/:page/:pageSize" element={<Login />}/>
    <NavLink to="/login/1/10">登录</NavLink>
    
    export default function Login() {
      const match = useMatch('/login/:x/:y')
      console.log(match) //输出match对象
      //match对象内容如下:
      /*
       {
          params: {x: '1', y: '10'}
          pathname: "/LoGin/1/10"  
          pathnameBase: "/LoGin/1/10"
          pattern: {
           path: '/login/:x/:y', 
           caseSensitive: false, 
           end: false
          }
        }
      */
      return (
       <div>
          <h1>Login</h1>
        </div>
      )
    }
    

# 7. useInRouterContext()

​ 作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。

# 8. useNavigationType()

  1. 作用:返回当前的导航类型(用户是如何来到当前页面的)。
  2. 返回值:POPPUSHREPLACE
  3. 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

# 9. useOutlet()

  1. 作用:用来呈现当前组件中渲染的嵌套路由。

  2. 示例代码:

    1
    2
    3
    4
    
    const result = useOutlet()
    console.log(result)
    // 如果嵌套路由没有挂载,则result为null
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象
    

# 10.useResolvedPath()

  1. 作用:给定一个 URL值,解析其中的:path、search、hash值。