当前位置: 首页 > news >正文

React(八):引出Hook、useState、useEffect的使用详解

React(八)

  • 一、类组件的优劣势
    • 1.类组件的优势
    • 2.类组件的劣势
      • (1)复杂组件会难以理解
      • (2)复杂的class
      • (3)组件复用状态很难
  • 二、Hook初体验useState
    • 1.使用Hook的计数器案例
    • 2.详解useState
      • (1)猜测它的原理
      • (2)参数和返回值是什么?
    • 3.使用Hook的一些规则
  • 三、useEffect使用详解
    • 1.基本使用方式和细节
    • 2.清除Effect(如事件监听、订阅)
    • 3.可以使用多个useEffect
    • 4.决定useEffect的执行次数

一、类组件的优劣势

1.类组件的优势

之前我们一直用类组件写demo,类组件相对于函数组件有什么优势?

  • 类组件可以保存组件自己的状态,但是在函数组件中,修改数据页面不会重新渲染,而且就算重新渲染,重新执行函数又重新赋值,等于没改。
import React, { PureComponent } from 'react'// 函数组件
function HelloHooks() {// 就算函数重新执行, 又会重新赋值, 无意义let message = "Hello Hooks"return(<div><h2>{message}</h2></div>)
}export class App extends PureComponent {render() {return (<div><HellWorld/><HelloHooks/></div>)}
}export default App
  • 类组件有自己的生命周期钩子,比如我们在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次。但是函数组件在哪里发请求呢?如果在函数中发请求,那么每次重新渲染又重新调用函数又重新发请求,这样反复发请求很沙雕。

class组件可以在状态改变时, 只重新执行render函数以及我们希望重新调用的生命周期函数(如componentDidUpdate)函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们内部的某段代码只执行一次。

所以在Hook之前通常都用类组件写项目。

2.类组件的劣势

(1)复杂组件会难以理解

随着业务的增多,我们的class组件会变得越来越复杂;

比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在 componentWillUnmount中移除);

而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;

(2)复杂的class

最显著的问题就是this的指向问题,如果this不处理好,可能会出现一些错误。

(3)组件复用状态很难

比如我们之前使用redux中的数据需要用connect这样的高阶组件,或者如果想实现什么功能还需要自己封装高阶组件给类组件注入一些东西,这还是非常费劲的。

二、Hook初体验useState

如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;
Hook只能在函数组件中使用,不能在类组件或者函数组件之外的地方使用;

1.使用Hook的计数器案例

之前用类组件的写法我们都很熟知了:

import React, { PureComponent } from 'react'export class Counter1 extends PureComponent {constructor() {super()this.state = {counter: 10}}changeNumber(num) {this.setState({counter: this.state.counter + num})}render() {const { counter } = this.statereturn (<div><h2>当前计数: {counter}</h2><button onClick={() => this.changeNumber(-1)}>-1</button><button onClick={() => this.changeNumber(1)}>+1</button></div>)}
}export default Counter1

我们再看看用函数式组件,这个东西怎么写:

import React, { memo, useState } from 'react';const Counter = memo(() => {let [counter, setCounter] = useState(99);return (<div><h2>Counter:{counter}</h2><button onClick={() => setCounter(counter+1)}>点击让Counter+1</button></div>)
})export default Counter

真的是非常的简洁,而且没有this的指向问题。

2.详解useState

(1)猜测它的原理

它与class组件里面的 this.state 提供的功能完全相同,就是用来保存状态(数据)的。

上面的代码点击按钮会发生两件事情:
1、调用setCounter修改counter
2、组件重新渲染(函数重新执行),根据新值返回DOM结构

一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。我盲猜这里用到了闭包:setCounter函数内部保存了state中的原始变量,等到setCounter函数被调用后原始变量才会被销毁,数据更改后再重新执行函数组件并把新值传过去,此时数据就有了最新的值。(当然只是盲猜)

(2)参数和返回值是什么?

