react useState的初始化函数 初始化值为props时的同步问题 | setState函数的使用与异步更新

文章目录

  • react setState函数
    • useState()钩子创建state
      • 如何根据props更新state值
    • setState的参数是下一个状态state
    • setState的参数是更新函数function
    • setState异步与同步
      • 合成事件
      • setState 实现原理

react setState函数

useState()钩子创建state

const [state, setState] = useState(initialState)`

useState钩子函数向组件添加一个状态变量,该函数返回状态变量(只用于显示)与设置状态变量的setState函数。

useState的使用细节

  1. 调用修改函数修改state变量的值(state值发生变化)会触发组件的重新渲染,直接修改state变量不会触发组件的重新渲染。
  2. 组件重新渲染等同于函数重新执行,函数体的代码会再次调用。执行useState时会返回本次渲染的state值。setState()是由钩子函数useState()生成的,useState()会保证数组的每次渲染都会获取到相同的setState()
  3. React只在初次渲染时保存初始状态,后续渲染将其忽略。

参数的两种形式

  1. 初始化函数
    惰性初始化函数:如果useState的参数是一个生成数据的函数,则可以直接将这个函数本身传递(仅在初始化期间调用一次),不是函数()(这种情况每次渲染时调用此函数)
    如果函数需要传参,可以在useState之前使用useMemo缓存该函数的结果。

  2. 参数是值 - 如果是props需要注意同步的问题

如何根据props更新state值

为什么父组件传递的props改变,子组件state值不同步更新?

父组件传递给子组件的props发生改变,引发子组件的render,并没有执行子组件的constructor函数(子组件没有被卸载自然不会重新加载)只会重新render。父组件的props传递给子组件的state,子组件的state只会在第一次加载的时候被赋值,后续的父组件props变化并不会被赋值到子组件的state

解决办法

  1. 子组件使用useEffect()
    useEffect的执行时机组件挂载->state改变->DOM改变->绘制屏幕->useEffect,所以如果在useEffect里更新state的数据,会在屏幕绘制成功后又重新开始这个流程。

    尽量不使用,官方解释:https://zh-hans.react.dev/learn/you-might-not-need-an-effect

    // 方法1:子组件使用useEffect
    const [tags, setTags] = useState(() => props.tags);useEffect(() => {
    setTags(props.tags);
    }, [props.tags]);
    
  2. 父组件使用子组件时,绑定key。当父组件要传递的props改变时改变key值,将子组件认为是不同的子组件(引发子组件重新加载而不是单纯的渲染) –推荐

    <children userId={userId} key={userId}/>
    

setState的参数是下一个状态state

  1. 在 React 中,状态被认为是只读的,因此应该替换它而不是改变现有对象。
  2. state值是一个对象时,setState(state)修改的参数,由于是下一次渲染的state值(组件重渲染后useState返回state值),需要使用新的对象去替换已有对象
// user是对象,对象的地址没有发生变化,所以不会引起组件重新渲染
const [user, setUser] = useState({name:"ranran",age:18})
user.name = "xxx"; 
setUser(user);// 解决方案:将其拷贝给另一个新对象,修改新对象的属性
setUser({...user,name:"xxx"}) // 后面的name会覆盖前面的name

setState()去修改一个state时,并不表示修改当前的state,修改的是组件下一次渲染的state(下一次 渲染中useState返回的内容)

说明案例1
假设name的初始值是Taylor,点击按钮后触发handleClick 函数,修改name的值,打印发现是修改前的值Taylor

// 点击按钮修改名字
const handleClick = ()=> {setName('Robin'); // 下一次渲染中useState返回的内容console.log(name); // Still "Taylor"! 本次渲染useState返回的内容
}

说明案例2
假设 age42,这个处理函数三次调用 setAge(age + 1),点击一次后,age 将只会变为 43

const handleClick() => {setAge(age + 1); // setAge(42 + 1)setAge(age + 1); // setAge(42 + 1)setAge(age + 1); // setAge(42 + 1)
}

setState的参数是更新函数function

更新函数的参数是待定状态state

const handleClick = () => {setAge(a => a + 1); // setAge(42 => 43)setAge(a => a + 1); // setAge(43 => 44)setAge(a => a + 1); // setAge(44 => 45)
}

React 将更新函数放入 队列 中。然后,在下一次渲染期间(组件重渲染,useState返回state的过程中),它将按照相同的顺序调用:
1.a => a + 1 将接收 42 作为待定状态,并返回 43 作为下一个状态。
2.a => a + 1 将接收 43 作为待定状态,并返回 44 作为下一个状态。
3.a => a + 1 将接收 44 作为待定状态,并返回 45 作为下一个状态。

按照惯例,通常将待定状态参数命名为状态变量名称的第一个字母,如 age 为 a。然而,你也可以把它命名为 prevAge 或者其他你觉得更清楚的名称。

批量更新
多个顺序的setState不是同步地一个一个执行,会一个一个加入队列,然后最后一起执行。在异步setState更新队列时,存储的是合并状态(上述案例age的最终状态45)。因此前面设置的 key 值会被后面所覆盖,最终只会执行一次更新。

setState异步与同步

明确这里所说的同步和异步指的是 API 调用后更新 DOM 是同步还是异步的。
react18之后已经全部异步了

设计为异步更新的原因

  1. setState设计为异步,可以显著的提升性能:如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;最好的办法应该是获取到多个更新,之后进行批量更新(这也为什么在react可控范围类,希望setState是异步的)。
  2. 如果同步更新了state,但是还没有执行render函数,而且props依赖于state中的数据,那么stateprops不能保持同步;stateprops不能保持一致性,会在开发中产生很多的问题。

setState是同步还是异步呢?

  • 在React内部机制能检测到的地方,组件生命周期(除componentDidUpdate) 或React合成事件中,setState是异步。
  • 如果 setState 在原生 JavaScript 控制的范围被调用,setState是同步。

setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”。

合成事件

React 中为元素添加的事件被叫做合成事件(区分一下js的原生事件)

合成事件的好处

  • 屏蔽了浏览器之间关于事件处理的兼容性问题,为合成事件对象内部提供了统一的 API。
  • 提升性能, React 并不会将事件添加到真正的DOM 元素上,事件都被委托给 document

步骤说明
1.React 会在拥有事件的 DOM 对象身上添加一个 store 对象,在 store 对象中存储事件名称及事件处理函数,然后通过document 分发事件。
2.当事件被触发后,通过获取事件源对象,查看事件源对象中是否存在store对象,获取 store 对象中事件处理函数,执行事件处理函数。

核心实现
react 中所有的合成事件都会经过dispatchEventForLegacyPluginEventSystem()处理,其中通过设置全局变量isBatchingEventUpdates来标志当前的变化是否发生在React的可调度范围内。

/* 所有的事件都将经过此函数统一处理 */
function dispatchEventForLegacyPluginEventSystem(){// handleTopLevel 事件处理函数 batchedEventUpdates 批量更新batchedEventUpdates(handleTopLevel, bookKeeping);
}
function batchedEventUpdates(fn,a){/* 开启批量更新  */isBatchingEventUpdates = true;try {/* 这里执行了的事件处理函数, 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */return batchedEventUpdatesImpl(fn, a, b);} finally {/* 完成一次事件,批量更新  */isBatchingEventUpdates = false;}
}

setState 实现原理

  1. 当 setState 方法被调用后,将状态传递给组件更新器,让组件更新器将状态临时存储起来。每个组件都会有自己的组件更新器,当需要更新组件时调用组件更新器。
  2. 状态临时保存完成后判断当前是否为批量更新模式,如果是,将组件更新器添加到更新队列中;如果不是,直接更新组件。
    当触发合成事件时, 在事件处理函数执行之前,会先将批量更新模式设置为 true,然后执行事件处理函数收集状态。当事件处理函数执行完成后,执行批量更新操作(从更新队列中获取组件更新器并调用)。组件更新器调用完成后再将批量更新模式设置为 false
  3. 更新组件时,先判断是否有状态需要更新,如果有就先计算最新状态,将得出的最新状态重新设置给组件(useState的返回值?)。
    计算状态时,如果状态是函数类型,调用函数传入当前状态,返回最新状态。如果状态是对象类型,使用对象状态覆盖原有状态。
  4. 组件状态计算完成后,通过调用组件内部的 render 方法获取新的 VirtualDOM,再通过 DOM 对象获取旧的虚拟 DOM,然后调用 diff 方法进行比对,对比完成后将差异更新到真实 DOM 对象中。

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

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

相关文章

大数据做「AI大模型」数据清洗调优基础篇

关于本文 近期一直在协助做AI大模型数据清洗调优的工作&#xff0c;主要就是使用大数据计算引擎Spark做一些原始数据的清洗工作&#xff0c;整体数据量大约6PB-8PB之间&#xff0c;那么对于整个大数据量的处理性能将是一个重大的挑战&#xff0c;关于具体的调优参数配置项暂时不…

【论文阅读+复现】AniPortrait: Audio-Driven Synthesis of Photorealistic Portrait Animation

AniPortrait&#xff1a;音频驱动的逼真肖像动画合成。 code&#xff1a;Zejun-Yang/AniPortrait: AniPortrait: Audio-Driven Synthesis of Photorealistic Portrait Animation (github.com) paper&#xff1a;[2403.17694] AniPortrait: Audio-Driven Synthesis of Photoreal…

Soot入门学习笔记

Soot 适合参考的文档和教程如下&#xff1a; 北京大学软件分析技术 南京大学软件分析 Tutorials for soot McGill University 198:515 (vt.edu) 比较好的笔记资料&#xff1a; 南京大学《软件分析》课程笔记 比较好的入门作业或者案例&#xff1a; CSCE710 Assignmen…

MySQL8 中文参考翻译完成

MySQL8 中文参考前言和法律声明第一章 一般信息1.1 关于本手册1.2 MySQL 数据库管理系统概述1.2.1 什么是 MySQL&#xff1f;1.2.2 MySQL 的主要特性1.2.3 MySQL 的历史1.3 MySQL 8.0 中的新功能1.4 MySQL 8.0 中新增、弃用或删除的服务器和状态变量和选项1.5 如何报告错误或问…

产品说明书二维码生成的优势:数字化时代的智能选择

随着二维码技术的不断发展&#xff0c;越来越多的企业开始选择使用二维码来展示产品使用说明&#xff0c;以取代传统的纸质说明书。这一趋势不仅符合数字化时代的潮流&#xff0c;更为消费者提供了更便捷、更智能的产品使用体验。以下是产品说明书二维码生成的优势&#xff1a;…

Android WebView的使用与后退键处理

目录 前言首先&#xff0c;我们需要在布局文件中添加webView组件在Activity中获取webView实例&#xff0c;并加载网页内容 前言 webView是Android中常用的组件之一&#xff0c;用于展示网页内容。它可以加载HTML文件、URL链接等网页内容&#xff0c;并提供交互功能。在使用webV…

C#_泛型_委托

文章目录 泛型泛型的使用泛型的约束委托委托的实例化多播委托委托的调用内置委托类型委托练习泛型委托Lambda表达式(进阶)上期习题答案本期习题 泛型 泛型&#xff08;Generic&#xff09; 是一种规范&#xff0c;它允许我们使用占位符来定义类和方法&#xff0c;编译器会在编…

Linux进程概念(下)

1. 进程的状态 为了弄明白正在运行的进程是什么意思&#xff0c;我们需要知道进程的不同状态。一个进程可以有几个状态&#xff08;在Linux内核里&#xff0c;进程有时候也叫做任务&#xff09;。 下面的状态在kernel源代码里定义&#xff1a; /* * The task state array is…

数对 离散化BIT

先把公式变个形&#xff0c;然后直接BIT 枚举右端点查询左端点累加答案 离散化好题&#xff0c;注意BIT写的时候右端点的范围是离散化区间的大小 #include<bits/stdc.h> using namespace std; #define int long long using ll long long; using pii pair<int,int&…

【ZZULIOJ】1011: 圆柱体表面积(Java)

目录 题目描述 输入 输出 样例输入 Copy 样例输出 Copy code 题目描述 输入圆柱体的底面半径r和高h&#xff0c;计算圆柱体的表面积并输出到屏幕上。要求定义圆周率为如下宏常量 #define PI 3.14159 输入 输入两个实数&#xff0c;为圆柱体的底面半径r和高h。 输出 输…

国内好用的chatGPT和AI绘图工具

分享一个比较好用的AI 分享一个比较好用的AI&#xff0c;只是需要开通会员&#xff0c;目前官网的价格是&#xff1a;298&#xff0c;开通之后可以使用chatgpt4、AI绘画、图片融合等等&#xff01;不开通的话是可以免费使用15次的&#xff0c;下面是一些介绍图片&#xff01;链…

UE5数字孪生系列笔记(三)

C创建Pawn类玩家 创建一个GameMode蓝图用来加载我们自定义的游戏Mode新建一个Pawn的C&#xff0c;MyCharacter类作为玩家&#xff0c;新建一个相机组件与相机臂组件&#xff0c;box组件作为根组件 // Fill out your copyright notice in the Description page of Project Set…

【python】网络编程socket TCP UDP

文章目录 socket常用方法TCP客户端服务器UDP客户端服务器网络编程就是实现两台计算机的通信 互联网协议族 即通用标准协议,任何私有网络只要支持这个协议,就可以接入互联网。 socket socke模块的socket()函数 import socketsock = socket.socket(Address Family,

Solidity Uniswap V2 Router swapTokensForExactTokens

最初的router合约实现了许多不同的交换方式。我们不会实现所有的方式&#xff0c;但我想向大家展示如何实现倒置交换&#xff1a;用未知量的输入Token交换精确量的输出代币。这是一个有趣的用例&#xff0c;可能并不常用&#xff0c;但仍有可能实现。 GitHub - XuHugo/solidit…

修改Docker Gitlab root 的密码

一、进入git docker 容器 docker exec -it [容器ID或名称] /bin/bash 二、查找并修改账号 user User.find_by(username: ‘root’) user.password ‘root********’ user.password_confirmation ‘root********’ user.save! 三、重启生效 附&#xff1a;第一次&#xff…

Golang实战:深入hash/crc64标准库的应用与技巧

Golang实战&#xff1a;深入hash/crc64标准库的应用与技巧 引言hash/crc64简介基本原理核心功能 环境准备安装Golang创建一个新的Golang项目引入hash/crc64包测试环境配置 hash/crc64的基本使用计算字符串的CRC64校验和计算文件的CRC64校验和 高级技巧与应用数据流和分块处理网…

Jmeter 配置说明之线程组

一、线程组介绍&#xff1a; 线程组元件是任何一个测试计划的开始点。在一个测试计划中的所有元件都必须在某个线程组下。所有的任务都是基于线程组&#xff1a; 通俗理解&#xff1a; 线程组&#xff1a;就是一个线程组&#xff0c;里面有若干个请求&#xff1b; 线程&am…

vue3.0 + ts + eslint报错:error Parsing error: ‘>‘ expected

eslint报错 这里加上对应的 eslint配置即可&#xff1a; parser: vue-eslint-parser, parserOptions: {parser: "typescript-eslint/parser",ecmaVersion: 2020,sourceType: module, }具体如下&#xff1a; module.exports {parser: vue-eslint-parser,parserOpti…

企业数据定制:营销/画像/大数据模型

随着信息技术的发展&#xff0c;数据已经成为企业发展的重要基石。现在信息广泛地分布在网络&#xff0c;人工收集对于企业运作来说效率太低了。 因此&#xff0c;很多企业选择将数据采集工作交给专业的团队来做&#xff0c;选择直接外包或者购买数据。 不管是哪种情况&#…

​马来语翻译中文去哪比较好?

据了解&#xff0c;马来语是马来西亚、文莱的官方语言&#xff0c;也是新加坡的官方语言之一&#xff0c;马来语跟印尼语是同一种语言&#xff0c;它在整个东南亚有着极大的影响力。如今在国内市场上&#xff0c;马来语翻译的需求也是供不应求&#xff0c;那么&#xff0c;如何…