一、React-router
(一)路由的由来
路由是一个网络工程里面的术语。路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动。
路由器提供了两种机制:路由(路由是决定数据包从来源到目的地的路径)和转送(转送将输入端的数据转移到合适的输出端)
路由中有一个非常重要的概念叫路由表,路由表本质上就是一个映射表,决定了数据包的指向。
路由的概念出现最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:
1️⃣ 后端路由阶段
早期的网站开发整个HTML页面是由服务器来渲染的,服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示。并且,一个页面有自己对应的网址,也就是URL。URL会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller进行处理。Controller进行各种处理,最终生成HTML或者数据,返回给前端,这就完成了一个IO操作。
上面的这种操作,就是后端路由。当我们页面中需要请求不同的路径内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户顿。这种情况下渲染好的页面,不需要单独加载任何的js和css,可以直接交给浏览器展示,这样也有利于SEO的优化。
后端路由的缺点:
一种情况是整个页面的模块由后端人员来编写和维护的。另一种情况是前端开发人员如果要开发页面,需要通过PHP和Java等语言来编写页面代码,而且通常情况下HTML代码和数据以及对应的逻辑会混在一起,编写和维护都是非常糟糕的事情。
2️⃣ 前后端分离阶段
前端渲染的理解:
每次请求涉及到的静态资源都会从静态资源服务器获取,这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染。需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件。同时可以看到,和之前的后端路由不同,这时后端只是负责提供API了。
前后端分离阶段:
随着Ajax的出现, 有了前后端分离的开发模式。后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中。这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上,并且当移动端(iOS/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可;目前很多的网站依然采用这种模式开发(jQuery开发模式);
3️⃣ 单页面富应用(SPA)
单页面富应用的理解:
单页面富应用的英文是single-page application,简称SPA。整个Web应用只有实际上只有一个页面,当URL发生改变时,并不会从服务器请求新的静态资源,而是通过JavaScript监听URL的改变,并且根据URL的不同去渲染新的页面。
如何可以应用URL和渲染的页面呢 👉 前端路由
前端路由维护着URL和渲染页面的映射关系,路由可以根据不同的URL,最终让我们的框架(比如Vue、React、Angular)去渲染不同的组件,最终我们在页面上看到的实际就是渲染的一个个组件页面。
(二)前端路由底层原理
前端路由是如何做到URL和内容进行映射呢?监听URL的改变。URL发生变化,同时不引起页面的刷新有两个办法:1️⃣ 通过URL的hash改变URL;2️⃣ 通过HTML5中的history模式修改URL;
当监听到URL发生变化时,我们可以通过自己判断当前的URL,决定到底渲染什么样的内容。
1️⃣ URL的hash
URL的hash也就是锚点(#),本质上是改变
window.location
的 href
属性;我们可以通过直接赋值 location.hash
来改变 href,但是页面不发生刷新。⚠️ 注意:hash的优势就是兼容性更好,在老版IE中都可以运行;但是缺陷是有一个
#
,显得不像一个真实的路径;2️⃣ HTML5的history
history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:
replaceState
:替换原来的路径;
pushState
:使用新的路径;
popState
:路径的回退;
go
:向前或向后改变路径;
forward
:向前改变路径;
back
:向后改变路径;
以下是案例代码:
(三)React-router 引入
目前前端流行的三大框架, 都有自己的路由实现:
- Angular的ngRouter
- React的react-router
- Vue的vue-router
React Router的版本4开始,路由不再集中在一个包中进行管理了:
- react-router是router的核心部分代码;
- react-router-dom是用于浏览器的;
- react-router-native是用于原生应用的;
安装react-router:
yarn add react-router-dom
,安装react-router-dom会自动帮助我们安装react-router的依赖(四)主要API × 基本使用
react-router最主要的API是给我们提供的一些组件:
<BrowserRouter>
或<HashRouter>
Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件。
<BrowserRouter>
使用history模式;<HashRouter>
使用hash模式;<Link>
和<NavLink>
通常路径的跳转是使用Link组件,最终会被渲染成a元素;NavLink是在Link基础之上增加了一些样式属性(后续学习);
to
属性:Link中最重要的属性,用于设置跳转到的路径;<Route>
<Route> 用于路径的匹配;
path
属性:用于设置匹配到的路径;component
属性:设置匹配到路径后,渲染的组件;exact
:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;以下是案例代码,首先需要创建页面管理文件夹pages,在其中创建几个组件Home、About、Profile,然后在App.js中加入以下代码。而且我们需要知道路由渲染的位置与
<Route>
组件放置的位置有关(占位)。(五)<NavLink>
需求:路径选中时,对应的
<a>
元素变为红色。原生方法实现:在state中定义一个对象数组存放路由信息和一个记录当前
index
的变量,并通过map渲染 <Link> 进行展示,并且当 index === this.state.currentIndex
时赋值一个控制红色的className。以下使用更方便的方法
<NavLink>
实现事实上在默认匹配成功时,
<NavLink>
就会添加上一个动态的 active
class。加入我们担心这个class在其他地方被使用了,出现样式的层叠,也可以自定义class。我们通过设置 activeClassName
属性即可实现。(六)<Switch>
以下代码为例,我们可以发现出部分问题:第23、24行代码中组件也渲染了。
原因:默认情况下,react-router中只要是路径被匹配到的Route对应的组件都会被渲染。但是实际开发中,我们往往希望有一种排他的思想,只要匹配到了第一个,那么后面的就不应该继续匹配了,这个时候我们可以使用
<Switch>
来将所有的Route进行包裹即可,具体代码如下:(七)<Redirect>
<Redirect>
用于路由的重定向,当这个组件出现时,就会执行跳转到对应的 to
路径中。💡需要注意的是通常这种逻辑会放在Redux中。以下是一个案例代码:
(八)路由的嵌套
在开发中,路由之间是存在嵌套关系的。以下假设about页面中有多个子页面。点击不同的链接可以跳转到不同的地方,显示不同的内容,以下是案例代码:
(九)手动路由跳转与源码阅读
目前我们实现的跳转主要是通过
<Link>
或者 <NavLink>
进行跳转的。实际上我们也可以通过JavaScript代码进行跳转,但有一个前提:必须获取到history对象。需要注意的是此处的history对象是由路由创建的,并不是我们上述说的路由模式中的history模式,我们可以通过
props.history
(函数组件)/ this.props.history
(类组件) 进行获取,此外可以获取 location
和 match
属性。 获取history对象有两种方式:
1️⃣ 如果该组件是通过路由直接跳转过来的,那么可以直接获取history、location、match对象;
2️⃣ 如果该组件是一个普通渲染的组件,那么不可以直接获取history、location、match对象。
以下是情况1️⃣
情况2️⃣:通过高阶组件可以在组件中添加想要的属性,此时我们使
withRouter
高阶组件帮助我们添加新的属性。但必须要满足两个条件:其一,App组件必须包裹在 Router组件 之内;其二,App组件使用 withRouter高阶组件 包裹。以下需要修该index.js文件:
🔎
withRouter()
源码解读首先我们找到 node_modules/react-router-dom/index.js,可以看到根据环境进行引入不同引入不同的文件。
然后我们跳转到 node_modules/react-router-dom/cjs/react-router-dom.js 文件
然后我们找到 react-router 库,按照类似逻辑,找到
withRouter()
函数定义的地方。 而
context
中的数据又源自于我们在开发中使用的 <BrowserRouter>
或 <HashRouter>
,以 <BrowserRouter>
为例:我们追踪到另一个库 history,react-router-dom本质上使用的是这个 history。
我们重新回到 react-router-dom.js 中,
React.createElement
第一个传入的元素是 reactRouter.Router
,(十)参数传递与动态路由
传递参数有三种方式:
- 动态路由的方式;
- search传递参数;
- Link中to传入对象
动态路由的概念指的是路由中的路径并不会固定:例如
/detail
的path对应一个组件Detail,这是固定的。如果我们将path在Route匹配时写成 /detail/:id
,那么 /detail/abc
、/detail/123
都可以匹配到该Route,并且进行显示。这个匹配规则,我们就称之为动态路由。通常情况下,使用动态路由可以为路由传递参数。1️⃣ 动态路由的方式
路由数据的获取:
props.match
2️⃣ search传递参数❎
假设我们需要传递更多的数据需要用到
search
参数,例如 /detail?id=1242&ok=false
,以下是获取数据的示例代码:url中?后面的参数在服务器端称之为query。
3️⃣ Link中
to
传入对象✅(十一)路由统一配置react-router-config
目前我们所有的路由定义都是直接使用
<Route>
组件,并且添加属性来完成的。但是这样的方式会让路由变得非常混乱,我们希望将所有的路由配置放到一个地方进行集中管理,这个时候可以使用react-router-config来完成。安装react-router-config:
yarn add react-router-config
首先我们需要配置路由映射的关系数组
其次我们需要修改App.js文件
about.js 页面也因子路由也需要作出相应的修改
🔎
renderRoutes()
源码解读二、React Hooks
(一)Hooks的出现
class组件相对于函数式组件的优势:
- class组件可以定义自己的
state
,用来保存组件自己内部的状态;函数式组件不可以,因为函数每次调用都会产生新的临时变量;
- class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑,比如在
componentDidMount
中发送网络请求,并且该生命周期函数只会执行一次;函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
- class组件可以在状态改变时只会重新执行
render
函数以及我们希望重新调用的生命周期函数componentDidUpdate
等;函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次;
class组件存在的问题:
- 复杂组件变得难以理解:
- 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂;
- 比如
componentDidMount
中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在componentWillUnmount
中移除); - 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;
- 难以理解的class:
- 很多人发现学习ES6的class是学习React的一个障碍。
- 比如在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this;
- 虽然我认为前端开发人员必须掌握this,但是依然处理起来非常麻烦;
- 组件复用状态很难:
- 在前面为了一些状态的复用我们需要通过高阶组件或render props;
- 像我们之前学习的redux中
connect
或者react-router中的withRouter
,这些高阶组件设计的目的就是为了状态的复用; - 或者类似于
Provider
、Consumer
来共享一些状态,但是多次使用Consumer
时,我们的代码就会存在很多嵌套; - 这些代码让我们不管是编写和设计上来说,都变得非常困难;
💡 Hook的出现
Hook的出现,可以解决上面提到的这些问题。它可以让我们在不编写
class
的情况下使用 state
以及其他的React特性。同时我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决。Hook的使用场景:
- Hook的出现基本可以代替我们之前所有使用class组件的地方(除了一些非常不常用的场景);
- 但是如果是一个旧的项目,并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;
- Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;
(二)计数器案例对比与useState()
我们通过传统的计数器案例,对比class组件和函数式组件结合hooks的对比
我们可以看到上面的代码差异非常大:函数式组件结合hooks让整个代码变得非常简洁,并且再也不用考虑this相关的问题。
useState()
来自 react 库,需要从react中导入,它是一个hook。其参数是初始化值,如果不设置为 undefined
;其返回值是个数组,包含两个元素:元素一是当前状态的值(第一调用为初始化值)、元素二是设置状态值的函数。当我们点击button后,会完成两件事情:1️⃣调用
setCount
,设置一个新的值;2️⃣组件重新渲染,并且根据新的值返回DOM结构。Hook 就是JavaScript 函数,这个函数可以帮助你钩入(hook into) React State以及生命周期等特性。
⚠️ 但是使用它们会有两个额外的规则:
- 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用。
- 只能在React的函数组件中调用Hook。不要在其他JavaScript函数中调用。
FAQ:为什么叫
useState
而不叫 createState
?“Create” 可能不是很准确,因为
state
只在组件首次渲染的时候被创建。在下一次重新渲染时,useState
返回给我们当前的 state
,如果每次都创建新的变量,它就不是“state”了。这也是Hook的名字总是以use开头的一个原因。(三)useState() 复杂场景
1️⃣ 当我们需要用到多个状态时:
2️⃣ 如果我们需要对复杂状态进行修改:
(四)useEffect()
Effect Hook可以帮助我们来完成一些类似于class中生命周期的功能。💡事实上,类似于网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(Side Effects),所以对于完成这些功能的Hook被称之为Effect Hook。
1️⃣
useEffect
基本使用案例:假如我们现在有一个需求,页面的title总是显示counter的数字,分别使用class组件和Hook实现:
useEffect()
的解析:- 通过
useEffect()
的Hook,可以告诉React需要在渲染后执行某些操作;
useEffect()
要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数;
- 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个回调函数。
2️⃣
useEffect
的清除操作以上是最基本的使用。我们在class组件的编写过程中,某些副作用的代码,我们需要在
componentWillUnmount
中进行清除(比如我们之前的事件总线或Redux中手动调用subscribe), useEffect()
函数的返回值得回调函数刚刚好能帮我们完成这个操作。以下我们使用
useEffect()
模拟实现以前类组件的订阅和取消订阅。为什么要在
useEffect
中返回一个函数?这是
useEffect
可选的清除机制。每个 useEffect
都可以返回一个清除函数,如此可以将添加和移除订阅的逻辑放在一起,它们都属于effect的一部分。但需要注意的是,React会在组件更新和卸载的时候执行清除操作,正如之前说到的,useEffect
在每次渲染的时候都会执行。3️⃣ 多个
useEffect
的使用根据以上类组件的逻辑,我们在
componentDidMount
中实现了修改DOM、订阅事件和网络请求。此时我们使用Effect Hook,我们可以将这些不同逻辑分离到不同的 useEffect
中(从定义的顺序依次执行)4️⃣ Effect 性能优化
默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题
- 某些代码我们只是希望执行一次即可,类似于
componentDidMount
和componentWillUnmount
中完成的事情(比如网络请求、订阅和取消订阅);
- 另外,多次执行也会导致一定的性能问题
我们如何决定
useEffect
在什么时候应该执行和什么时候不应该执行。参数一是执行的回调函数;参数二是useEffect在哪些state发生变化时,才重新执行(受谁的影响)(五)useContext()
在之前的开发中,我们要在组件中使用共享的Context有两种方式:
- 类组件可以通过
类名.contextType = MyContext
方式,在类中获取context;
- 多个Context或者在函数式组件中通过
MyContext.Consumer
方式共享context;
但是有个问题,如果是多个Context共享时就会存在大量的嵌套,优化方案就是通过Context Hook,其允许我们通过Hook来直接获取某个Context的值。⚠️但是需要注意:当组件上层最近的
<MyContext.Provider>
更新时,该 Hook 会触发重新渲染,并使用最新传递给MyContext provider的 context value 值。以下是案例代码:(六)useReducer()
useReducer()
仅仅是 useState()
的一种替代方案。在某些场景下,如果state的处理逻辑比较复杂,我们可以通过 useReducer()
来对其进行拆分,或者这次修改的state需要依赖之前的state时,也可以使用。⚠️ 但是需要注意,数据是不会共享的,它们只是使用了相同的 counterReducer 的函数而已。(我们可以创建一个新的组件进行测试,看Home组件的数据加减是否会影响新的组件)
实验证明,counter在两个组件中是相互独立的。所以,
useReducer()
只是 useState()
的一种替代品,并不能替代Redux。(七)useCallback()
useCallback()
实际的目的是为了进行性能的优化。其具体的优化措施是:useCallback()
会返回一个函数的memoized(记忆的)值,在依赖不变的情况下,多次定义的时候,返回的值是相同的。案例1️⃣:使用useCallback和不使用useCallback定义一个函数是否会带来性能的优化
案例2️⃣:使用useCallback和不使用useCallback定义一个函数传递给子组件是否会带来性能的优化。结论:通常使用
useCallback()
的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存。案例1️⃣中
useCallback()
并没有做到性能优化,以下是性能优化的案例。(八)useMemo()
useMemo()
实际的目的也是为了进行性能的优化。性能优化步骤:
useMemo()
返回的也是一个memoized(记忆的)值,在依赖不变的情况下,多次定义的时候,返回的值是相同的;案例1️⃣:进行大量的计算操作,是否有必须要每次渲染时都重新计算
案例2️⃣:对子组件传递相同内容的对象时,使用
useMemo()
进行性能的优化总结
useMemo()
是对返回值做优化,useCallback()
传入的回调函数做优化。其实useMemo()
可以转化为 useCallback()
,以下是根据useCallback()案例修改后的代码(九)useRef()
在之前类组件的学习中,我们知道refs用于操作DOM。其hook
useRef()
返回一个ref对象,返回的ref对象在组件的整个生命周期保持不变。最常用的ref两种用法:
用法1️⃣:引入DOM(或者组件,但是需要是class组件)元素
用法2️⃣:保存一个数据(前一次的值),这个对象在整个生命周期中可以保存不变
(十)useImperativeHandle()
useImperativeHandle()
与 forwardRef()
有关系,我们通过 forwardRef()
可以将ref转发到子组件,子组件拿到父组件中创建的ref,绑定到自己的某一个元素中,具体解析见之前代码。forwardRef()
的做法本身没有什么问题,但是我们是将子组件的DOM直接暴露给了父组件,其导致的问题是带来某些情况的不可控,父组件可以拿到DOM后进行任意的操作。但是,事实上在上面的案例中,我们只是希望父组件可以操作的focus,其他并不希望它随意操作;(十一)useLayoutEffect()
useLayoutEffect()
看起来和 useEffect()
非常的相似,事实上他们也只有一点区别而已:useEffect()
会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
useLayoutEffect()
会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;
所以,如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到
useLayoutEffect()
。以下是案例代码:
(十二)customHook-自定义Hooks
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。以下以所有的组件在创建和销毁时都进行打印为案例,展示自定义Hook。
(十三)自定义hook案例
1️⃣ Context共享
例如,我们很多组件都需要获取user和token数据,则我们可以封装到一个hook中。
2️⃣ 获取滚动的位置
3️⃣ localStorage数据存储
三、Hooks 原理
(一)引入Fiber
Fiber与浏览器渲染有关系,浏览器渲染与GPU有关系。
浏览器与GPU的交互主要通过WebGPU API实现。WebGPU API使web开发人员能够使用底层系统的GPU(图形处理器)进行高性能计算并绘制可在浏览器中渲染的复杂图形。WebGPU是WebGL的继任者,为现代GPU提供更好的兼容、支持更通用的GPU计算、更快的操作以及能够访问到更高级的GPU特性。
在多进程架构的浏览器如Chromium中,有一个专门的进程(或者线程)和GPU设备进行交互,执行GL操作。任何一条GL命令都必须经由这个进程处理后才提交给GPU驱动,这个进程就是GPU进程。Renderer进程或者Browser进程必须通过IPC才能与GPU进程交互,告诉GPU进程需要执行哪些GL命令。
在这种架构中,多个GPU客户端可能会同时访问GPU服务,而GPU客户端之间可能存在数据依赖关系,因此必须提供一种同步机制保证GPU操作的先后次序。这就涉及到了同步点 (SyncPoint)机制的基本原理。GPU客户端与GPU进程之间的交互封装在
GpuCommandBufferProxy
类中,这个代理类会向GPU服务端的 GpuCommandBufferStub
发送IPC消息请求执行某些GPU操作。总的来说,浏览器与GPU的交互和同步信号的关系是通过一系列的API和同步机制来实现的,以确保GPU操作的正确性和效率。
⚠️ 而且GUI渲染和Js代码的执行是在同一个线程,即二者是互斥的,过长的Js代码执行时间会阻塞GUI的渲染造成页面的卡顿。当然在该线程中还包含有例如用户事件的响应、键盘事件的响应、Js代码的执行、Layout布局、Paint和raf(request Animation FrameCall)
React为了优化浏览器渲染,将部分的操作(E.g. diff算法、更新)放到了一个算法中——reconciliation(协同),通过该算法将操作切分成了许多的Fiber,最终形成一个Fiber树,而Fiber树也是根据ReactElement树生成的,Fiber可以理解为一个执行单元(碎片)。
然后先让浏览器执行一些执行的操作(上述的用户事件的响应、键盘事件的响应…),当浏览器闲置时,通过
requestIdleCallback()
传入我们的Fiber碎片,执行React的部分代码,即充分使用剩余剩余的时间。💡 requestIdleCallback()
在浏览器中兼容性并不好,并且如果在 requestIdleCallback()
内部进行DOM更改操作,可能会超出浏览器提供的deadline。对此React 实现了一个类似的机制。他们使用了 requestAnimationFrame
和 MessageChannel
来模拟一帧的空闲判断。requestAnimationFrame
是由系统来决定回调函数的执行时机,它会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随屏幕的刷新频率,不会引起丢帧和卡顿。加上我们之前说的Lane概念,可以得出以下结论:
Fiber:
- Fiber是React的核心算法,也被称为Fiber Reconciliation。
- Fiber是一种数据结构,可以用一个纯JS对象来表示。它是React架构运行时动态的工作单元。
- Fiber的存在使异步可中断的更新成为可能。每次执行完一个执行单元,React就会检查还剩多少时间,如果没有时间就将控制权让出去。
- Fiber是一种将reconciliation(递归diff)拆分成无数个小任务的算法;它随时能够停止,恢复。
Lane:
- Lane是React 18中引入的一种新的优先级机制,用于判断具体任务属于哪个优先级。
- React使用二进制来表示优先级,然后在运行时会将Lane优先级转换成Scheduler优先级。
Fiber和Lane都是React内部的关键机制,它们共同支持React的并发模式和时间切片功能,从而提高了React的性能和用户体验。
(二)Hooks原理
上一节提到Fiber的原因是,hooks存储的数据实际上是挂载到fiber上面,使用fiber进行存储,从而生成fiber树。下文以
useState()
为案例分析hooks的底层原理。我们查看React@18的源码,从 packages/react/index.js 入手,跳转到 packages/react/src/ReactClient.js,最后发现
useState()
源自于此:以上实际上只是声明了函数的类型,具体实现需要到 packages/react-reconciler/src/ReactFiberHooks.js 文件中
以上是第一次调用
useState()
的大致执行流程,第二次执行就会执行类似的 HooksDispatcherOnUpdate()
函数,最终发现是调用 updateReducer()
函数💡
updateWorkInProgressHook()
也解释了为什么 useState()
不能使用if条件判断,因为当条件中止时,链表结构就会断裂对后续执行造成影响。四、Redux Hooks
Redux的hooks是React Redux库提供的一种新的API,它允许你在函数组件中直接访问Redux store和dispatch actions。这些hooks在v7.1.0版本中首次添加。以下是Redux的主要hooks:
useSelector()
:这个hook允许你从Redux store的state中提取数据,使用一个选择器函数。选择器函数应该是纯函数,因为它可能会被执行多次并在任意时间点执行。选择器将会用整个Redux store state作为其唯一的参数。选择器可以返回任何结果,包括直接返回嵌套在state内部的值,或者派生新的值。当一个action被dispatch时,useSelector()
会进行引用比较,比较前一个选择器结果值和当前结果值。如果它们不同,组件将被强制重新渲染。如果它们相同,组件将不会重新渲染。⚠️useSelector()
默认使用严格的===
引用相等检查,而不是浅相等。
useDispatch()
:这个hook返回一个dispatch函数,允许你在组件中直接dispatch一个action。
useStore()
:这个hook返回Redux store的引用,你可以用它来访问store的state或者dispatch actions。但是,你应该尽量避免使用这个hook,因为它会导致组件重新渲染。
以下是一些使用Redux hooks的例子:
在这个例子中,我们创建了一个
Counter
组件,它从Redux store中获取 counter
值,并使用 dispatch
函数来发送 INCREMENT
和 DECREMENT
动作。这个例子前提是假设你的Redux store中有一个
counter
属性,以及对应的 INCREMENT
和 DECREMENT
动作。useStore
通常不推荐使用,因为它会导致组件重新渲染。但是,如果确实需要访问store,可以这样做:在这个例子中,我们使用
useStore
获取了Redux store的引用,然后使用 getState
和 dispatch
方法。- 作者:😈Zabanya
- 链接:https://blog.zabanya.space/article/aecd30cc-df10-465a-9033-14eb941f8af4
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处