hooks组件+例子+底层机制

news2024/11/18 23:50:08

1.React组件分类

函数组件
1.不具备"状态、ref、周期函数"等内容,第一次渲染完毕后,无法基于组件内部的操作来控制其更新,因此称之为静态组件!。但是具备属性及插槽,父组件可以控制其重新渲染
2.渲染流程简单,渲染速度较快
3.基于FP(函数式编程)思想设计,提供更细粒度的逻辑组织和复用
类组件
1.具备“状态、ref、周期函数、属性、插槽"等内容,可以灵活的控制组件更新,基于钩子函数也可灵活掌控不同阶段处理不同的事情。
2.渲染流程繁琐,渲染速度相对较慢
3.基于OOP(面向对象编程)思想设计,更方便实现继承等

React Hooks组件,就是基于React 中新提供的 Hook函数,可以让函数组件动态化!


基础Hook
useState使用状态管理
useEffect使用周期函数
useContext使用上下文信息

额外的 Hook
useReducer useState的替代方案,借鉴redux处理思想,管理更复杂的状态和逻辑
useCallback构建缓存优化方案
useMemo构建缓存优化方案
useRef 使用ref获取DOM
useImperativeHandle配合forwardRef (ref转发)一起使用
useLayoutEffect 与useEffect相同,但会在所有的DOM变更之后同步调用effect‘

2.回顾之前学习的两个hooks

createRef:

createRef API提供了一种简单的方法来创建一个容器用来存储对组件实例的引用。createRef在组件的constructor中被调用,并返回一个可分配给ref属性的React引用。当组件实例化时,React会创建一个引用,并将其与组件的ref属性进行绑定,可以通过这个引用来访问组件的属性和方法。可以将ref值传递给子组件,并在子组件中使用props设置。

forwardRef:
ref直接赋值给一个函数组件的时候是报错的,需要配合forwardRef实现一个ref的转发,就是将ref的值转发到组件的内部,这样就可以在父组件中拿到函数子组件中的某个元素

例子:

使用forwardRef实现ref转发的例子:

import React, { forwardRef } from 'react';

function MyComponent(props, ref) {
  return (
    <input type="text" ref={ref} />
  );
}

export default forwardRef(MyComponent);

在以上代码中,我们使用forwardRef方法来将ref向下传递到组件内部。forwardRef接受一个函数组件作为参数,并返回一个新的组件,该组件可以接受ref作为第二个参数。

然后在父组件中,我们可以使用ref来访问MyComponent组件内部的input元素。以下是一个使用ref访问MyComponent组件内部输入框值的例子:

import React, { useRef } from 'react';
import MyComponent from './MyComponent';

function ParentComponent() {
  const myRef = useRef(null);

  function handleClick() {
    const node = myRef.current;
    console.log(node.value);
  }

  return (
    <div>
      <MyComponent ref={myRef} />
      <button onClick={handleClick}>Click Me</button>
    </div>
  );
}

export default ParentComponent;

在以上代码中,我们通过将myRef传递给MyComponent,将ref向下传递到组件内部,并将其分配给input元素。在handleClick方法中,我们使用myRef.current来访问input元素的value属性。

为什么报错??报错的底层原因?

如果直接将ref赋值给函数而不是函数的实例,则会导致TypeError: Cannot add property X, object is not extensible错误。底层原因是在JavaScript中,函数是一个非扩展的对象。这意味着不能像处理普通对象一样将属性添加到函数中。因此,直接将ref分配给函数是不允许的。当将ref分配给函数时,React会尝试将ref对象分配给该函数的[[Prototype]],从而导致该错误。如果想要使用类似的模式,可以考虑将ref分配给函数的属性。这个模式可以使函数组件更加可控,从而避免尝试向函数分配非法属性。

那么为什么非扩展的对象不能像处理普通对象一样将属性添加到函数中??

在JavaScript中,对象是一组键值对的集合,而函数是一个特殊的对象,它可以包含属性和方法。对象可以根据需要动态添加新属性,但是函数却是一个非扩展的对象,它不能添加新属性。

函数对象是非扩展的,是因为在运行时引擎对它们进行了优化,使其能够快速执行函数调用。如果一个函数是可扩展的,那么引擎就必须为每个函数实例预留额外的空间,这会影响性能。

另外,在ECMAScript中,函数被视为一种特殊类型的对象,但函数不会像对象那样包含内部属性和方法。因此,函数会由于其执行的上下文而隐式地获取一些属性和方法。

因此,虽然在JavaScript中函数看起来像对象,但它们实际上是一种与对象不同的特殊类型,这就是为什么非扩展的函数对象不能像处理普通对象一样添加属性的原因。


3.useState函数

