React 18 state 如同一张快照

参考文章

state 如同一张快照

也许 state 变量看起来和一般的可读写的 JavaScript 变量类似。但 state 在其表现出的特性上更像是一张快照。设置它不会更改已有的 state 变量,但会触发重新渲染。

设置 state 会触发渲染

可能会认为用户界面会直接对点击之类的用户输入做出响应并发生变化。在 React 中,它的工作方式与这种思维模型略有不同。这意味着要使界面对输入做出反应,需要设置其 state。

在这个例子中,当按下 “send” 时,setIsSent(true) 会通知 React 重新渲染 UI:

import { useState } from 'react';export default function Form() {const [isSent, setIsSent] = useState(false);const [message, setMessage] = useState('Hi!');if (isSent) {return <h1>Your { message } is on its way!</h1>}return (<form onSubmit={(e) => {e.preventDefault();setIsSent(true);sendMessage(message);}}><textareaplaceholder="Message"value={message}onChange={e => setMessage(e.target.value)}/><button type="submit">Send</button></form>);
}function sendMessage(message) {// ...
}

当单击按钮时会发生以下情况:

  1. 执行 onSubmit 事件处理函数。
  2. setIsSent(true)isSent 设置为 true 并排列一个新的渲染。
  3. React 根据新的 isSent 值重新渲染组件。

仔细看看 state 和渲染之间的关系。

渲染会及时生成一张快照

“正在渲染” 就意味着 React 正在调用组件——一个函数。从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。

与照片或电影画面不同,返回的 UI “快照”是可交互的。它其中包括类似事件处理函数的逻辑,这些逻辑用于指定如何对输入作出响应。React 随后会更新屏幕来匹配这张快照,并绑定事件处理函数。因此,按下按钮就会触发JSX 中的点击事件处理函数。

当 React 重新渲染一个组件时:

  1. React 会再次调用函数
  2. 函数会返回新的 JSX 快照
  3. React 会更新界面来匹配返回的快照

React 执行函数 ——> 计算快照 ——> 更新 DOM 树

作为一个组件的记忆(状态),state 不同于在函数返回之后就会消失的普通变量。state 实际上“活”在 React 本身中——就像被摆在一个架子上!——位于函数之外。当 React 调用组件时,它会为特定的那一次渲染提供一张 state 快照。组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值 被计算出来的!

React 收到 setUpdate 通知 ——> React 更新 state 的值 ——> React 向组件内传入一张 state 的快照

这里有个展示其运行原理的小例子。在这个例子中,可能会以为点击“+3”按钮会调用 setNumber(number + 1) 三次从而使计数器递增三次。

看看点击“+3”按钮时会发生什么:

import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 1);setNumber(number + 1);setNumber(number + 1);}}>+3</button></>)
}

请注意,每次点击只会让 number 递增一次!

设置 state 只会为下一次渲染变更 state 的值。在第一次渲染期间,number0。这也就解释了为什么在 那次渲染中的 onClick 处理函数中,即便在调用了 setNumber(number + 1) 之后,number 的值也仍然是 0

<button onClick={() => {setNumber(number + 1);setNumber(number + 1);setNumber(number + 1);
}}>+3</button>

以下是这个按钮的点击事件处理函数通知 React 要做的事情:

  1. setNumber(number + 1) : number0,所以 setNumber(0 + 1)
    • React 准备在下一次渲染时将 number 更改为 1
  2. setNumber(number + 1) : number0,所以 setNumber(0 + 1)
    • React 准备在下一次渲染时将 number 更改为 1
  3. setNumber(number + 1) : number0,所以 setNumber(0 + 1)
    • React 准备在下一次渲染时将 number 更改为 1

尽管调用了三次 setNumber(number + 1),但在 这次渲染的 事件处理函数中 number 会一直是 0,所以会三次将 state 设置成 1。这就是为什么在事件处理函数执行完以后,React 重新渲染的组件中的 number 等于 1 而不是 3

