React中useState、useReducer与useRef
useState 详解
useState
是 React 函数组件中用于管理组件状态的 Hook。它提供了一种简洁的方式来在函数组件中添加状态,并在状态改变时触发组件重新渲染。以下是 useState
的详细解析:
一、基本概念
useState
是一个函数,它接收一个初始状态值作为参数,并返回一个数组。这个数组包含两个元素:当前状态值和一个用于更新该状态的函数。
二、语法与参数
const [state, setState] = useState(initialState);
- initialState:状态的初始值。可以是任何类型的值,包括数字、字符串、对象、数组等。
- 返回值:
useState
返回一个数组,数组的第一个元素是当前的状态值(state
),第二个元素是一个函数(setState
),用于更新状态。
三、工作原理
- 初始化状态:当组件首次渲染时,
useState
会使用传入的initialState
参数来初始化状态。 - 更新状态:当调用
setState
函数时,React 会将新的状态值与当前状态值进行比较。如果它们不相同,React 会重新渲染组件,并使用新的状态值。 - 合并更新:如果有多个
setState
调用在同一个事件循环中发生(例如在setTimeout
或Promise
的回调中),React 会将它们合并成一个更新,以减少不必要的渲染次数。
四、使用示例
以下是一个简单的计数器示例,展示了如何使用 useState
:
import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );
} export default Counter;
这个例子中,我们定义了一个 Counter
组件,它使用 useState
来管理一个名为 count
的状态。初始状态值设置为 0
。当用户点击按钮时,setCount
函数被调用,并将 count
的值增加 1
。由于状态发生了改变,React 会重新渲染组件,并显示更新后的计数值。
五、特性与优势
- 简洁性:
useState
提供了一种简洁的方式来在函数组件中添加状态。 - 响应式更新:当状态发生改变时,React 会自动重新渲染组件,以确保视图与状态保持一致。
- 函数式更新:
setState
函数可以接受一个函数作为参数,这个函数接收当前状态作为参数,并返回一个新的状态值。这种方式可以确保状态更新的原子性和一致性。 - 避免直接修改状态:React 推荐使用
setState
函数来更新状态,而不是直接修改状态值。这是因为直接修改状态可能会导致组件状态与视图不一致,从而引发不可预测的行为。
六、注意事项
- 不要将状态存储在局部变量中:状态应该始终通过
useState
Hook 来管理,而不是存储在局部变量中。否则,React 无法检测到状态的变化,也不会触发重新渲染。 - 避免在循环或条件语句中调用
useState
:useState
应该在组件的顶层调用,而不是在循环、条件语句或嵌套函数中调用。这是因为每次渲染时,useState
的调用顺序和参数都应该保持不变。 - 不要过度使用状态:虽然状态是 React 应用的核心,但过度使用状态可能会导致组件变得复杂和难以维护。在可能的情况下,优先考虑使用 React 的其他特性(如 props、context 或 hooks)来传递数据和逻辑。
总之,useState
是 React 中一个非常重要的 Hook,它提供了一种简洁而强大的方式来管理函数组件中的状态。通过合理使用 useState
,我们可以创建出响应式、可维护和可扩展的 React 应用
useRef详解
useRef
是 React 提供的一个 Hook,它在函数组件中非常有用,主要用于以下几种场景:
一、访问 DOM 元素
useRef
可以用来获取并操作 DOM 元素,这在某些场景下非常有用,比如设置焦点、测量元素尺寸、执行动画等。使用 useRef
访问 DOM 元素的步骤如下:
- 调用
useRef
并传入null
或一个初始值来创建一个 ref 对象。 - 将这个 ref 对象附加到 React 元素的
ref
属性上。 - 在组件的生命周期方法或事件处理函数中,通过
ref.current
访问对应的 DOM 元素。
例如:
import React, { useRef, useEffect } from 'react'; function MyComponent() { const myDivRef = useRef(null); useEffect(() => { // 访问 DOM 元素并设置其背景颜色 if (myDivRef.current) { myDivRef.current.style.backgroundColor = 'lightblue'; } }, []); return <div ref={myDivRef}>这个 div 的背景颜色是通过 useRef 设置的。</div>;
}
二、存储任意可变值
useRef
还可以用来存储任意可变值,并且这些值的改变不会触发组件的重新渲染。这对于存储不需要触发渲染的逻辑状态或缓存数据非常有用。
例如,我们可以使用 useRef
来存储一个计数器,并在按钮点击事件中更新它:
import React, { useRef } from 'react'; function Counter() { const countRef = useRef(0); const handleClick = () => { countRef.current += 1; console.log(`计数值现在是: ${countRef.current}`); }; return <div><button onClick={handleClick}>增加计数</button></div>;
}
在这个例子中,countRef.current
存储了一个计数器,并且每次点击按钮时都会增加它的值。但是,由于 useRef
的更新不会触发组件的重新渲染,所以即使计数器的值改变了,组件也不会重新渲染。
三、缓存上一次的值
在某些情况下,我们可能需要缓存上一次渲染时的值,以便在后续的逻辑中使用。useRef
可以很好地满足这个需求,因为它在组件的整个生命周期内保持不变。
例如,在 useEffect
中,我们可以使用 useRef
来缓存上一次的状态值,以便在比较前后状态的变化时使用:
import React, { useRef, useState, useEffect } from 'react'; function App() { const [count, setCount] = useState(0); const prevCount = useRef(); useEffect(() => { // 存储更新前的数值,不会触发组件渲染 prevCount.current = count; }, [count]); // 注意:这里将 count 作为依赖项,确保每次 count 变化时都执行这个 effect return ( <div> <button onClick={() => setCount(count + 1)}>增加计数</button> <div>更新后的值: {count}</div> <div>更新前的值: {prevCount.current}</div> </div> );
}
在这个例子中,prevCount.current
缓存了上一次渲染时的 count
值,这样我们就可以在组件中方便地比较前后状态的变化了。
四、在自定义 Hook 中共享数据
useRef
还可以在自定义 Hook 中用来共享数据,使得多个组件可以共享同一个数据源。这对于实现某些全局状态管理或跨组件通信的场景非常有用。
例如,我们可以创建一个自定义 Hook 来管理一个计数器,并在多个组件中使用它:
import { useRef } from 'react'; function useSharedCounter(initialValue) { const counterRef = useRef(initialValue); const increment = () => { counterRef.current += 1; }; const decrement = () => { counterRef.current -= 1; }; return { count: counterRef.current, increment, decrement, };
} // 在组件中使用这个自定义 Hook
function CounterComponent1() { const { count, increment } = useSharedCounter(0); return ( <div> <p>Counter in Component 1: {count}</p> <button onClick={increment}>Increment</button> </div> );
} function CounterComponent2() { const { count, decrement } = useSharedCounter(100); // 注意:这里传入了一个不同的初始值,但实际上在多个组件中共享的是同一个 ref 对象,所以初始值只会在第一个组件挂载时生效 return ( <div> <p>Counter in Component 2: {count}</p> <button onClick={decrement}>Decrement</button> </div> );
}
在这个例子中,useSharedCounter
是一个自定义 Hook,它使用 useRef
来存储计数器的值,并提供了增加和减少计数器值的方法。CounterComponent1
和 CounterComponent2
都使用了这个自定义 Hook,并且它们共享了同一个计数器。这意味着无论哪个组件修改了计数器的值,其他组件都会立即反映这个变化。
注意事项
useRef
创建的 ref 对象在组件的整个生命周期内保持不变。- 修改
ref.current
的值不会触发组件的重新渲染。 - 不要在渲染期间写入或读取
ref.current
,否则可能会使组件行为变得不可预测。 useRef
通常用于直接访问和操作 DOM 元素或存储不需要触发渲染的逻辑状态。
综上所述,useRef
在 React 函数组件中是一个非常有用的 Hook,它提供了多种功能来满足不同的需求。
useReducer详解
useReducer是React中的一个Hook,它提供了一种更复杂的状态管理机制,适用于那些状态逻辑较为复杂、包含多个子值的情况。以下是对useReducer的详细解析:
一、基本概念
useReducer基于一个叫做reducer的函数来更新状态。Reducer接收当前的状态和一个表示要进行的操作的动作对象(action),并返回新的状态。
二、语法与参数
useReducer的基本语法为:
const [state, dispatch] = useReducer(reducer, initialState);
- reducer:一个函数,接收两个参数:当前状态(state)和要执行的动作(action)。它根据动作来决定如何更新状态,并返回更新后的状态。
- initialState:状态的初始值。
- 返回值:useReducer返回一个数组,数组的第一个元素是当前状态(state),第二个元素是一个函数(dispatch),用于向reducer发送动作。
三、工作原理
- 定义reducer函数:根据传入的动作类型来更新状态。
- 使用useReducer Hook:并传入reducer函数和初始状态。
- 在组件中使用dispatch函数:来发送动作,从而触发状态的更新。
四、使用示例
以下是一个简单的计数器示例,展示了如何使用useReducer:
import React, { useReducer } from 'react'; // 定义reducer函数
function counterReducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); }
} // 定义初始状态
const initialState = { count: 0 }; // 定义计数器组件
function Counter() { const [state, dispatch] = useReducer(counterReducer, initialState); return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'increment' })}>+</button> </div> );
} export default Counter;
在这个例子中,我们定义了一个Counter
组件,它使用useReducer
来管理状态。state
包含一个count
属性,表示当前的计数值。dispatch
函数用于发送动作,根据不同的动作类型来更新状态。当用户点击增加按钮时,我们调用dispatch({ type: 'increment' })
,触发counterReducer
中的increment
动作,从而将计数器的值加一。同理,当用户点击减少按钮时,会触发decrement
动作,将计数器的值减一。
五、特性与优势
- 复杂状态逻辑:对于涉及多个状态变量和复杂的更新逻辑的场景,使用useReducer可以更好地组织和维护代码。
- 可预测的状态更新:useReducer使用函数来更新状态,这使得状态更新更加可预测和易于理解。
- 更好的代码可读性:通过将状态更新逻辑拆分为独立的函数(reducer),可以提高代码的可读性和可维护性。
- 性能优化:在某些情况下,useReducer可以提供更好的性能,尤其是在处理大量状态更新时。
- 支持合并更新操作:useReducer支持合并多个更新操作,从而减少不必要的重新渲染。
六、与其他状态管理方法的比较
与useState相比,useReducer更适合处理包含多个子值的复杂状态逻辑,或者当下一个状态依赖于之前的状态时。它让状态管理逻辑外部化和中心化,使得逻辑更易于理解和维护,尤其是在大型组件或复杂交互中。
与useContext相比,useReducer主要关注于组件内部的状态管理,而useContext则更适合在不同组件之间共享状态。在实际开发中,可以根据项目的规模和复杂度来选择合适的状态管理方法。
总之,useReducer是React中一个强大的状态管理Hook,它提供了一种更简洁、更易于理解的方式来处理复杂的状态逻辑。在需要处理复杂状态的情况下,推荐使用useReducer来管理应用状态。
比较
Hook | useState | useRef | useReducer |
---|---|---|---|
主要用途 | 在函数组件中添加可变状态,管理需要触发渲染更新的状态数据 | 创建对值或DOM元素的持久引用,存储不需要触发重新渲染的数据 | 管理组件状态,适用于复杂的状态逻辑和多种操作类型 |
返回值 | 一个数组,包含当前状态的值和一个更新状态的函数 | 一个ref对象,其current属性被初始化为传给useRef的参数 | 当前状态和dispatch函数 |
触发渲染 | 当状态更新时,组件会重新渲染 | 不会触发组件的重新渲染 | 状态的更新不直接触发渲染,但可通过触发状态更新间接导致渲染 |
数据类型 | 通常用于处理基本数据类型,或使用useReducer处理复杂数据结构 | 可以存储任何可变的值,包括对象和函数 | 可以管理复杂的数据结构,如对象和数组 |
状态更新方式 | 使用返回的更新函数来更新状态,可以是同步或异步更新 | 直接修改ref对象的current属性来更新值 | 通过dispatch函数发送action给reducer函数,reducer根据action更新状态 |
初始值设置 | 接受一个初始值参数,这个值在组件的生命周期内只会被设置一次 | 初始值可以是任何值,并且可以在组件的生命周期内随时更新(但不触发渲染) | 需要定义一个初始状态,作为useReducer的第一个参数 |
适用场景 | 适用于简单的状态管理,当状态之间没有复杂的依赖关系时 | 适用于直接访问和操作DOM元素,或存储不需要触发渲染的可变数据 | 适用于复杂的状态逻辑,当状态之间有复杂的依赖关系或需要进行多种操作时 |
内存管理 | 状态在组件卸载时会被清除 | ref对象的current属性中的数据会在组件卸载后依然存在,直到组件被垃圾回收 | 与useState类似,状态在组件卸载时会被清除(但reducer逻辑和初始状态定义不受影响) |
相关文章:

React中useState、useReducer与useRef
useState 详解 useState 是 React 函数组件中用于管理组件状态的 Hook。它提供了一种简洁的方式来在函数组件中添加状态,并在状态改变时触发组件重新渲染。以下是 useState 的详细解析: 一、基本概念 useState 是一个函数,它接收一个初始状…...

ReGCL Rethinking Message Passingin Graph Contrastive Learning
AAAI24 推荐指数: #paper/⭐ 总体说:利用梯度对对比正负样本加权的。个人觉得和与正负样本加权没有区别,读完之后不想做笔记了。...

ubutun安装ffmpeg
安装依赖 sudo apt-get install yasm sudo apt-get install libsdl1.2-dev sudo apt-get install libsdl2-dev 下载安装 tar -zxvf filename.gz ./configure --enable-shared --prefix/usr/local/ffmpeg make -j4 sudo make install 添加路径 路径/usr/local/ffmpeg…...

Vue的基本用法及模板语法
Vue.js使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue实例的数据。所有 Vue.js的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。 在底层的实现上,Vue将模板编译成虚拟 DOM 渲染函数。结合响应系…...

Redis数据库与GO完结篇:redis操作总结与GO使用redis
一、redis操作总结 由于写redis命令的时候有提示符,所以下表只给出命令名称 数据类型操作简介字符串GET, SET, MGET, MSET, SETEX,DEL最基本的数据类型,存储任意二进制数据,支持简单操作和原子计数。适合存储重复数据。哈希HSET, HGET, HDE…...