useState返回的是一个长度为 2 的数组,其中第一个元素是状态对象,第二个元素是更新状态的函数。这个更新状态的函数与 Class 组件中的 setState 函数类似,但是它不会自动进行合并更新,而是直接替换。

例如,以下代码使用了 useState 来维护一个数字类型的状态:

import React from 'react';

const MyComponent = () => {
  const [count, setCount] = React.useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
};

export default MyComponent;

在这个例子中,useState的初始值为0,这个值赋给了count变量。setCount函数用于更新这个状态值,它接受一个新的状态值作为参数,调用这个函数后,count的值会被替换为这个新值。

因此,与 Class 组件中的state不同,useState返回的是一个数组,而不是一个对象。但是,使用数组的下标来获取状态和更新函数并不是非常方便或可读性很高的,因此,可以使用数组解构来获取这两个变量。

注意点

没问题,以下是修改后的回答:

函数组件「或者Hooks组件」不是类组件,所以没有实例的概念。调用函数组件会执行函数体,产生一个私有的上下文,函数内部的局部变量和状态只在这个私有上下文中存在。因此,在函数组件中不涉及this的处理。这里的this都是undefined


4.探索hooks组件底层机制

函数组件的每一次渲染或更新的过程中,都会执行一次函数体,产生一个全新的私有上下文,这是函数组件和类组件最大的不同之处。这也就意味着,函数组件中的内部代码也需要重新执行。

当函数组件被渲染时
会创建一个新的私有上下文和这个上下文中的变量。这些变量的值可能来源于组件的props、组件自身的state、Hooks以及其他局部变量。渲染完成后,这些变量的值就会被保存在私有上下文中,供后续的更新过程使用。

当组件进行更新时
会执行函数体,并根据新的props、state、Hooks等值,生成新的私有上下文,更新这些变量的值。在更新完成后,这些变量的值也会被保存在私有上下文中,供下一次更新使用。

示例:

在这里插入图片描述

由于函数组件没有实例的概念,所以在函数组件中不可能像类组件那样保存实例级别的变量。这也是为什么「Hooks」把状态与组件自身解耦,让每次调用都是一个新的过程,且依赖项变化时才会重新渲染。


5.继续深究

usestate是否有缓存??

useState 确实有缓存机制,每次重新渲染组件时,并不会重新创建 useState 声明的状态变量,而是直接读取上一次保存的状态。这是因为,React 会在内存中将组件的状态缓存起来,以便在下一次重新渲染时进行比较与处理。当一个组件重复渲染时,由于组件内部代码会重新执行,所以每次调用useState时,都会返回相同的状态值和更新函数,而不是重新创建这些状态值和更新函数。diff算法后面会写

当然,这个缓存机制并不是绝对的,它受到多种因素的影响,例如组件的依赖关系、组件的状态变化频率、调用 useState 的位置等等。在某些情况下,useState 的变量会被重新创建,导致缓存失效。

缓存在什么地方?

React 会将所有组件的状态和相关信息存储在内存中,而非持久化存储在硬盘上,因此当关闭浏览器窗口或刷新页面时,缓存的信息会被清空。

useState细节处理和同步异步

1.每次记得管理所有的对象!!!不更改的对象一定要展开赋值
在这里插入图片描述

官方推荐写法:
每个状态分开!!!

在这里插入图片描述

2.在React18中,我们基于useState创建出来的“修改状态的方法”,它们的执行也是异步的原理:等同于类组件中的this.setState基于异步操作&更新队列,实现状态的批处理,在任何地方修改状态,都是采用异步编程的。

如果想要变成同步的代码该怎么办????---->flushSync

在这里插入图片描述

什么时候会变成同步的??

使用 useStateuseReducer 钩子函数更新状态时,如果在合成事件或生命周期函数中触发,那么它的更新也是异步的。也就是说,在这种情况下,无论执行多少次更新操作,实际上只有最后一次更新操作被添加到了更新队列中,而之前的更新操作都被忽略了。

但是,如果使用 useStateuseReducer 钩子函数更新状态的操作被放在其他异步操作中(例如定时器、手动事件绑定等),那么它的更新操作实际上是同步执行的,而不是异步的。这是因为,React 无法感知到这些操作,也就无法将它们添加到更新队列中进行批量处理。

在这里插入图片描述

为什么react无法感知???这些操作????

React 并不知道在一些异步操作中进行的状态更新操作,是因为 React 的更新机制这是基于同步的调用栈。

当我们在合成事件或生命周期函数中触发状态更新操作时,React 会立即响应这些操作并将它们添加到更新队列中,从而在组件进行下一次更新时批量处理这些操作,以达到性能优化的目的。

但是,对于一些异步操作(比如定时器回调函数、Promise 等)而言,状态更新操作并不是在 React 的调用栈中执行的,而是在异步上下文中执行的。由于 React 只能追踪自己的调用栈中发生的状态更新操作,因此它无法知道一些异步操作中进行的状态更新操作,也就无法将它们添加到更新队列中来进行批量处理。