还可以通过在心里把 state 变量替换成它们在你代码中的值来想象这个过程。由于 这次渲染 中的 state 变量 number0,其事件处理函数看起来会像这样:

<button onClick={() => {setNumber(0 + 1);setNumber(0 + 1);setNumber(0 + 1);
}}>+3</button>

对于下一次渲染来说,number1,因此 那次渲染中的 点击事件处理函数看起来会像这样:

<button onClick={() => {setNumber(1 + 1);setNumber(1 + 1);setNumber(1 + 1);
}}>+3</button>

这就是为什么再次点击按钮会将计数器设置为 2,下次点击时会设为 3,依此类推。

随时间变化的 state

试着猜猜点击这个按钮会发出什么警告:

import { useState } from 'react';export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 5);alert(number);}}>+5</button></>)
}

如果使用之前替换的方法,就能猜到这个提示框将会显示 “0”:

setNumber(0 + 5);
alert(0);

但如果在这个提示框上加上一个定时器, 使得它在组件重新渲染 之后 才触发,又会怎样呢?是会显示 “0” 还是 “5” ?

export default function Counter() {const [number, setNumber] = useState(0);return (<><h1>{number}</h1><button onClick={() => {setNumber(number + 5);setTimeout(() => {alert(number);}, 3000);}}>+5</button></>)
}

如果使用替代法,就能看到被传入提示框的 state “快照”。答案是:显示 “0”。

setNumber(0 + 5);
setTimeout(() => {alert(0);
}, 3000);

到提示框运行时,React 中存储的 state 可能已经发生了更改,但它是使用用户与之交互时状态的快照进行调度的!

一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。在 那次渲染的 onClick 内部,number 的值即使在调用 setNumber(number + 5) 之后也还是 0。它的值在 React 通过调用组件“获取 UI 的快照”时就被“固定”了。

这里有个示例能够说明上述特性会使事件处理函数更不容易出现计时错误。下面是一个会在五秒延迟之后发送一条消息的表单。想象以下场景:

  1. 按下“发送”按钮,向 Alice 发送“你好”。
  2. 在五秒延迟结束之前,将“To”字段的值更改为“Bob”。

你觉得 alert 会显示什么?它是会显示“你向 Alice 说了你好“还是会显示“你向 Bob 说了你好”?

答案是:会显示“你向 Alice 说了你好“

import { useState } from 'react';export default function Form() {const [to, setTo] = useState('Alice');const [message, setMessage] = useState('Hello');function handleSubmit(e) {e.preventDefault();setTimeout(() => {alert(`You said ${message} to ${to}`);}, 5000);}return (<form onSubmit={handleSubmit}><label>To:{' '}<selectvalue={to}onChange={e => setTo(e.target.value)}><option value="Alice">Alice</option><option value="Bob">Bob</option></select></label><textareaplaceholder="Message"value={message}onChange={e => setMessage(e.target.value)}/><button type="submit">Send</button></form>);
}

React 会使 state 的值始终”固定“在一次渲染的各个事件处理函数内部。 无需担心代码运行时 state 是否发生了变化。

但是,万一想在重新渲染之前读取最新的 state 怎么办?应该使用 状态更新函数。

摘要

  • 设置 state 请求一次新的渲染。
  • React 将 state 存储在组件之外,就像在架子上一样。
  • 当调用 useState 时,React 会为提供该次渲染 的一张 state 快照。
  • 变量和事件处理函数不会在重渲染中“存活”。每个渲染都有自己的事件处理函数。
  • 每个渲染(以及其中的函数)始终“看到”的是 React 提供给这个 渲染的 state 快照。
  • 可以在心中替换事件处理函数中的 state,类似于替换渲染的 JSX。
  • 过去创建的事件处理函数拥有的是创建它们的那次渲染中的 state 值。

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

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

相关文章