《重生到现代之从零开始的C语言生活》—— 动态内存管理
动态内存分配 我们在开辟内存的时候就是 int a 3;这样 但是这样开的空间大小是固定的,且大小不能调整 但是如果我们用动态内存开辟的话,就可以自己申请和释放空间、 malloc 是C语言提供的一个开辟动态空间的函数 void* malloc (size_t size);//si…...

四、Spring Boot集成Spring Security之登录登出业务逻辑
Spring Boot集成Spring Security之登录登出业务逻辑 一、概要说明二、基于内存的用户名密码1、默认用户名密码2、自定义用户名密码3、为方便测试添加测试接口TestController 三、登录登出重要概念介绍四、登录业务逻辑1、登录业务相关过滤器2、访问业务请求处理流程①、访问业务…...

pipe和pipefd
Linux 中 pipe 的详细介绍 在 Linux 中,pipe 是一个系统调用,用于创建一个管道,这是一种用于进程间通信(IPC)的机制。管道允许两个进程之间进行单向数据传输,通常是一个进程向管道写入数据,而另…...

无人机之飞控仿真技术篇
一、无人机飞控仿真技术的定义 无人机飞控仿真技术主要是指飞行控制系统仿真,它是以无人机的运动情况为研究对象,面向对象的复杂系统仿真。通过该技术,可以模拟无人机的飞行过程,评估飞行控制系统的性能,优化飞行参数&…...