6.useState函数更新和优化机制

在这里插入图片描述
问x会被渲染多少次???最后x的值是多少

渲染一次,x最后是11

为什么???

1次渲染就是挂在跟新队列中最后统一跟新:

在这里插入图片描述

那么使用flushSync能解决这个问题吗???

在这里插入图片描述
不可以,最后会渲染两次,但是x还是11,为什么???

useState自带了性能优化的机制:
每一次修改状态值的时候,会拿最新要修改的值和之前的状态值做比较「基于Object.is作比较」如果发现两次的值是一样的,则不会修改状态,也不会让视图更新「可以理解为︰类似于PureComponent,在shouldcomponentUpdate中做了浅比较和优化」

在这里插入图片描述
11是因为每一次循环都用的第一次跟新的那个闭包中的那个10!!!

其实这里也应该是一次,(这里的两次没有计算初始化那一次!!),那为什么这里会是两次,因为第一次可能浏览器还没有识别过来,在第二次flushsync的时候我们的x还没更新为11,打了个时间差所以进行了第二次渲染!!!


7.需求:让函数只更新一次,但是最后的结果是20

使用函数式
在这里插入图片描述
我们往更新队列里面放的时候放的是一个函数,最后更新处理的时候会把函数依次执行从而实现结果累计


8.初始化state的时候可以写成函数也可写成具体的值

看看这种写法

在这里插入图片描述
我们设置初始值只有第一次会触发设置!!!!但是后面每一次进去我们都会执行下面这一段逻辑

在这里插入图片描述
那么我们只需要第一次执行的话该怎么办???

初始值设置为函数就行!!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Same Symbol | 哇咔咔!!!盘点一下表达矩阵中重复基因的处理方法!~

1写在前面 医院天天叫我们填问卷&#xff0c;我真是不能理解。&#x1fae0; 动不动就问我们对医院的福利满意吗&#xff0c;对自己的收入满意吗&#xff0c;觉不觉得工作负荷太重了&#xff1f;&#xff1f;&#xff1f;&#x1f642; 我们满不满意&#xff0c;觉不觉得累&…

大学物理(上)-期末知识点结合习题复习(2)——运动的描述考点总结、质点运动学-牛顿运动定律

目录 运动的描述 期末考点 质点运动学 牛顿运动定律知识点 题1(牛顿第二定律) 题目描述 题解 题2 (圆周运动) 题目描述 题解 运动的描述 期末考点 1.速度和加速度的推导 平均速度平均速度反映的只是在一段时间内位移的变化&#xff0c;如果需要精准的地知道质点在…

chatgpt赋能python:Python如何判断奇偶数?

Python如何判断奇偶数&#xff1f; 作为一门功能强大且容易上手的编程语言&#xff0c;Python具有许多有用的工具和功能。其中之一就是判断奇偶数。在本文中&#xff0c;我们将介绍Python中判断奇偶数的不同方法。 H1&#xff1a;Python中的基本判断方法 Python中最基本的判…

(八)CSharp-泛型协变和逆变(3)

一、协变和逆变 可变性分为三种&#xff1a; 协变、逆变和不变。 协变和逆变&#xff1a; 为泛型接口和泛型委托添加了一个处理类型转换问题的扩展。 问题&#xff1a; 当两个类对象是继承与派生的关系时&#xff0c;由于编译器通过泛型实例化时没法确认它们之间的关系&…

(数组) 1991. 找到数组的中间位置 ——【Leetcode每日一题】

❓1991. 找到数组的中间位置 难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums &#xff0c;请你找到 最左边 的中间位置 middleIndex &#xff08;也就是所有可能中间位置下标最小的一个&#xff09;。 中间位置 middleIndex 是满足 nums[0] nums[1] ... num…

FTP协议详解

文章目录 1 FTP概述2 实验环境3 FTP详解3.1 文件传输过程3.2 报文格式3.3 数据连接3.4 主动模式3.5 被动模式3.6 匿名服务器 4 总结 1 FTP概述 FTP为File Transfer Protocol的缩写&#xff0c;即文件传输协议&#xff0c;是TCP/IP 协议族中的协议之一。FTP是一个用于在计算机网…

算法模板(3):搜索(3):图论提高

图论提高 最小生成树 &#xff08;1&#xff09;朴素版prim算法&#xff08; O ( n 2 ) O(n ^ 2) O(n2)&#xff09; 适用范围&#xff1a;稠密图易错&#xff1a;注意有向图还是无向图&#xff1b;注意有没有重边和负权边。从一个集合向外一个一个扩展&#xff0c;最开始只…

(文章复现)面向配电网韧性提升的移动储能预布局与动态调度策略(1)-灾前布局matlab代码