【微信小程序创作之路】- 小程序远程数据请求、获取个人信息

【微信小程序创作之路】- 小程序远程数据请求、获取个人信息 第七章 小程序远程数据请求、获取个人信息 文章目录 【微信小程序创作之路】- 小程序远程数据请求、获取个人信息前言一、远程数据请求1.本地环境2.正式域名 二、获取用户个人信息1.展示当前用户的身份信息2.获取用…

Ubuntu安装docker

安装 要是之前安装过&#xff0c;可以进行卸载然后再安装&#xff0c;旧版本的 Docker 的名称为docker、docker.io或 docker-engine。安装新版本之前卸载任何此类旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc使用存储库安装 在新主机上首次安…

kafka-保证数据不重复-生产者开启幂等性和事务的作用?

1. 生产者开启幂等性为什么能去重&#xff1f; 1.1 场景 适用于消息在写入到服务器日志后&#xff0c;由于网络故障&#xff0c;生产者没有及时收到服务端的ACK消息&#xff0c;生产者误以为消息没有持久化到服务端&#xff0c;导致生产者重复发送该消息&#xff0c;造成了消…

runit-docker中管理多个服务

runit-docker中管理多个服务 介绍Runit, systemctl和supervisor是三种不同的服务管理工具区别runit优点程序构成快速开始runit实现服务退出执行指定操作runit监管服务打印日志到syslogrunit监管服务后台运行runit监管服务一些错误总结 介绍 runit 是一个轻量级的、稳定的、跨平…

【error 踩坑】AttributeError: ‘DataFrame‘ object has no attribute ‘iteritems‘

新建了虚拟环境py38,安装pandas pip install pandas接着使用spark向hive表中写数据 发现出现了error: AttributeError: DataFrame object has no attribute iteritemsgoogle后找到答案&#xff1a; Looks like iteritems was removed in pandas 2.0 - try using pandas versi…

Golang交叉编译

Golang交叉编译主要依赖几个参数&#xff1a;GOOS、GOARCH和CGO_ENABLED。 参数作用GOOS交叉编译的OSGOARCH交叉编译的CPU架构CGO_ENABLED设置为0时&#xff0c;编译出的二进制是静态的&#xff0c;也就是说没有外部的依赖。 编译在arm64的linux环境运行的程序&#xff1a; …

econml双机器学习实现连续干预和预测

连续干预 在这个示例中&#xff0c;我们使用LinearDML模型&#xff0c;使用随机森林回归模型来估计因果效应。我们首先模拟数据&#xff0c;然后模型&#xff0c;并使用方法来effect创建不同干预值下的效应&#xff08;Conditional Average Treatment Effect&#xff0c;CATE&…

【深度学习MOT videos detect】Detect to Track and Track to Detect

论文&#xff1a;https://arxiv.org/abs/1710.03958 代码&#xff1a;https://github.com/feichtenhofer/Detect-Track 文章目录 Abstract1. Introduction2. Related work后面翻译略 Abstract 近期用于在视频中高精度检测和跟踪目标类别的方法越来越复杂&#xff0c;每年都变得…

【Express.js】使用zod检验

使用zod检验 上一节我们介绍了 express-validator&#xff0c;本节我们介绍一个更通用的检验工具 Zod What’s Zod.js? 写前端的同学可能知道Zod&#xff0c;我们在提交表单前需要对数据初步检查&#xff0c;Zod是一个很棒的工具。前端可以偷懒&#xff0c;但后端不能偷懒&…

Camunda 7.x 系列【10】使用 Java API 运行流程实例

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 2.7.9 本系列Camunda 版本 7.19.0 源码地址:https://gitee.com/pearl-organization/camunda-study-demo 文章目录 1. 前言2. 运行流程实例2.1 查询流程定义2.2 启动流程2.3 任务查询2.4 审批3. 数据表1. 前言…

vue3—SCSS的安装、配置与使用