Tetra Pak利乐触摸屏维修beijer北尔触摸屏维修E1151
TetraPak利乐包装机触摸显示屏维修,北尔全系列型号触摸屏修理 维修注意事项: 上电前,应检查负载是否接上或是否正确; 测量电压时,确认档位是否在电压档。要确认仪器仪表的量程应大于测试点的电压; 更换电…...

Python_网络编程(IP 端口 协议)
网络编程: 互联网时代,现在基本上所有的程序都是网络程序,很少有单机版的程序了。网络编程就是如何在程序中实现两台计算机的通信。Python语言中,提供了大量的内置模块和第三方模块用于支持各种网络访问,而且Python语言…...

Adobe Acrobat提示“3D数据解析错误”
原因:在使用Adobe Acrobat打开3D PDF时,因当前Adobe Acrobat的配置存在错误,所以无法打开 解决方法:重新生成配置 首先到达下面的路径C:\Users\你的用户名\AppData\Local\Adobe\Acrobat 下面为我的路径内容 若该路径下存在文件…...

红帽7—Mysql路由部署
MySQL Router 是一个对应用程序透明的InnoDB Cluster连接路由服务,提供负载均衡、应用连接故障转移和客户端路 由。 利用路由器的连接路由特性,用户可以编写应用程序来连接到路由器,并令路由器使用相应的路由策略 来处理连接,使其…...

LLM4Rec最新工作: 字节发布用于序列推荐的分层大模型HLLM
前几个月 Meta HSTU 点燃各大厂商对 LLM4Rec 的热情,一时间,探索推荐领域的 Scaling Law、实现推荐的 ChatGPT 时刻、取代传统推荐模型等一系列话题让人兴奋,然而理想有多丰满,现实就有多骨感,尚未有业界公开真正复刻 …...

怎么高效对接SaaS平台数据?
SaaS平台数据对接是指将一个或多个SaaS平台中的数据集成到其他应用或平台中的过程。在当前的数字化时代,企业越来越倾向于使用SaaS平台来管理他们的业务和数据。然而,这些数据通常散布在不同的SaaS平台中,这对于企业数据的整合和分析来说可能…...

Spark算子使用-Map,FlatMap,Filter,diatinct,groupBy,sortBy
目录 Map算子使用 FlatMap算子使用 Filter算子使用-数据过滤 Distinct算子使用-数据去重 groupBy算子使用-数据分组 sortBy算子使用-数据排序 Map算子使用 # map算子主要使用长场景,一个转化rdd中每个元素的数据类型,拼接rdd中的元素数据…...