useState只有一个参数: 接收一个初始化状态的值(设置初始值),在第一次组件被调用时使用来作为初始化值(如果不设置则默认为undefined)。

useState的返回值: 返回一个数组,数组包含两个元素:

  • 元素1:当前状态的值(第一次调用为初始值)
  • 元素2:修改状态的函数

一般我们会对数组进行解构(名字当然是自己取):

 let [counter, setCounter] = useState(99);

3.使用Hook的一些规则

通过上面的讲解,可以感受到hook顾名思义就是把要用的东西钩过来用一下子。

使用Hook的规则:

1、只能在函数组件的顶层调用 Hook。不能在循环语句、条件判断语句或者子函数中调用。
2、只能在 React 的函数组件和自定义hook中调用 Hook。不能在其他 JavaScript 函数中调用。

当然,我们可以定义更多类型的数据。
也可以把修改的操作单独封装一个函数。

import React, { memo, useState } from 'react';const Counter = memo(() => {let [counter, setCounter] = useState(99);let [newsList, setNewsList] = useState(['体育','娱乐']);let [userInfo, setUserInfo] = useState({name:'zzy',age:18});function changeNum() {setCounter(counter - 1);}return (<div><h2>Counter:{counter}</h2><button onClick={() => setCounter(counter+1)}>点击让Counter+1</button><button onClick={changeNum}>点击让Counter-1</button><h3>{newsList}</h3><h3>{userInfo.name}-{userInfo.age}</h3></div>)
})export default Counter

三、useEffect使用详解

在类组件中是可以有生命周期函数的, 那么如何在函数组件中定义类似于生命周期这些函数呢? 答案就是:useEffect钩子

顾名思义,其实我们平时的网络请求、手动更新DOM、一些事件的监听、订阅redux数据变化等操作,都是除了更新DOM之外需要做的操作,也就是一些副作用effect

1.基本使用方式和细节

比如一个这样的案例:修改counter时,实现页面标题和counter同步变化

类组件实现:

import React, { PureComponent } from 'react'export class App extends PureComponent {constructor() {super()this.state = {counter: 100}}// 进入页面时, 标题显示countercomponentDidMount() {document.title = this.state.counter}// 数据发生变化时, 让标题一起变化componentDidUpdate() {document.title = this.state.counter}render() {const { counter } = this.statereturn (<div><h2>{counter}</h2><button onClick={() => this.setState({counter: counter+1})}>+1</button></div>)}
}export default App

不难看出,类组件想要实现这个效果,需要写两个生命周期钩子,componentDidMount只有第一次挂载完成会执行,componentDidUpdate在每次更新结束后会执行。

那么函数组件怎么实现这个效果呢?答案是使用useEffect

import React, { memo, useEffect, useState } from 'react'const App = memo(() => {const [counter, setCounter] = useState(200)// useEffect传入一个回调函数, 在页面渲染完成后自动执行useEffect(() => {// 一般在该回调函数在编写副作用的代码(网络请求, 操作DOM, 事件监听)document.title = counter})return (<div><h2>{counter}</h2><button onClick={() => setCounter(counter+1)}>+1</button></div>)
})export default App

useEffect传入一个回调函数, 这个回调函数在每次页面渲染完成后自动执行。也就是说,每次在函数式组件执行的顺序是:

执行函数组件 => 定义初始状态 => 渲染DOM => 执行useEffect中的回调

=> 修改数据 => 重新执行函数组件 => 更新状态 => 渲染最新DOM => 执行useEffect中的回调

不难看出,其实useEffect中的回调相当于完成了componentDidMountcomponentDidUpdate做的事情。

2.清除Effect(如事件监听、订阅)

在类组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除,比如事件总线redux的数据订阅,那这在函数式组件中怎么做呢?

useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B

useEffect(() => {console.log('订阅了redux,绑定了某个事件A')return () => {console.log('取消订阅redux,解绑某个事件A')}
})

返回的这个回调函数执行的时机有两个:组件即将更新数据、组件即将卸载。