SCSS 安装 使用npm安装scss&#xff1a; npm install sass sass-loader --save-dev 配置 配置到全局 &#x1f31f;附赠代码&#x1f31f; css: {preprocessorOptions: {scss: {additionalData:import "./src/Function/Easy_I_Function/Echarts/ToSeeEcharts/utill.…

Spring Boot Admin 环境搭建与基本使用

Spring Boot Admin 环境搭建与基本使用 一、Spring Boot Admin是什么二、提供了那些功能三、 使用Spring Boot Admin3.1搭建Spring Boot Admin服务pom文件yml配置文件启动类启动admin服务效果 3.2 common-apipom文件feignhystrix 3.3服务消费者pom文件yml配置文件启动类control…

前端面试的性能优化部分(6)每天10个小知识点

目录 系列文章目录前端面试的性能优化部分&#xff08;1&#xff09;每天10个小知识点前端面试的性能优化部分&#xff08;2&#xff09;每天10个小知识点前端面试的性能优化部分&#xff08;3&#xff09;每天10个小知识点前端面试的性能优化部分&#xff08;4&#xff09;每天…

Simulation 线性静力分析流程

有限元仿真分析软件有很多&#xff0c;但是分析的流程却是大同小异&#xff0c;今天给大家分享的是Simulation的线性静力分析流程。 1.构思分析方案。 确定研究对象&#xff0c;研究的方法、验证方案等等。听起来比较空洞&#xff0c;实践过程中我建议首先需要把目标和有限元分…

HDFS中的Trash垃圾桶回收机制

Trash垃圾桶回收机制 文件系统垃圾桶背景功能概述Trash Checkpoint Trash功能开启关闭HDFS集群修改core-site.xml删除文件到trash删除文件跳过从trash中恢复文件清空trash 文件系统垃圾桶背景 回收站&#xff08;垃圾桶&#xff09;是windows操作系统里的一个系统文件夹&#…

C++学习笔记总结练习:并发编程与多线程

并发编程与多线程 1. 基础知识 C多线程 线程&#xff1a;线程是操作系统能够进行CPU调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;一个进程可包含单个或者多个线程。可以用多个线程去完成一个任务&#xff0c;也可以用多个进程去完成一个任务&#xff0c;它们的…

一起学SF框架系列7.1-spring-AOP-基础知识

AOP(Aspect-oriented Programming-面向切面编程&#xff09;是一种编程模式&#xff0c;是对OOP(Object-oriented Programming-面向对象编程&#xff09;一种有益补充。在OOP中&#xff0c;万事万物都是独立的对象&#xff0c;对象相互耦合关系是基于业务进行的&#xff1b;但在…

python获取类名__qualname__,解决django接口ObjectDoesNotExist异常寻找model的问题

在django项目中&#xff0c;经常使用类似Model.objects.get(id1)的方法取对象&#xff0c;默认抛出的异常是ObjectDoesNotExist类型&#xff0c;通过try catch可以把异常捕获&#xff0c;获取的异常是Model.DoesNotExist类型&#xff0c; 要获知其类名&#xff0c;可以使用__na…

目标识别模型两种部署形态图

目标检测预训练模型基于新数据进行微调&#xff08;训练&#xff09;之后&#xff0c;得到一个权重文件。 在日常工业、车载等需求环境下&#xff0c;需要在嵌入式移动端的软件系统中调用该模型文件进行推断测试&#xff0c;软件系统追求性能经常使用C/C进行编码实现&#xff…

第十一次CCF计算机软件能力认证

第一题&#xff1a;打酱油 小明带着 N 元钱去买酱油。 酱油 10 块钱一瓶&#xff0c;商家进行促销&#xff0c;每买 3 瓶送 1 瓶&#xff0c;或者每买 5 瓶送 2 瓶。 请问小明最多可以得到多少瓶酱油。 输入格式 输入的第一行包含一个整数 N&#xff0c;表示小明可用于买酱油的…