参考文献&#xff1a; [1]王月汉,刘文霞,姚齐,万海洋,何剑,熊雪君.面向配电网韧性提升的移动储能预布局与动态调度策略[J].电力系统自动化,2022,46(15):37-45. 1.基本原理 1. 1 目标函数 本文以最恶劣光伏出力场景下的移动储能配置成本与负荷削减成本最小为目标&#xff0c;建…

(数组) 724. 寻找数组的中心下标 ——【Leetcode每日一题】

❓724. 寻找数组的中心下标 难度&#xff1a;简单 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为…

2023-6-9

1.网络训练&#xff1a; 在训练前先要看看读取数据的时间&#xff08;常见的性能瓶颈&#xff09;2.import dis dis 是 Python 内置的一个模块&#xff0c;其全称为 “Disassembler for Python bytecode”&#xff0c;用于反汇编 Python 字节码。它可以将 Python 代码编译成字…

视频换天造物实践秒变科幻大片实践记录

视频换天造物实践秒变科幻大片实践记录&#xff0c;过程中遇到些坑&#xff0c;结果还是相当震撼 预装软件&#xff1a; matplotlib scikit-image scikit-learn scipy numpy torch torchvision opencv-python opencv-contrib-python 安装使用的时候可能碰上scikit-image 新版…

傅里叶级数简介

先看动图 将函数f(x) 用 sin(nx) cos(nx) 的形式表示出来的方式就是傅里叶级数 这里有几个使用条件 收敛性&#xff1a;符合迪力克雷收敛条件。简单理解为 f(x) 必须是一个丝滑的曲线。周期性&#xff1a; f(x) 必须是一个周期函数 还有一个基础条件&#xff0c;三角函数具…

element-plus布局排版问题总结(更新ing)

文章目录 el-container空隙修改app组件 el-container空隙 源码-更改了容器的显示&#xff0c;占满屏幕 <template><div class"common-layout"><el-container><el-header><el-row class"el-row1"><el-col :span"12&…

oppo r11 升级8.1系统 图文教程

Time: 2023年6月11日13:39:25 By:MemroyErHero 1 预留一定的空间,存放刷机包. 2 导入刷机包 r11.ozip 到手机上 3 手机文件管理器 打开 r11.ozip 文件 4 点击立即更新即可 5 重要的事情说三遍,刷机过程中 不能关机 不能断电 否则会变成砖头 重要的事情说三遍,刷机过程中 …

cmake 基本使用

目录 CMake都有什么? 使用cmake一般流程为&#xff1a; 1 生成构建系统 使用命令在build外编译代码: cmake基本语法 指定使用最低版本的cmake 指定项目名称 指定生成目标文件的名称 指定C版本 cmake配置文件使用 cmake配置文件生成头文件 版本号定义方法一: 版本号定…

软件测试正在面试银行的可以看下这些面试题

前言 最近呢有很多的小伙伴问我有没有什么软件测试的面试题&#xff0c;由于笔者之前一直在忙工作上的事情&#xff0c;没有时间整理面试题&#xff0c;刚好最近休息了一下&#xff0c;顺便整理了一些面试题&#xff0c;现在就把整理的面试题分享给大家&#xff0c;废话就不多说…

C 语言实现简单工厂模式

文章目录 1. 背景介绍2. 设计实现3. 运行测试4. 总结 1. 背景介绍 印象中&#xff0c;设计模式是由面向对象的语言(C、JAVA)才能完成的&#xff0c;而 C 语言是面向过程的语言&#xff0c;不能实现设计模式。但C 语言中有 函数指针、回调函数 等机制&#xff0c;使用这些机制便…

Java中线程的生命周期

Java中线程的生命周期 Java中线程的声明周期与os中线程的生命周期不太一样&#xff0c;java中线程有6个状态&#xff0c;见下&#xff1a; NEW: 初始状态&#xff0c;线程被创建出来但没有被调用 start() 。RUNNABLE: 运行状态&#xff0c;线程被调用了 start()等待运行的状态…

Elasticsearch:使用 Redis 让 Elasticsearch 更快

Elasticsearch 是一个强大的搜索引擎&#xff0c;可让你快速轻松地搜索大量数据。但是&#xff0c;随着数据量的增长&#xff0c;响应时间可能会变慢&#xff0c;尤其是对于复杂的查询。在本文中&#xff0c;我们将探讨如何使用 Redis 来加快 Elasticsearch 搜索响应时间。 Re…

【数据结构】常见排序算法——常见排序介绍、归并排序、各大排序复杂度和稳定性

文章目录 1.常见排序2.归并排序2.1归并排序基本思想2.2归并排序的实现2.3归并排序特性总结 3.各大排序复杂度和稳定性 1.常见排序 2.归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide andCon…