什么意思呢?如果我们在useEffect回调中绑定了某个事件,那么每次更新数据,每次都要渲染DOM,每次渲染DOM就会引起每次useEffect回调重新执行,这样的话相当于只要更新数据就反复绑定事件,嘚儿嘚儿嘚儿绑定一堆事件,这显然有点奥里给啊。

所以说我们可以协商这个返回的回调函数,每次更新数据前先执行回调解绑事件,再更新再绑定事件,也就是同样的事件每次只绑定一个就够啦。

3.可以使用多个useEffect

使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:

比如网络请求、事件监听、手动修改DOM,这些往往都会放在componentDidMount中;

一个函数组件中可以使用多个Effect Hook,我们可以将逻辑分离到不同的useEffect中:

import React, { memo } from 'react';
import { useState, useEffect } from 'react';const Counter = memo(() => {let [counter, setCounter] = useState(99);useEffect(() => {//当前回调会在页面渲染完成后重新执行,每次都重新执行document.title = counter;})useEffect(() => {console.log('订阅了redux,绑定了某个事件A')return () => {console.log('取消订阅redux,解绑某个事件A')}})useEffect(() => {console.log('发送网络请求的逻辑')})return (<div><h2>Counter:{counter}</h2><button onClick={() => setCounter(counter + 1)}>点击让Counter+1</button></div>)
})
export default Counter

React将按照 effect 声明的顺序依次调用组件中的每一个 effect;

4.决定useEffect的执行次数

默认情况下,useEffect的回调函数会在每次渲染都重新执行,但是某些代码我们只是希望执行一次即可(比如网络请求,绑定事件、订阅redux)。另外,多次执行也会导致一定的性能问题。

我们如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?

useEffect实际上有两个参数:

  • 参数1:回调函数,上面已经提到了
  • 参数2:一个数组,表示该useEffect在哪些state发生变化时,才重新执行(受谁的影响才会重新执行)

我们来看下面这个案例,就明白了:

import React, { memo } from 'react';
import { useState, useEffect } from 'react';const Counter = memo(() => {let [counter, setCounter] = useState(99);let [message, setMessage] = useState('DJ drop');useEffect(() => {//当前回调会在页面渲染完成后重新执行,每次都重新执行document.title = counter;console.log('counter的值变了,我瞅见了!')},[counter])useEffect(() => {console.log('message的值改变了!我瞅见了!')},[message])useEffect(() => {console.log('counter和message的值都变了!我瞅见了!')},[counter,message])return (<div><h2>Counter:{counter}</h2><button onClick={() => setCounter(counter + 1)}>点击让Counter+1</button><button onClick={() => setMessage('the beat')}>改变message</button></div>)
})
export default Counter

在上面这个案例中,页面第一次渲染每个useEffect都会先执行,然后第一个useEffect只有当counter的值改变时会重新调用,第二个useEffect只有当message的值改变时会重新调用,第三个useEffectcountermessage的值任意一个改变时会重新调用。

当然啊,如果要模拟生命周期如componentDidMount这种只在挂载时执行一次,那么第二个参数写个空数组[]就行了,表示我只调用第一次,后面任何数据的修改都不会引起我的重新调用。

import React, { memo } from 'react';
import { useState, useEffect } from 'react';const Counter = memo(() => {let [counter, setCounter] = useState(99);useEffect(() => {console.log('订阅了redux,绑定了某个事件A')return () => {console.log('取消订阅redux,解绑某个事件A')}},[])useEffect(() => {console.log('发送网络请求的逻辑')},[])return (<div><h2>Counter:{counter}</h2><button onClick={() => setCounter(counter + 1)}>点击让Counter+1</button></div>)
})
export default Counter

那么这样的话,其实回调中返回值的那个回调,也只有在组件即将销毁时调用,相当于componentWillUnmount了,非常奈斯。

相关文章:

React(八):引出Hook、useState、useEffect的使用详解

React&#xff08;八&#xff09;一、类组件的优劣势1.类组件的优势2.类组件的劣势&#xff08;1&#xff09;复杂组件会难以理解&#xff08;2&#xff09;复杂的class&#xff08;3&#xff09;组件复用状态很难二、Hook初体验useState1.使用Hook的计数器案例2.详解useState&…...

32*4VKL128 LQFP44超低功耗/超低工作电流/抗干扰LCD液晶段码驱动IC/LCD驱动芯片(IC) 适用于激光/红外线测距仪

产品型号&#xff1a;VKL128产品品牌&#xff1a;永嘉微电/VINKA封装形式&#xff1a;LQFP44产品年份&#xff1a;新年份原厂&#xff0c;工程服务&#xff0c;技术支持&#xff01;VKL128概述:VKL128是一个点阵式存储映射的LCD驱动器&#xff0c;可支持最大128点&#xff08;3…...

自定义控件(?/N) - 事件分发

一、外部传递到ViewGroup中Activity会通过 getWindow( ) 获取PhoneWindow对象并调用它的superDispatchTouchEvent( )&#xff0c;该方法会调用它&#xff08;PhoneWindow&#xff09;的内部类 DecorView 的 superDispatchTouchEvent( )&#xff0c;而它&#xff08;DecorView&a…...

诗一样的代码命名规范

有文化&#xff1a;落霞与孤鹜齐飞&#xff0c;秋水共长天一色&#xff1b;没文化&#xff1a;太阳落山的时候&#xff0c;看见一只鸟在水上飞&#xff1b;日常编码中&#xff0c;代码的命名是个大的学问。能快速的看懂开源软件的代码结构和意图&#xff0c;也是一项必备的能力…...

L1-010 比较大小 L1-030 一帮一 L1-015 跟奥巴马一起画方块 L1-035 情人节

本题要求将输入的任意3个整数从小到大输出。 输入格式: 输入在一行中给出3个整数&#xff0c;其间以空格分隔。 输出格式: 在一行中将3个整数从小到大输出&#xff0c;其间以“->”相连。 输入样例: 4 2 8 输出样例: 2->4->8 // 题目链接 https://pintia.cn/prob…...

打怪升级之如何发送HEX进制的数据出去

Hex数据老大难 不少人都困扰于如何将电脑中读取到的string类型的数据变成整形发送出去。一半来说&#xff0c;不论你调用的通信方式是串口的还是网络的&#xff0c;亦或是PCIE的&#xff0c;其在电脑端的实际情况都是以系统API的形式呈现的。而系统API函数提供的接口&#xff…...

国产8K摄像机拍摄回顾与画面数据反馈

本文分析两款国产8K摄像机&#xff0c;一款是全画幅&#xff0c;一款是M43画幅。一、全新国产全画幅8K B1机器参数数据汇总&#xff1a;全画幅8K 60fps&#xff0c;受益于8K全画幅的优势与大幅升级的图像处理系统&#xff0c;BOSMA 8K摄像机系统提升到新的高度。拍摄支持&#…...

C++中拷贝构造和赋值重载的注意事项以及编译器的优化处理

C中拷贝构造和赋值重载的注意事项以及编译器的优化处理前言1. 拷贝构造和赋值重载的易混淆点和注意事项1.1 易混淆点1.2 注意事项2.编译器对拷贝构造和赋值重载的优化处理前言 本文可以帮助你对下面&#xff1a; &#xff08;1&#xff09;何时调用拷贝构造何时调用赋值重载 &a…...

Java设计模式_单例模式

Java设计模式_单例模式 亦称&#xff1a; 单件模式、Singleton 意图 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a…...

刚刚学完CSS :display float,flex flow 傻傻分不清了

目录 描述 示例&#xff1a; CSS 中的 display CSS 中的 float CSS 中的 flex 描述 刚刚学完CSS ,导致浮动&#xff08;float&#xff09;&#xff0c;弹性布局&#xff08;display:flex&#xff09;好几个字段配置属性已经分不清了。 display float 就同层级别&#xf…...

python建立图片索引数据库,根据一段文字,找到存放在电脑上最匹配的图片

python建立图片索引数据库&#xff0c;根据一段文字&#xff0c;找到存放在电脑上最匹配的图片 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、程序的用处 一键视频 可以根据一…...

MySQL OCP888题解048-letter N in slow query log(慢查询日志里的字母N)

文章目录1、原题1.1、英文原题1.2、中文翻译1.3、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3.1、知识点1&#xff1a;mysqldumpslow - 总结缓慢的查询日志文件4、实验4.1、实验14.1.1、实验目的4.1.2、实验前准备4.1.3、实验步骤4.1.4、实验结论5、总结1、原题 1.1…...

数据采集 - 笔记 2

1快速实现西门子S7系列PLC数据采集 快速实现西门子S7系列PLC数据采集 - 知乎 2 什么是时序数据库&#xff1f; 时序数据库&#xff08;Time Series Database&#xff09;是一种特殊类型的数据库&#xff0c;用于存储和处理时间序列数据。时间序列数据是指按时间顺序排列的数…...

电子技术——数字IC技术,逻辑电路和设计方法

电子技术——数字IC技术&#xff0c;逻辑电路和设计方法 在我们之前的学习中&#xff0c;我们学习了CMOS技术&#xff0c;然而CMOS技术并不是唯一的数字逻辑技术&#xff0c;因此&#xff0c;本节系统的介绍当今使用的数字技术和逻辑电路族。 数字IC技术和逻辑电路族 逻辑电…...

[ROS2 知识] 包依赖关系和rosdep详述

一、说明 如果你建立一个工作空间,试图将所有包的依赖项搞明白,或者期望将包的依赖项全部安装到工作空间中,您看本文是正确选择。本文将解释如何使用 rosdep 管理外部依赖项。 二、介绍rosdep 2.1 rosdep是何物? rosdep 是 ROS 的依赖管理实用程序,可以与 ROS 包和外部库…...

mysql创建索引导致死锁,数据库崩溃,完美解决方案

文章目录写在前面一、短事务场景下&#xff0c;执行DDL语句场景分析1、短事务场景下&#xff0c;执行表字段添加操作2、短事务场景下&#xff0c;执行表字段修改操作3、短事务场景下&#xff0c;执行表字段删除操作&#xff08;1&#xff09;往里添加一条数据试试4、短事务场景…...

c++11 标准模板(STL)(std::unordered_map)(八)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…...

企业ISO体系认证办理,可以自行申请吗?为什么都要找咨询公司?

企业ISO体系认证办理&#xff0c;可以自行申请吗&#xff1f;为什么都要找咨询公司&#xff1f; 很多人认为ISO咨询公司为中介机构&#xff0c;希望直接找认证公司进行认证。其实认证机构担任的是认证审核职责&#xff0c;咨询机构担任的是咨询职责。按中国国家任可监委员会的…...

二、Neo4j源码研究系列 - 单步调试

二、Neo4j源码研究系列 - 单步调试 一、背景介绍 上一篇我们已经把了neo4j的源码准备以及打包流程完成了&#xff0c;本篇将讲解如何对neo4j进行单步调试。对于不了解如何编译打包neo4j的读者&#xff0c;请阅读《一、Neo4j源码研究系列 - 源代码准备》。 大纲&#xff1a; …...

基于Qt WebEngine 的Web仪器面板GUI程控技术

随着IIoT的发展&#xff0c;很多工业仪器也具备了远程管理的GUI。与早期使用串口进行命令交互不同&#xff0c;这些GUI可以直接在远程呈现数据。 作为希望对仪器、软件进行二次开发的小公司来说&#xff0c;会遇到GUI人工操作转自动化的需求。在无法通过串口等传统接口进行自动…...

海康摄像头使用RTSP

1.协议格式。海康威视IP摄像头rtsp协议地址如下&#xff1a;rtsp://[username]:[passwd][ip]:[port]/[codec]/[channel]/[subtype]/av_stream主码流&#xff1a;rtsp://admin:12345192.168.1.64:554/h264/ch1/main/av_streamrtsp://admin:12345192.168.1.64:554/MPEG-4/ch1/mai…...

编程语言分类

目录 ❤ 机器语言 机器语言的编程 ❤ 汇编语言 ❤ 高级语言(编程语言) 编译型 解释型 ❤ 动态语言和静态语言 ❤ 强类型定义语言和弱类型定义语言 ❤ 主流语言介绍 C语言 C java python JavaScript SQL PHP python从小白到总裁完整教程目录:https://blog…...

[nodejs开发] typescript引入js模块或文件

首先更改tsconfig.json 中的compilerOptions属性&#xff1a;"moduleResolution": "Node"假设有一个abc.js其内容如下&#xff1a;var Circle (function () {function Circle() {}Circle.prototype.draw function () {console.log("Cirlce is drawn…...

小帮软件机器人应用于通信集团财务数据填报、编制、稽核、银企对账

某大型通信集团是国有控股通信运营服务提供商&#xff0c;主要从事国内外通信设施服务业务、固定通信业务、移动通信业务、数据通信业务、网络接入业务、卫星国际专线业务和通信业务相关系统集成业务&#xff0c;管辖20多家子&#xff08;分&#xff09;公司、服务运营和支持网…...

37. CF-Weights Distributing

链接 这是一个比较经典的题目。容易想到求出两段路径重合的部分&#xff0c;然后贪心的放权值。那么跑三次最短路&#xff0c;枚举重合部分的端点即可。 正解没什么好说的。这题有趣的地方在于&#xff0c;如果数据比较弱&#xff0c;可能会把一些错误做法放过去。 一种错误…...

百丽时尚×优维科技×道客战略启动「云原生一体化项目」

3月7日&#xff0c;由百丽时尚集团&#xff08;以下简称&#xff1a;百丽时尚&#xff09;联合优维科技、道客共同举办的「云原生一体化项目启动会」在深圳百丽国际大厦圆满落幕&#xff0c;项目合作三方齐聚一堂&#xff0c;就云原生一体化建设战略方案达成合作共识&#xff0…...

小诺开源技术

小诺开源技术 文章目录小诺开源技术前言页面演示介绍文档学习建议登录地址下载地址前言 近期接触了小诺开源技术的一个前端框架&#xff0c;底层是蚂蚁框架&#xff0c;感觉很好用&#xff0c;不过需要稍微学习并适应一下&#xff0c;推荐给大家&#xff0c;本篇仅用于学习&am…...

AidLux AI应用案例悬赏选题 | 纺织品表面瑕疵检测

AidLux AI 应用案例悬赏征集活动 AidLux AI 应用案例悬赏征集活动是AidLux推出的AI应用案例项目合作模式&#xff0c;悬赏选题将会持续更新。目前上新的选题涉及泛边缘、机器人、工业检测、车载等领域&#xff0c;内容涵盖智慧零售、智慧社区、智慧交通、智慧农业、智能家居等…...

UE官方教程笔记02-实时渲染基础下

对官方教程视频[官方培训]02-实时渲染基础下 | 陈拓 Epic的笔记没听懂的地方就瞎写反射实时渲染中反射是一个非常有挑战的特性UE中有多种不同的方案&#xff0c;各有各的优势和缺点反射捕获屏幕空间反射平面反射LumenRT Reflection反射捕获在指定位置捕获一张Cube Map需要预计算…...

grep命令——在文件中搜索指定的文本模式

grep是英文词组“global search regular expression and print out the line”的缩写&#xff0c;意思是全局搜索正则表达式&#xff0c;并将结果输出。 通常将grep命令与正则表达式搭配使用&#xff0c;命令选项作为搜索过程中的补充或对输出结果的筛选&#xff0c;命令模式十…...