CSS响应式布局
CSS 响应式布局也称自适应布局,是 Ethan Marcotte 在 2010 年 5 月份提出的一个概念,简单来讲就是一个网站能够兼容多个不同的终端(设备),而不是为每个终端做一个特定的版本。这个概念是为解决移动端浏览网页而诞生的。…...

AI大模型书籍丨掌握 LLM 和 RAG 技术,这本大模型小鸟书值得一看!
本指南旨在帮助数据科学家、机器学习工程师和机器学习/AI 架构师探索信息检索与 LLMs 的集成及其相互增强。特别聚焦于 LLM 和检索增强生成(RAG)技术在信息检索中的应用,通过引入外部数据库与 LLMs 的结合,提高检索系统的性能。 …...

Mysql和Oracle使用差异和主观感受
这两种常用的关系型数据库有何差异? 支持和社区 MySQL:有一个活跃的开源社区,用户可以获取大量的文档和支持。 Oracle:提供了专业的技术支持,但通常需要额外的费用。 易用性 MySQL:通常被认为是更易于学…...

【Java】—— File类与IO流:File类的实例化与常用方法
目录 1. java.io.File类的使用 1.1 概述 1.2 构造器 1.3 常用方法 1、获取文件和目录基本信息 2、列出目录的下一级 3、File类的重命名功能 4、判断功能的方法 5、创建、删除功能 1.4 练习 练习1: 练习2: 练习3: 1. java.io.Fil…...

C++设计模式——装饰器模式
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 什么是装饰器模式? 装饰器模式(Decorator Pattern)是一种结构型设计模式,允许你向一个现有的对象添加新的功能,同时又不改变其结构。这种模式通过创…...

C#使用ITextSharp生成PDF文件实例详解
许多项目开发中需要生成PDF, 常规办法使用官方提供的Microsoft.Office.Interop.Worddll插件,但是这种方法需要完全安装OFFICE,另外版本不一致还会出现很多错误。一般不推荐使用。 下面介绍这种巧妙的用法,定能事半功倍。 本文使用ITextSharp完成功能。 首先,通过NuGet…...

10.9QT对话框以及QT的事件机制处理
MouseMoveEvent(鼠标移动事件) widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 设置窗口为无边框,去掉标题栏等装饰this->setWi…...

SiLM266x系列SiLM2661高压电池组前端充/放电高边NFET驱动器 为电池系统保护提供可靠性和设计灵活性
SiLM2661产品概述: SiLM2661能够灵活的应对不同应用场景对锂电池进行监控和保护的需求,为电池系统保护提供可靠性和设计灵活性。是用于电池充电/放电系统控制的低功耗、高边 N 沟道 FET 驱动器,高边保护功能可避免系统的接地引脚断开连接&am…...

linux中sed命令详解
sed 是 Linux 中的一个流编辑器(stream editor),主要用于处理文本的编辑和转换。它可以从文件或标准输入读取内容,然后根据指定的模式和指令对数据进行处理,最后输出修改后的结果。它的强大之处在于可以通过脚本或命令…...

vue 模板语法
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML解析器解析。 文本插值 最基本的数据绑定形式是文本插值,它使用的是…...

bladex漏洞思路总结
Springblade框架介绍: SpringBlade是一个基于Spring Boot和Spring Cloud的微服务架构框架,它是由商业级项目升级优化而来的综合型项目。 0x1 前言 最近跟一些大佬学习了blade的漏洞,所以自己总结了一下,在渗透测试过程中&#x…...

解决SqlServer自增主键使用MybatisPlus批量插入报错问题
报错 SqlServer 表中主键设置为自增,会报以下错误。 org.springframework.jdbc.UncategorizedSQLException: Error getting generated key or setting result to parameter object. Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 必须执行该语句才能获…...

leetcode:反转字符串中的单词III
题目链接 string reverse(string s1) {string s2;string::reverse_iterator rit s1.rbegin();while (rit ! s1.rend()){s2 *rit;rit;}return s2; } class Solution { public:string reverseWords(string s) {string s1; int i 0; int j 0; int length s.length(); for (i …...

深度学习常见问题
1.YOLOV5和YOLOV8的区别 YOLOv5 和 YOLOv8 是两个版本的 YOLO(You Only Look Once)目标检测算法,它们在网络架构、性能优化、功能扩展等方面有显著的区别。YOLOv5 是 YOLO 系列的重要改进版本,而 YOLOv8 是最新的一次重大升级&am…...