探地雷达数据处理
GNSS数据处理
-
探地雷达使用过程中,有一个基站RTK、一个绑定在探地雷达上的移动站RTK
-
由于要用基站RTK去纠正移动站RTK,因此移动站RTK的数据采集时间应当在基站RTK采集时间包含中
-
对RTK的数据.GNS文件的处理:使用HGO数据处理软件
-
HGO软件中:工具→RINEX转换工具
-


基站数据处理
- 基站RTK数据-1
- 注意转换基站数据时,前10s和后10s可以不要,防止启动和结束时数据的不稳定
- 起始时间加10,截止时间减10

- 基站RTK数据-2

- 转换后结果
- 转换后出现.o和.p文件

移动站数据处理
- 移动站RTK数据-1
- 可以看出其时间在基站RTK数据-1的范围内

- 移动站RTK数据-2
- 可以看出其时间在基站RTK数据-2的范围外
- 则需要对

RTK数据使用IE解算
- 创建项目——点击左上角圈中的按钮,然后下一步

- 导入移动站GNSS数据——刚刚转换后的.o文件,然后一直下一步

- 导入基站GNSS数据——刚刚转换后的.o文件,然后一直下一步

- 站点信息设置
- 坐标选项设置为平均值、投影设置为WGS84——然后下一步


GNSS解算
- process→Process GNSS

- 参数设置为如图所示——点击process


解算结果导出
- Output→Export Wizard
-
注意选择导出文件格式,这里可以选择cor,如果没有用new添加,格式如图所示,可以点击预览如图所示——然后一直下一步
-
cor的配置
-
Header/Footer Options参数设置


-
Field Separator参数设置


-
Export Variables内各项参数设置

具体参数如下图所示:




-
-




探地雷达数据绑定RTK
- 探地雷达数据每个项目的文件如下所示——有若干个数据,每个数据都包含一个time文件

- 利用探地雷达的time与GNSS输出文件中的time比较,就可以得到每个探地雷达数据的经纬度与海拔信息——所以要求GNSS的time比探测雷达的time长
1 | timeFilePath = 'GPR/2023041102line_time' |

- 将生成的cor文件复制回数据文件夹,替代原有的cor,但还会有一些cor为空,这一部分数据在后面是无法使用的

探地雷达数据的处理
-
打开CrossPoint
- 需要插U盘,要不然很多功能无法使用
-
打开同一个项目中的各项文件
- 如这里的6和7是同一个地方的项目
- 点进去这个文件夹就可以出现能打开的文件,注意与刚刚的cor对比,如果该文件的cor大小为0kb,这个文件就不能导入
- 打开后如图所示



数据处理
- 过滤器设置
- 目前我也不会设置,先简单打开这两个选项,那么左边的波形就会更加变化,我们将依据这个波形去判断是否有异常

- 0m深度位置设置,这个是为了使得到的高度正常
- 按道理来说,第一天反射横线应该是地面,也就是此次的深度为0,但是图示并不是这样
- 左键点住红色三角并向下拖动,就可以改变0深度位置,注意上下两个都要设置,上面的图为更细节的,也是我们在处理数据过程中用得更多的


- 波形图对比度调整(应该是叫做对比度)
- 分为左右两个,左边的调上面的图,右边的调下面的图,一般也就使用左边的调节上面的图
- 可以多调整实验一下,调整到可以清晰判断就行

- 显示文件数据设置
- 在判断过程中,我们经常需要观察两条相邻带之间有相似的异常
- 因此,可以在中间的波形图区域设置显示多条带,对比着看异常
- 在Tools→Settings→Data view中设置,设置一般为偶数,因为对每一条带都要一个细节的和一个不那么细节的数据需要展示
- 设置为6时如下图所示
- 因为我们不常使用下面那一条不那么细节的数据,因此可以将其缩小,便于我们观察需要的数据(鼠标放在蓝色条带上往下拉,拉倒两条数据的交界处),结果如图所示



- 异常点标注
- 如图中的明显异常点,可以标注出来
- 可以看出,三条带出现了类似的异常点,那么我们用同一种Marker标记
- 按照这种逻辑将所有的异常标出来,再导出和正射影像叠加分析,如图所示


PhotoScan三维重建及坐标转换
对齐图片(投影后)
添加图片

全景图片一张可以投影为若干张普通图片,在PhotoScan中可以将一张全景图片所投影出的图片设置为一个组,对其加以约束,会使得图片对齐效果更好
图片分组
- 首先选中一组图片,将其添加到一个图片组中


- 保存后项目文件夹下会有一下红线圈出的内容

进入对应的chunk文件夹下

- 解压chunk.zip得到doc.xml文件
doc.xml内容如图所示,其中包含了我们预设的group,我们只需要将其他camera也组织成group格式就可以分组成功

- 现将剩余的camera组织为group格式,并输出到txt中,将txt中的结果复制到doc.xml中替换原来的内容
1 | def photoScanNewChunk(): |
注意更改id、label和type的值

- 重新打开项目文件

对齐图片
Workflow→Align Photos
根据需求更改精度之类的东西

如果对齐成功,就会像下面一样只有一个component,而如果对齐失败就会出现多个component这时就需要其他方法去改进优化

对齐图片(全景图片直接处理)
-
除了按上述将投影后的图片进行对齐及后续处理,还可以直接使用全景图片进行操作
-
全景图片数量更少(一张全景图片会投影为若干张普通图片),因此处理速率更快
-
出现多个component的概率更小
-
但可能也会出现其他问题
-
-
由于是全景图片,因此不存在分group的情况,当然也可以将比较靠近的若干图片分为一组
-
注意导入图片后要有一个步骤告诉软件我们正在使用全景图片
- Tools→Camera Calibration

- 其余步骤与之前相似,得到align后的结果
计算机网络——OSI七层模型

一、物理层
物理层所传数据单位为比特。要求发送法发送1/0时,接受方应当准确接受到1/0。因此物理层要考虑用多大的电压代表1或0,以及接受方如何识别出发送法发送的比特。
物理层的作用正是要尽可能的屏蔽掉多种多样的传输媒体和通信手段的差异,使物理层上面的数据链路层感觉不到这些差异。
信道:向某一个方向传送信息的媒体。单工通信、半双工通信、全双工通信
二、数据链路层
两台主机之间的数据传输,总是在一段一段的链路上完成的,这就需要专门的链路层协议。在两个相邻节点之间传输数据时,数据链路层将网络层交下来的IP数据报组装成帧,两个相邻节点在链路中传送帧。
每一帧会包括必要的控制信息:同步信息、地址信息、差错控制。
react项目的配置与搭建
React创建与配置
React创建项目
React脚手架搭建项目
1 | create-react-app name |

删除默认生成的文件:

项目目录结构配置

项目基本配置
配置项目的icon
直接把需要使用的icon替换public下面的favicon.ico
配置项目的标题
设置html文件下的title属性
配置jsconfig.json文件
配置后编写项目的智能提示会好很多

1 | { |
项目的别名配置

采用craco配置
webpack的配置:
- craco会使项目的配置与webpack的配置融合
1 | npm install @craco/craco@alpha -D |
- craco.config.js的配置

1 | const path = require('path') |
要是craco生效,项目启动必须以craco启动,才能和webpack的配置融合——修改package.json中的scripts
1 | "scripts": { |
此时就可以用别名去写路径,避免长串的…/…/

项目的less配置:
1 | npm i craco-less@2.1.0-alpha.0 --save -D |
1 | const path = require('path') |
CSS样式的重置
对默认样式进行重置:
- normalize.css
- reset.css
normalize.css:
1 | npm install normalize.css --save |
在index.js中导入
reset.css:
1 | /* 把需要重置的元素名称放在这里 */ |
React全家桶配置
路由配置
1 | npm install react-router-dom --save |
index.js:
1 | import React, { Suspense } from 'react'; |
router/index.js:
1 |
|
Redux状态管理
- 普通方式:目前使用效率依然非常高
- @reduxjs/toolkit方式:推荐方式,未来的趋势
1 | npm install @reduxjs/toolkit react-redux --save |
普通方式:

- reducer.js代码:
1 | const initialState = { |
- index.js代码:
1 | import reducer from "./reducer"; |
@reduxjs/toolkit方式:

- home.js代码
1 | import { createSlice } from "@reduxjs/toolkit"; |
- index.js代码
1 | import { configureStore } from "@reduxjs/toolkit"; |
网络请求配置
发起网络请求库:
1 | npm install axios --save |

index.js代码:
1 | import owwRequest from "./request" |
request/index.js代码:
1 | import axios from "axios"; |
request/config.js:
1 | export const BASE_URL = "" |
内存管理和闭包
内存管理
内存的管理都会有如下的生命周期:
- 第一步:分配申请你需要的内存(申请)
- 第二步:使用分配的内存(存放一些东西,比如对象等)
- 第三步:不需要使用时,对其进行释放
不同的编程语言对于第一步和第三步会有不同的实现:
- 手动管理内存:比如C、C++,包括早期的OC,都是需要手动来管理内存的申请和释放的(malloc和free函数)
- 自动管理内存:比如Java、JavaScript、Python、Swift、Dart等,它们有自动帮助我们管理内存
对于开发者来说,JavaScript 的内存管理是自动的、无形的:
- 我们创建的原始值、对象、函数……这一切都会占用内存
- 但是我们并不需要手动来对它们进行管理,JavaScript引擎会帮助我们处理好它
JavaScript内存管理
- JS对于原始数据类型内存的分配会在执行时, 直接在栈空间进行分配
- JS对于复杂数据类型内存的分配会在堆内存中 开辟一块空间,并且将这块空间的指针返回值 变量引用

JavaScript的垃圾回收
因为内存的大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,以便腾出更多的内存空间
在手动管理内存的语言中,我们需要通过一些方式自己来释放不再需要的内存,比如free函数:
- 但是这种管理的方式其实非常的低效,影响我们编写逻辑的代码的效率
- 并且这种方式对开发者的要求也很高,并且一不小心就会产生内存泄露
垃圾回收的英文是Garbage Collection,简称GC
常见GC算法——引用计数
引用计数:
-
当一个对象有一个引用指向它时,那么这个对象的引用就+1
-
当一个对象的引用为0时,这个对象就可以被销毁掉
-
循环引用不会被销毁

常见GC算法——标记清除
标记清除:
- 标记清除的核心思路是可达性(Reachability)
- 这个算法是设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些 没有引用到的对象,就认为是不可用的对象
- 这个算法可以很好的解决循环引用的问题
常见GC算法——算法优化
标记整理法
- 不同的是,回收期间同时会将保留的存储对象搬运汇集到连续的内存空间,从而整合空闲空间,避免内存碎片化
分代收集法
- 对象被分成两组:“新的”和“旧的”
- 许多对象出现,完成它们的工作并很快死去,它们可以很快被清理
- 那些长期存活的对象会变得“老旧”,而且被检查的频次也会减少
增量收集
- 如果有许多对象,并且我们试图一次遍历并标记整个对象集,则可能需要一些时间,并在执行过程中带来明显的延迟
- 所以引擎试图将垃圾收集工作分成几部分来做,然后将这几部分会逐一进行处理,这样会有许多微小的延迟而不是一个大的 延迟
闲时收集
- 垃圾收集器只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响

JavaScript闭包
JavaScript函数式编程
- 在JavaScript中,函数是非常重要的,并且是一等公民
- 那么就意味着函数的使用是非常灵活的
- 函数可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用
- avaScript存在很多的高阶函数
- 自己编写高阶函数
- 使用内置的高阶函数
闭包
-
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包
-
闭包让你可以在一个内层函数中访问到其外层函数的作用域
-
在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
-
个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包
-
从广义的角度来说:JavaScript中的函数都是闭包
-
从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,那么它是一个闭包
闭包访问过程:


闭包执行过程:
-
makeAdder函数执行完毕,正常情况下我们的AO对象会被释放
-
但是因为在0xb00的函数中有作用域引用指向了这个AO对象,所以它不会被释放掉

闭包的内存泄露问题:
- 因为在全局作用域下add10变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以最终 会造成这些内存都是无法被释放的
- 所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的
- 因为当将add10设置为null时,就不再对函数对象0xb00有引用,那么对应的AO对象0x200也就不可达了
- 在GC的下一次检测中,它们就会被销毁掉
AO不使用的属性优化:
AO对象不会被销毁时,是否里面的所有属性都不会被释放?
-
下面这段代码中name属于闭包的父作用域里面的变量
-
我们知道形成闭包之后count一定不会被销毁掉,那么name是否会被销毁掉呢


react中的hooks
为什么需要Hook
不编写class的情况下使用state以及其他的React特性
class组件
class组件的优势
- class组件可以定义自己的state,用来保存组件自己内部的状态
- 函数式组件不可以,因为函数每次调用都会产生新的临时变量
- class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑
- 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次
- 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求
- class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等
- 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次
class组件存在的问题
-
复杂组件变得难以理解
- 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂
- 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在
componentWillUnmount中移除) - 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
-
难以理解的class
- 在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this
-
组件复用状态很难
- 状态的复用我们需要通过高阶组件
- 像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用
- 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套
Hook的作用
Hook可以解决上述class和函数组件存在的问题
它可以让我们在不编写class的情况下使用state以及其他的React特性
Hook的使用场景
- Hook的出现基本可以代替我们之前所有使用class组件的地方
- 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它
- Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用
Hooks的使用
Hooks只能在函数组件中使用
为什么Hooks中的函数都叫use,不叫create呢?
- Hooks的一大作用是保存组件的状态,在下一次渲染时,返回当前的state
- 而create的意思是,每次渲染都重新创建,那么就与state只在组件初次渲染时被创建相反
useState的使用
useState主要在函数组件中定义state
1 | import React, { memo, useState } from 'react' |
-
useState传入的参数是state中变量的初始值,不传则为默认值undefined
-
useState返回的是一个数组,其中数组中的第一个值是state变量的值,第二个值是设置状态值的函数
-
只能在最外层调用Hook,不能在循环、条件判断或者子函数中调用
-
只能在React的函数组件中调用Hook,不能在其他JavaScript函数中调用
useState如何实现状态
Effect Hook
- Effect Hook可以在函数组件中实现类似于生命周期的功能
- 网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(不是页面渲染)
- 对于完成这些功能的Hook被称之为 Effect Hook
Effect Hook的基本使用
1 | import React, { memo, useEffect, useState } from 'react' |
-
useEffect传入的参数为一个回调函数,这个回调函数在更新DOM操作之后,就会执行
-
但是在默认情况下,无论第一次渲染,还是在上面更改count更新后的渲染,在渲染结束后都会执行这个回调函数
-
由于每次渲染都会执行useEffect中的函数,那么每次都会添加监听,最后会添加若干监听,因此需要在函数组件卸载或渲染前卸载掉之前的监听
Effect Hook的清除
-
在class组件中,某些副作用的代码,可以在componentWillUnmount中进行清除,比如在这个函数中清除Redux的订阅
-
useEffect通过传入的回调函数1的返回值函数2,在返回值函数2中做清除操作
- 这样做可以将添加和移除订阅的逻辑放在一起
- 同属于一个effect的一部分,更好管理
-
React何时清除useEffect
- 在组件更新和卸载时执行清除操作(装载时不会清除)
多个Effect的使用
-
使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题
- 比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中
-
使用Effect Hook,我们可以将它们分离到不同的useEffect中
- Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样
- React 将按照 effect 声明的顺序依次调用组件中的每一个 effect
1 | import React, { memo, useEffect, useState } from 'react' |
Effect的性能优化
-
默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题
- 某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅)
- 另外,多次执行也会导致一定的性能问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import React, { memo, useEffect, useState } from 'react'
function App() {
const [count, setCounter] = useState(0)
const [message, setMessage] = useState("Hello!")
useEffect(()=>{
console.log("监听redux中的数据变换,监听eventBus中的事件")
return ()=>{
console.log("清除监听redux中的数据变换,清除监听eventBus中的事件")
}
})
return (
<div>
<h2>count:{count}</h2>
<h2>message:{message}</h2>
<button onClick={()=>{console.log("加count");setCounter(count+1)}}>+1</button>
<button onClick={()=>{console.log("修改message");setMessage("你哈个锤子")}}>修改message</button>
</div>
)
}
export default memo(App)
-
useEffect的实际上有两个参数
- 参数一:执行的回调函数
- 参数二:该useEffect在哪些state发生变化时,才重新执行;(受谁的影响)——如果不希望受到任何依赖的影响,则传入[],这时候仅会在创建和销毁时分别执行对应回调
1 | import React, { memo, useEffect, useState } from 'react' |

那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了
useContext的使用
获取Context的方式:
- 类组件可以通过 类名.contextType = MyContext方式,在类中获取context
- 多个Context或者在函数式组件中通过 MyContext.Consumer 方式共享context
但是多个Context共享时会存在大量的嵌套——Context Hook允许我们通过Hook来直接获取某个Context的值
但是Provider依然会有嵌套
Provider代码:
1 | import React from 'react'; |
useContext代码:
1 | import React, { memo, useContext, useEffect, useState } from 'react' |

useReducer的使用
useReducer仅仅是useState的一种替代方案——与Redux关系不大
- 如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分
- 或者这次修改的state需要依赖之前的state时,也可以使用
- 数据是不会共享的,它们只是使用了相同的counterReducer的函数而已
- useReducer只是useState的一种替代品,并不能替代Redux
1 | import React, { memo, useReducer } from 'react' |
useReducer的参数与返回值:
- 第一个参数:reducer函数
- reducer函数的参数一:上一次的state/useReducer的第二个参数
- reducer函数的参数二:dispatch所传入的参数
- reducer的返回值:新的状态(…state是为了保障除了这次改变的其余状态依然在新的状态中)
- 第二个参数:state初始值
- 第一个返回值:更新后的state值
- 第二个返回值:用于发起改变state的函数
useCallback的使用
useCallback实际的目的是为了进行性能的优化
- 函数式组件在每次刷新时都会重新执行函数,那么里面的函数就会重新定义,虽然刷新后,原来定义的函数会被回收,但每次都定义也会影响性能,同时会生成新的foo
- useCallback就可以实现当满足某些条件时重新定义函数,但得到相同的foo,而如果有子组件依赖这个foo,相同的foo不会引起子组件的刷新
父组件代码:
1 | import React, { memo, useCallback, useState } from 'react' |
子组件代码:
1 | import React, { memo } from 'react' |

可以看出每次修改count都会刷新不依赖count子组件,会造成性能浪费
useCallback的基本使用
1 | import React, { memo, useCallback, useState } from 'react' |

可以看出点击更改count后,子组件并未刷新
useCallback的参数与返回值:
- 参数一:需要处理的函数
- 参数二:该函数改变依赖的状态,如果该依赖的状态不变,就返回相同的函数,相同的函数不会引起依赖该函数子组件的刷新
- 返回值:返回一个函数的 memoized(记忆的) 值
使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存
- 并不是不会新定义函数,新定义函数是一定的
- 只针对子组件的渲染次数做优化,在本组件层面是没有优化的
useMemo的使用
useMemo也是为了进行性能的优化:
- 同样是返回一个memoized(记忆的)值
- 在依赖不变的情况下,多次定义的时候,返回的值是相同的
- useCallback是在依赖不变的情况下,多次定义的时候,返回的函数是相同的
- useMemo针对变量进行优化,useCallback针对函数进行优化
useMemo的使用案例:
- 进行大量的计算操作,是否有必须要每次渲染时都重新计算
- 对子组件传递相同内容的对象时,使用useMemo进行性能的优化
父组件代码:
1 | import React, { memo, useCallback, useState } from 'react' |
子组件代码:
1 | import React, { memo } from 'react' |

可以看出:
- 每次更新count后,message是不变的,那么information,也就不必要每次都去运行foo函数,当foo函数逻辑赋值时会引起性能浪费
- 子页面仅依赖于message,这里明显每次都是得到的information值是一样的,但由于是不同的对象,子组件每次也会刷新
useMemo的基本使用
1 | import React, { memo, useMemo, useState } from 'react' |

可以看出:
修改count后,依赖于message的函数不再重新执行,子组件也不再刷新
注意:
- useMemo这种Hook函数,只能在函数组件和hook函数中使用,自定义hook函数,以use开头,use后的第一个字母为大写
- useMemo不会传递参数,因为useMemo是由React调用的,我们不知道其在上面时候调用,也就无法传参数进行
- useMemo的参数与返回值
- 参数一:useMemo判断是否执行的函数
- 参数二:useMemo依赖的值,如果这个值没变,就不会执行传入的函数,直接返回上一次的计算值
- 返回值:返回一个带记忆的值
useRef的使用
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变
useRef基本使用
- 用法一:引入DOM(或者组件,但是需要是class组件)元素
1 | import React, { memo, useRef} from 'react' |
- 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变(解决闭包陷阱)
- 由于函数里的count是获取上层作用域里的,也就是闭包,那么无论页面刷新多少次,函数里的count都是函数定义是的count 0,因此无论多少次点击得到的结果都是1(当然这里可以设置依赖count——仅颜色useRef的作用,所以没有添加)
- 而使用inputRef所得到的对象每次本身就是一样的,那么有没有闭包陷阱也就无所谓,因为我们本身就是要获取相同的值,而通过修改inptRef这个对象里面的值,从而获得相同的对象不同的值,解决闭包陷阱
1 | import React, { memo, useCallback, useRef, useState} from 'react' |

父组件传ref给子组件
- 方式一:作为参数放到props中传递过去——参数名不能使用ref
- 方式二:利用forwardRef传递给子组件
父组件代码:
1 | import React, { memo, useRef, useState} from 'react' |
子组件代码:
1 | import React, { forwardRef, memo } from 'react' |
注:要使用ref参数,子组件需用forwardRef包裹,且要使用memo的话,memo在forwardRef外层
useImperativeHandle的使用
- 上述通过父组件中的ref传递给子组件中的元素,可以在父组件中获得子组件元素,对子组件元素进行操作
- 将整个元素给到父组件,就容易在父组件中过渡使用,从而在某种情况下产生某种bug,如上述代码,点击showref会在聚焦子组件input的同时,将其值修改为空
- useImperativeHandle就是为了解决样的问题,不直接把子组件元素给到父组件,而是把子组件元素的某些操作给到父组件
1 | import React, { forwardRef, memo, useImperativeHandle, useRef } from 'react' |
useImperativeHandle的基本使用
- 参数一:父组件传给子组件的ref
- 参数二:回调函数,函数返回结果是传递给父组件的属性值对象,如这里只传了focus函数,那么父组件中就只有focus属性生效,其余属性无效
useLayoutEffect使用
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新

useEffect与useLayoutEffect执行顺序演示
1 | import React, { memo, useEffect, useLayoutEffect} from 'react' |

注:先"render app",等数据更新后,渲染到DOM前执行useLayoutEffect,渲染结束后执行useEffect
useLayoutEffect的作用
在页面即将刷新错误数据前及时拦截
使用useEffect代码:
1 | import React, { memo, useEffect, useState} from 'react' |

使用useLayoutEffect代码:
1 | import React, { memo, useLayoutEffect, useState} from 'react' |

自定义Hook
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性
注:
- 自定义Hook以use开头,且use后的字母要大写
自定义Hook——所有的组件在创建和销毁时都进行打印
- 组件被创建:打印 组件被创建了
- 组件被销毁:打印 组件被销毁了


自定义Hook——Context的共享

自定义Hook——获取滚动位置

redux hooks的使用
- 在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux中的connect
- 但是这种方式必须使用高阶函数结合返回的高阶组件
- 且必须编写:mapStateToProps和 mapDispatchToProps映射的函数
- useSelector的作用是将state映射到组件中
- 参数一:将state映射到需要的数据中
- 参数二:可以进行比较来决定是否组件重新渲染;(后续讲解)
- useSelector默认会比较我们返回的两个对象是否相等
- 也就是我们必须返回两个完全相等的对象才可以不引起重新渲染
- useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可
- 我们还可以通过useStore来获取当前的store对象
redux的常规使用——使用connect函数
整个项目index.js代码:
1 | import React from 'react'; |
countSlice.js代码
1 | import { createSlice } from "@reduxjs/toolkit"; |
导出的stero的index.js代码:
1 | import { configureStore } from "@reduxjs/toolkit"; |
stero使用的jsx代码:
1 | import React, { memo, useLayoutEffect, useState} from 'react' |
注:
- 这种方式必须使用高阶函数结合返回的高阶组件
- 并且必须编写:mapStateToProps和 mapDispatchToProps映射的函数
redux的Hook使用
useSelector与useDispatch的使用
App.jsx代码:
1 | import React, { memo} from 'react' |
注:
可以看到能够直接使用useSelector对stero中的数据进行映射,直接通过useDispatch获取dispatch
但是上述会出现一些问题:
- useSelector是监听的是state,也就是state改变后,所有使用useSelector的组件都会刷新
ChildComponent代码:
1 | import React, { memo } from 'react' |
子组件只与state中的message有关,但当count改变时子组件依然会刷新,会引起性能的降低

给useSelector设置shallowEqual就可以避免上述问题,实际上是进行一个浅层比较,如果相同就不更新
1 | const {message} = useSelector((state)=>({ |

react中的路由设置
中间件的实现
React-Router路由
URL的hash:
- URL的hash也就是锚点(#), 本质上是改变window.location的href属性
- 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
React Router的安装
1 | npm install react-router-dom |
基本使用
BrowserRouter或HashRouter:
- Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件
- BrowserRouter使用history模式
- HashRouter使用hash模式
路由的配置与使用
要使用的组件前面添加路由设置
1 | import React from 'react'; |
路由的映射
映射设置:
1 | import React, { PureComponent } from 'react' |
注:
- 如果使用HashRouter,浏览器的路径设置一定要加/#/,否则是失效的


路由的配置跳转
Link元素
1 | import React, { PureComponent } from 'react' |

注:
- Link元素会自动渲染为a元素
- 如果要更改这个a元素的样式,需要使用NavLink组件
NavLink元素
1 | import React, { PureComponent } from 'react' |
index.css代码:
1 | .active{ |

注:
- NavLink元素会自动给当前渲染的a元素添加active类,所以可以通过active去设置当前渲染的样式
- 而如果active类在其他地方已经使用过了呢,使用active就容易引起冲突,react-router提供了className属性去自定义我们的类名称,属性应该传入函数
- 同样react-router为我们提供了style属性去定义样式,属性应该传入函数
使用style属性:
1 | import React, { PureComponent } from 'react' |

使用className属性:
1 | import React, { PureComponent } from 'react' |

Navigate导航
- 如当页面location为/时,自动跳转到/home/页面
- 这种情况与上面直接element的直接区别为:这种情况下不会出现"/"的情况,因为会直接跳转,而上面是替换匹配到的组件
- 导航的所有东西都是放在#后面的,#前面在解析时会忽略


1 | import React, { PureComponent } from 'react' |
Not Found页面配置
- 当所有的Route都未匹配成功时,当前组件内容为空,而有时想给用户提醒输入了错误的url,则需要一个可以匹配所有字符的默认Route

-
react-route中的通配符为’*‘,因此将一个path=’*'放在最后,那么如果前面的path都没匹配上,最后一个肯定可以匹配,并找到默认页面
-
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
29import React, { PureComponent } from 'react'
import { Routes, Route, NavLink, Navigate } from 'react-router-dom'
import Home from './components/Home'
import MyMusic from './components/MyMusic'
import NotFound from './components/NotFound'
import "./index.css"
export default class App extends PureComponent {
render() {
return (
<div>
{/* 主要是通过路由的配置来更改当前页面的location,从而使下面的Routes匹配结果改变 */}
<div className='header'>
<NavLink to="/" className={({isActive})=>{ return isActive?"nav-active one":"one"}}>发现音乐</NavLink>
<br />
<NavLink to="/my" className={({isActive})=>{ return isActive?"nav-active two":"two"}}>我的音乐</NavLink>
</div>
<div className="content">
{/* 这里会con上往下匹配,直至匹配到一个结果,后续就不匹配了*/}
<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home" element={<Home/>}/>
<Route path="/my" element={<MyMusic/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
</div>
</div>
)
}
}
-
路由的嵌套
在实际开发中,经常出现路由之间存在嵌套关系——如网易云中的路由

1 | import React, { PureComponent } from 'react' |
注:
-
通过上述写法可以实现嵌套,在Home组件中编写下一层的路由跳转逻辑
- Home.jsx的代码——Outlet是NavLink元素得到组件的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import React, { PureComponent } from 'react'
import { NavLink, Outlet } from 'react-router-dom'
import "../index.css"
export default class Home extends PureComponent {
render() {
return (
<div className='home-container'>
<div>home</div>
<div className='home-content'>
<NavLink to="/home/discover">推荐</NavLink>
<NavLink to="/home/toplist">排行榜</NavLink>
</div>
{/* <Outlet/>是NavLink元素得到组件的位置 */}
<Outlet/>
</div>
)
}
}
-
第二层路由最终会变成第一层路由的子组件,而如果我们在两个’/'路由的时候,想让他在第一层,而不出现在第二层呢——可以在第一层时,不写element
1
2
3
4
5
6
7
8
9
10<Routes>
<Route path='/' element={<Navigate to="/home"/>}/>
<Route path="/home">
<Route path='/home' element={<Navigate to="/home/discover"/>}/>
<Route path='/home/discover' element={<Recommend/>}/>
<Route path='/home/toplist' element={<TopList/>}/>
</Route>
<Route path="/my" element={<MyMusic/>}/>
<Route path='*' element={<NotFound/>}/>
</Routes>
手动实现路由的跳转
- 上面的跳转实现主要靠NavLink自动生成的a元素,但有时候我们想要使用其他组件跳转,或者在逻辑过程中跳转,这种方法就不太好实现
- hooks提供了useNavigate函数,返回一个navigate对象,可以通过这个对象在代码中进行跳转
- 但是hooks只能在函数组件中使用,类组件不能直接使用useNavigate
- 对于对状态要求不高的类组件,可以更改为函数组件使用useNavigate
- 对于有很多状态的类组件,可以使用高阶组件,在其外包裹一个函数组件,通过参数的形式,将navigate对象传递给类组件使用
高阶函数代码:
1 | import { useNavigate } from "react-router-dom" |
要被包裹的组件代码:
1 | import React, { PureComponent } from 'react' |
路由参数的传递
- 动态路由的方式
- search传递参数
动态路由
- 比如/detail的path对应一个组件Detail
- 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示
- 这个匹配规则,我们就称之为动态路由
- 通常情况下,使用动态路由可以为路由传递参数
App.jsx代码——配置Route:
1 | import React, { PureComponent } from 'react' |
hooksFunction代码:——获取参数使用useParams的hook,依然不能直接在类组件中使用,因此与navigate一样,使用高阶组件包含
1 | import { useNavigate, useParams } from "react-router-dom" |
MyMusic.jsx代码:——获取参数的组件
1 | import React, { PureComponent } from 'react' |

search传递参数
search传递参数直接在跳转路由时设置(Link/navigate)——如navigate(“/friend?name=oww&age=22”)
App.jsx代码:
1 | import React, { PureComponent } from 'react' |
***hooksFunction代码:***要获取传递的参数同样需要hooks,则同样需要用高阶函数包裹
-
useLocation:会获取到当前页面url的一些信息
-
useSearchParams:获取到当前页面的参数相关信息
1 | import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom" |

路由的配置文件
- 之前直接用Route组件定义路由,如果项目中路由很多的情况下,页面会相当混乱
- 可以通过useRoutes的hook配置到一个地方集中管理
- 早期,Route没有相关的api,需要借助于react-router-config
- Router6.x中,为我们提供了useRoutes API完成相关的配置

React与Redux
Redux
为什么要使用Redux
- JavaScript开发的应用程序,已经变得越来越复杂了
- JavaScript需要管理的状态越来越多,越来越复杂
- 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示 加载动效,当前分页
- 管理不断变化的state是非常困难的
- 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化
- 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪
- React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理
- 无论是组件定义自己的state,还是组件之间的通信通过props进行传递;也包括通过Context进行数据之间的共享
- React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定
- React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定
- Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)
Redux三大原则
- 单一数据源
- 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中
- Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护
- 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
- State是只读的
- 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State
- 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state
- 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题
- 使用纯函数来执行修改、
- 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State
- 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
- 但是所有的reducer都应该是纯函数,不能产生任何的副作用
Redux项目搭建与使用
-
安装redux
1
npm install redux --save
-
创建一个对象,作为我们要保存的状态
-
创建Store来存储这个state
-
创建store时必须创建reducer
-
我们可以通过 store.getState 来获取当前的state
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
28const { createStore } = require("redux")
// 初始化数据——要保存的状态
const initialState = {
name:"ouwenwu",
age:22
}
// 定义reducer函数:纯函数
/**
* 参数1:store中目前报错的state——在createStore时会调用reducer一次,此时参数state为undefined,因此需要为其定义一个初始值,即我们的初始化数据
* 参数2:传入的action
* 返回值:返回值会作为store之后存储的state
*/
function reducer(state=initialState,action){
console.log("reducer",state,action)
// 这里使用switch更好
switch(action.type){
case "change_name":
return {...state,name:action.name}
case "add_age":
return {...state,age:state.age+action.age}
default:
return state
}
}
//创建store
const store = createStore(reducer)
module.exports = store -
-
通过action来修改state
- 通过dispatch来派发action
- 通常action中都会有type属性,也可以携带其他的数据
-
修改reducer中的处理代码
- 这里一定要记住,reducer是一个纯函数,不需要直接修改state
- 后面我会讲到直接修改state带来的问题
-
可以在派发action之前,监听store的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25const store=require("./store");
// 开启store订阅——这个订阅是在派发action之前订阅的,会在store所监听的state修改时调用
const unsubscribe = store.subscribe(()=>{
console.log("订阅数据发生变化",store.getState())
})
// actionFunction——将action抽取为函数,那么后面每次需要创建action不用手动编写,调用函数生成
const changeNameAction = (name)=>({
type:"change_name",
name
})
const addAgeAction = (age)=>({
type:"add_age",
age
})
// 修改store中的数据:必须action——手动写action
const nameAction ={type:"change_name",name:"kobe"}
store.dispatch(nameAction)
store.dispatch(changeNameAction("kebi1"))
// 取消订阅
unsubscribe()
store.dispatch({type:"add_age",age:1})
store.dispatch(addAgeAction(1))输出

Redux项目抽取
如果我们将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护
-
store/index.js文件——利用reducer创建store
1
2
3
4import {createStore} from "redux"
import reducer from "./reducer"
//创建store
export const store = createStore(reducer) -
store/reducer.js文件——reducer函数和state默认值\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import * as actionType from "./constants.js"
const initialState = {
name:"ouwenwu",
age:22
}
// 定义reducer函数:纯函数
/**
* 参数1:store中目前报错的state
* 参数2:传入的action
* 返回值:返回值会作为store之后存储的state
*/
function reducer(state=initialState,action){
console.log("reducer",state,action)
// 这里使用switch更好
switch(action.type){
case actionType.CHANGE_NAME:
return {...state,name:action.name}
case actionType.ADD_AGE:
return {...state,age:state.age+action.age}
default:
return state
}
}
export default reducer -
store/actionCreator.js文件——将store要派发的action,抽取成函数放在此处
1
2
3
4
5
6
7
8
9
10import * as actionType from "./constants.js"
export const changeNameAction = (name)=>({
type:actionType.CHANGE_NAME,
name
})
export const addNameAge = (age)=>({
type:actionType.ADD_AGE,
age
}) -
store/constants.js文件——常量文件,将action中的type抽取出来
1
2export const CHANGE_NAME = "change_name"
export const ADD_AGE = "add_age"
Redux与React融合

redux配置代码如上所述
jsx代码:
1 | import React from "react"; |
react-redux的使用
-
index.js(整个react的入口):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App"
import { Provider } from "react-redux";
import store from "./store";
// 编写REACT代码,通过REACT渲染内容
/**
* 这里的Provider实际上是基于Context实现的,用它包裹App及其子组件可以使用传入的stero
* stero在底层是通过value实现的
*/
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(
<Provider store={store}>
<App/>
</Provider>
) -
App.js代码(获取stero并使用代码):
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
44import React from "react";
import { connect } from "react-redux";
import { addAgeAction } from "./store/actionCreator";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
addAgeByButton(){
// store.dispatch(addAgeAction(1))
this.props.addAge(1)
}
render(){
return (
<div>
<button onClick={()=>{this.addAgeByButton()}}>age+1</button>
<div>
<h3>{this.props.age}</h3>
</div>
</div>
)
}
}
// 相当于添加监听,会把stero中的state中的对应属性添加到props中
function mapStateToProps(state){
// 需要用哪些就使用哪些,会将这个返回对象和本来的props合并
return{
age:state.age
}
}
// 设置dispatch,会把return中的函数放到this.pros中去,通过调用这个函数可以发送action给stero
const mapDispatchToProps = (dispatch)=>{
return{
addAge(age){
dispatch(addAgeAction(age))
}
}
}
// connect()返回值是一个高阶函数
/**
* connect()参数1:store中的哪些数据需要映射到这个组件的props——函数
* connect()参数2:
*/
export default connect(mapStateToProps,mapDispatchToProps)(App) -
stero的代码,index.js/constants.js/actionCreate.js/reducer.js依然像上方写的一样
redux异步处理
react获取网络请求:
-
componentDidMount中发起网络请求,并在结束后赋值给state
- 这种方式数据管理和react耦合性高,和redux的要求不符
-
通过redux获取网络请求,由jsx组件中发起action,在action中获取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* 由于网络请求通常是异步的,函数的return中不能直接获得网络请求的值,因此普通的action是不能实现异步的
*/
export const netWorkAction = ()=>{
new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("oww")
},5000)
}).then(res=>{
return{
type:actionType.CHANGE_NAME,
name:res
}
})
return {}
}redux-thunk中间件技术
-
安装redux-thunk
1
npm install react-thunk --save
-
设置react可以使用thunk,thunk允许dispatch一个函数,之后会自动执行这个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import { applyMiddleware, combineReducers, createStore } from "redux";
import countReducer from "./countStore/reducer";
import homeReducer from "./homeStore/reducer";
import thunk from "redux-thunk";
const reducer = combineReducers({
age:countReducer,
name:homeReducer
})
// combineReducers的底层实现
function reducerByUs(state = {}, action){
return {
/**
* 第一次执行时(createStore),传入undefined,得到的是默认值对象
* 后续执行时,每次传入上次的state,得到正确值
*/
age:countReducer(state.age, action),
name:homeReducer(state.name,action)
}
}
// 包裹中间件
export const store = createStore(reducer,applyMiddleware(thunk))
export default storeaction代码
1
2
3
4
5
6
7
8
9
10
11
12
13export const netWorkAction = ()=>{
console.log("dispatch foo")
const foo = (dispatch, getState)=>{
new Promise((resolve) => {
setTimeout(()=>{
resolve("oww")
},5000)
}).then(res=>{
dispatch(changeNameAction(res))
})
}
return foo
}jsx中发起dispatch代码
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
46
47
48
49
50
51
52
53import React from "react";
import { connect } from "react-redux";
import { addAgeAction } from "./store/countStore";
import { netWorkAction } from "./store/homeStore";
//编写组件
class App extends React.PureComponent{
constructor(){
super()
}
addAgeByButton(){
// store.dispatch(addAgeAction(1))
this.props.addAge(1)
this.props.changeName()
}
render(){
return (
<div>
<button onClick={()=>{this.addAgeByButton()}}>changeinfo</button>
<div>
<h2>{this.props.name}</h2>
<h3>{this.props.age}</h3>
</div>
</div>
)
}
}
// 相当于添加监听,会把stero中的state中的对应属性添加到props中
function mapStateToProps(state){
// 需要用哪些就使用哪些,会将这个返回对象和本来的props合并
return{
age:state.age.age,
name:state.name.name
}
}
// 设置dispatch,会把return中的函数放到this.pros中去,通过调用这个函数可以发送action给stero
const mapDispatchToProps = (dispatch)=>{
return{
addAge(age){
dispatch(addAgeAction(age))
},
changeName(){
dispatch(netWorkAction())
}
}
}
// connect()返回值是一个高阶函数
/**
* connect()参数1:store中的哪些数据需要映射到这个组件的props——函数
* connect()参数2:
*/
export default connect(mapStateToProps,mapDispatchToProps)(App)
-
redux代码拆分
不拆分的reducer:
- 一个reducer处理多个页面的数据
- 将所有状态都放到reducer中进行管理,随着项目的日趋庞大,会造成代码臃肿
对reducer进行拆分:
- 将对不同页面的数据或造成抽取为不同的reducer
- 将多个reducer合并为一个
combineReducers函数
事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并
页面一store代码:
-
actionCreator.js
1
2
3
4
5
6import * as actionType from "./constants.js"
export const changeNameAction = (name)=>({
type:actionType.CHANGE_NAME,
name
}) -
constants.js
1
export const CHANGE_NAME = "change_name"
-
index.js
1
2
3
4
5
6import homeReducer from "./reducer"
// 统一当初当前模块的数据
export default homeReducer
export * from "./actionCreator"
export * from "./constants" -
reducer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import * as actionType from "./constants.js"
const initialState = {
name:"ouwenwu",
age:22
}
// 定义reducer函数:纯函数
/**
* 参数1:store中目前报错的state
* 参数2:传入的action
* 返回值:返回值会作为store之后存储的state
*/
function homeReducer(state=initialState,action){
console.log("reducer",state,action)
// 这里使用switch更好
switch(action.type){
case actionType.CHANGE_NAME:
return {...state,name:action.name}
default:
return state
}
}
export default homeReducer
页面一store代码:
-
actionCreator.js
1
2
3
4
5
6import * as actionType from "./constants.js"
export const changeNameAction = (name)=>({
type:actionType.CHANGE_NAME,
name
}) -
constants.js
1
export const CHANGE_NAME = "change_name"
-
index.js
1
2
3
4
5
6import homeReducer from "./reducer"
// 统一当初当前模块的数据
export default homeReducer
export * from "./actionCreator"
export * from "./constants" -
reducer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import * as actionType from "./constants.js"
const initialState = {
name:"ouwenwu",
age:22
}
// 定义reducer函数:纯函数
/**
* 参数1:store中目前报错的state
* 参数2:传入的action
* 返回值:返回值会作为store之后存储的state
*/
function homeReducer(state=initialState,action){
console.log("reducer",state,action)
// 这里使用switch更好
switch(action.type){
case actionType.CHANGE_NAME:
return {...state,name:action.name}
default:
return state
}
}
export default homeReducer
页面二store代码:
-
actionCreator.js
1
2
3
4
5
6import * as actionType from "./constants.js"
export const addAgeAction = (age)=>({
type:actionType.ADD_AGE,
age
}) -
constants.js
1
export const ADD_AGE = "add_age"
-
index.js
1
2
3
4
5import countReducer from "./reducer"
export default countReducer
export * from "./actionCreator"
export * from "./constants" -
reducer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import * as actionType from "./constants.js"
const initialState = {
age:22
}
// 定义reducer函数:纯函数
/**
* 参数1:store中目前报错的state
* 参数2:传入的action
* 返回值:返回值会作为store之后存储的state
*/
function countReducer(state=initialState,action){
console.log("reducer",state,action)
// 这里使用switch更好
switch(action.type){
case actionType.ADD_AGE:
console.log(state.age,action.age)
return {...state,age:state.age+action.age}
default:
return state
}
}
export default countReducer
合并两个store的代码:
index.js:
1 | import { combineReducers, createStore } from "redux"; |
合并数据的使用:
1 | import React from "react"; |
ReduxToolkit的使用(RTK)
-
安装
1
npm install @reduxjs/toolkit react-redux
-
Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题
Redux Toolkit的核心API主要是如下几个:
-
**configureStore:**包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供 的任何 Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension
-
**createSlice:**接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions
-
createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分 派动作类型的 thunk
createSlice代码
1 | import { createSlice } from "@reduxjs/toolkit"; |
configureStore代码
1 | import { configureStore } from "@reduxjs/toolkit" |
jsx代码
1 | import React from "react"; |
React中的CSS
React中的过渡动画
- 动画插件:react-transition-group
- npm install react-transition-group --save
Transition
与框架无关,常见的CSS动画使用方法:
1 | .start{ |
CSSTransition
CSSTransition具有三种状态
- 开始状态,对应的类是-appear、-enter、-exit
- 执行动画,对应的类是-appera-active、-enter-active、-exit-active
- 执行结束,对应的类是-appera-done、-enter-done、-exit-done
CSSTransition常见属性:
-
**in:**触发进入或退出状态
- 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉
- 当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class, 并且添加-enter-done的class
- 当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并 且添加-enter-done的class
-
**classNames:**动画class的名称
- 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done
-
**timeout:**动画过渡的时间,指的是动画执行后添加-enter-done的时间,动画真实执行时间由transition决定
-
**appear:**是否在初次进入添加动画(appear和in都要为true、同时要设置-appear类)
-
CSSTransition的钩子函数:
- onEnter:在进入动画之前触发
- OnEntering:在进入动画时触发
- OnEntered:在应用进入动画结束时触发
- …
css代码:
1 | /*设置初始状态,不发生动画的状态,可以用appear的三个类替代*/ |
jsx代码:
1 | import React from "react"; |
SwitchTransition
-
***控件两种状态之间的切换:***on和off指定两种状态
-
mode属性:
-
in-out:新组件先进入,旧组件再移除
-
out-in:旧组件先移除,新组件再进入
-
-
SwitchTransition组件里面要有CSSTransition或者Transition组件,不能直接包裹要切换的组件
-
SwitchTransition里面的CSSTransition或Tasnsition组件不再像之前那样接受属性来判断元素状态,取而代之的是key属性
css代码:
1 | h2{ |
- 起始状态可以没有,那么刚开始就是默认状态
- out-in模式:先制性exit代码,再执行enter代码,没有h2show-enter-done,动画执行结束会回归初始状态,可以没有h2show-exit-done
- in-out模式:先执行enter代码,再执行exit代码,没有h2show-exit-done,动画结束会回归初始状态,可以没有h2show-enter-done
jsx代码:
1 | import React from "react"; |
TransitionGroup:
- 有一组动画时,可以将这些CSSTransition放入到TransitionGroup中
有些搞不懂,官网看教程吧
React中CSS的编写
CSS组件化!!!:
- **可以编写局部css:**css具备自己的局部作用域,不会污染其他组件的元素
- **可以编写动态css:**可以获取当前组件的一些状态,根据状态生成不同的css样式
- **支持所有的css特性:**伪类、动画、媒体查询等
- 编写起来简单方便,符合一贯的css风格特点
内联样式
- style接受小驼峰命名属性的JavaScript对象,而不是字符串
- 可以接受state中的状态来设置相关的格式
内联样式优点:
- 样式之间不会有冲突
- 可以动态获取当前state中的状态
内联样式缺点:
- 都需要驼峰标识
- 某些样式没有提示
- 大量样式,代码混乱
- 某些样式无法编写,比如伪类/伪元素
普通CSS
-
将CSS编写到一个单独的文件,之后进行引入
-
和普通网页开发中的编写方式一致
-
按照普通的网页标准去写,那么不会有太大的问题
-
但是组件化开发中我们总是希望组件是一个独立的模块,样式只在自己内部生效,不会相互影响
-
但是普通的CSS都属于全局的CSS,样式组件会相互影响
-
-
这种编写方式的最大问题是样式之间会相互层叠掉
CSS modules
- 将.css文件修改为.module.css看就可以引用并且局部使用
- 但是类名不能使用(.home-title),因为JavaScript不识别-
- 所有的className都必须使用{style.className}的形式来编写
- 不方便动态的修改某些样式,依然需要使用内联样式的方式
CSS in Js
CSS in Js是一种模式,其中CSS有javaScript生成而不是在外部文件中定义
- 传统的前端开发中,HTML、CSS、JavaScript通常分类
- React认为逻辑和UI是无法分离的,所以才会有了JSX的语法
- JSX是Html in Js,加上CSS in Js在完整的体现的逻辑和UI无法分离,因此React被称为All in JS
CSS in JS库:styled-components:
- CSS-in-JS使用JavaScript为CSS赋予一些能力,可以类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改
- CSS预处理器也具备某些能力,但获取动态状态依然是一个不好处理的点
ES6标签模板字符串

即函数可以通过字符串的方式来调用
- 第一个参数是数组,是被模块字符串拆分后的组合
- 后面的元素是一个个模块字符串传入的内容
- 注意是反引号
styled-components的使用
基本使用
jsx代码:
1 | import React from "react"; |
style.js代码:
1 | import styled from "styled-components"; |
- 样式js的命名不能和组件名称相同,例如不能用App.js
- 可以像正常CSS一样编写CSS代码
- 可以用&来获取当前元素
- 可以通过设置styled.__来设置包裹的类型
- styled返回的是一个组件,可以按照组件的方法使用
props\attrs属性
- 获取props需要通过${}传入一个插值函数,props会作为该函数的参数;
- 这种方式可以有效的解决动态样式的问题
CSS代码:
1 | import styled from "styled-components"; |
jsx代码:
1 | import React from "react"; |
注:
- tcolor不能与color同名,要不然会循环拿取
- tcolor传入的是一个函数,括号可以省略,会自动调用生成值
- tcolor:props=>(props.color||“blue”)是为了处理不传值时给定默认值,没有默认值时可以在下面直接取
styled的继承
CSS代码:
1 | import styled from "styled-components"; |
jsx代码:
1 | import React from "react"; |

