React的State和setState

news2025/1/16 2:53:28

如何确地使用 State

  1. 不要直接修改 State.
  2. 修改State应该使用 setState():
  3. 构造函数是唯一可以给 this.state 赋值的地方

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件

我们可以在我们的自定义组件中添加私有的State
jcode

class Clock extends React.Component {
  constructor(props) {   
      super(props);    
      this.state = {date: new Date()}; 
      }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);

那我们如果要更新State要怎么去更新呢?

答案就是setState,而且必须使用setState。

为什么必须使用setState呢?

在开发中我们并不能直接通过修改state的值来让界面发生更新。因为我们修改了state之后,是希望React根据最新的State来重新渲染界面,但是这种方式的修改React并不知道数据发生了变化。React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化;所以我们必须通过setState来告知React数据已经发生了变化。React才会更新我们对应的数据以及更新页面的渲染

那你可能会问:为什么我们在组件中并没有实现setState的方法,为什么可以调用呢?

原因很简单,setState方法是从Component中继承过来的。

关于setState的源码截图:

image.png

setState的更新是异步的!

异步的情况

我们做个实验,我们在更新完的函数里面直接console.log我们的state的值。可以发现我们的setState是异步的

代码:

jcode

import React, { useState, useCallback } from 'react';
import ReactDom from 'react-dom';

class Test extends React.Component {
  constructor(props) {   
      super(props);    
      this.state = {message:"我是初始的State"  }; 
      this.handleClick = this.handleClick.bind(this)
      }
      

     handleClick(){
       this.setState({message:"我是更新后的结果" },);
       console.log(this.state.message)
     } 
     
      
  render() {
    return (
      <button onClick={this.handleClick}>更新</button>
    );
  }
}
ReactDom.render(<Test />, document.getElementById('app'));
      

点击按钮之后的打印结果,

image.png

我们可以很明显的发现打印出来的值仍然是旧值,未修改前的值。所以虽然我们setState修改了我们的State值,迪但是在代码运作中,他并不是立即同步去进行修改的。

为什么setState设计为异步呢?

  • setState设计为异步,可以显著的提升性能

    • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的。
    • 最好的办法应该是获取到多个更新,之后进行批量更新。
  • 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步

    • state和props不能保持一致性,会在开发中产生很多的问题。

github上也有很多讨论,React核心成员(Redux的作者)Dan Abramov也有对应的回复GitHub回应链接这里就不细究了。

如何获取异步的结果

方式一:setState的回调

  • setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;
  • 格式如下:setState(partialState, callback)

jcode

import React, { useState, useCallback } from 'react';
import ReactDom from 'react-dom';

class Test extends React.Component {
  constructor(props) {   
      super(props);    
      this.state = {message:"我是初始的State"  }; 
      this.handleClick = this.handleClick.bind(this)
      }
      
     handleClick(){
       this.setState({message:"我是更新后的结果" },() =>{
           //拿到的是更新后的值
          console.log(this.state.message)
       });
       //拿到的是更新前的值
       console.log(this.state.message)
     } 
     
  render() {
    return (
      <button onClick={this.handleClick}>更新</button>
    );
  }
}

ReactDom.render(<Test />, document.getElementById('app'));
      

点击按钮之后的打印结果:

image.png

可以看见我们拿到的值是最新的值

方式二: componentDidUpdate()生命周期函数

jcode

import React, { useState, useCallback } from 'react';
import ReactDom from 'react-dom';

class Test extends React.Component {
  constructor(props) {   
      super(props);    
      this.state = {message:"我是初始的State"  }; 
      this.handleClick = this.handleClick.bind(this)
      }
      
     handleClick(){
       this.setState({message:"我是更新后的结果" });
     } 

     componentDidUpdate(){
       console.log(this.state.message)
     }
      
  render() {
    return (
      <button onClick={this.handleClick}>更新</button>
    );
  }
}

ReactDom.render(<Test />, document.getElementById('app'));
      

点击按钮之后的打印结果:

image.png

可以看见我们拿到的值是最新的值

方式三:箭头函数

因为 this.props 和 this.state 可能会异步更新,所以我们不能依赖他们的值来更新下一个状态。可能会出现bug

那如果我们一定要同时使用this.propsthis.state来更新我们的数据怎么办呢?

可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 作为第二个参数

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }

  handleClick = () => {
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + props.increment
      };
    });
  };

  render() {
    return (
      <div>
        <p>Counter: {this.state.counter}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

非异步的情况

setState一定是异步吗?

其实分成两种情况:

  1. 在组件生命周期或React合成事件中,setState是异步
  2. 在setTimeout或者原生dom事件中,setState是同步

在setTimeout中更新

我们可以做个试验,在setTimeout里面进行更新,更新完之后直接打印我们的state值。可以看见我们的setState是同步的

import React, { useState, useCallback } from 'react';
import ReactDom from 'react-dom';

class Test extends React.Component {
  constructor(props) {   
      super(props);    
      this.state = {message:"我是初始的State"  }; 
      this.handleClick = this.handleClick.bind(this)
      }
    
     handleClick(){
       setTimeout(() =>{
         this.setState({
           message:"我是更新后的结果"
           })
          // 更新后里面打印结果
            console.log(this.state.message)
       },0)
     }

  render() {
    return (
      <button onClick={this.handleClick}>更新</button>
    );
  }
}

ReactDom.render(<Test />, document.getElementById('app'));
      

点击按钮之后的打印结果:

image.png

可以看见我们拿到的值是最新的值

在原生DOM事件中更新

我们可以做个试验,在原生DOM事件中进行更新,更新完之后直接打印我们的state值。可以看见我们的setState是同步的

componentDidMount(){
  const btnEl =document.getElementById("btn");
  btnEl.addEventListener('click',() =>{
    this.setState({
      message:"我是更新后的结果"
    })
    console.loh(this.state.message)
  })
}

点击按钮之后的打印结果:

image.png

可以看见我们拿到的是最新的值而且是同步更新的

State 的更新会被合并

在 React 中,setState() 的更新操作可能会被合并。当你多次调用 setState() 时,React 可能会将多个更新操作合并为单个更新,以提高性能。

      componentDidMount() {
        fetchPosts().then(response => {
          this.setState({
            posts: response.posts      });
        });

        fetchComments().then(response => {
          this.setState({
            comments: response.comments      });
        });
      }

当我们的posts被更新之后comments还是旧的值,comments在更新完成后两个值才是最新的

注意: React状态更新的合并是基于浅合并(shallow merge)的方式。如果状态对象中包含嵌套的对象或数组,并且你希望进行深层次的合并,需要自行处理合并逻辑。

什么是浅合并?

首先先给大家用代码介绍什么是浅合并

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      person: {
        name: 'John',
        age: 25
      }
    };
  }

  handleClick = () => {
    this.setState({
      person: {
        name: 'Jane'
      }
    });
  };

  render() {
    return (
      <div>
        <p>Name: {this.state.person.name}</p>
        <p>Age: {this.state.person.age}</p>
        <button onClick={this.handleClick}>Update</button>
      </div>
    );
  }
}

上述示例中,我们的状态对象 person 包含了 name 和 age 属性。当点击按钮时,我们调用 setState() 更新状态中 person 对象的 name 属性。

由于状态更新是基于浅合并的方式,React 会用新的 person 对象替换当前状态中的 person 对象,导致 age 属性丢失。因此,在重新渲染时,person 对象只包含 name 属性。

如何进行深层次的合并?

如果你希望进行深层次的合并,需要自行处理合并逻辑。可以使用深拷贝方法(如 Object.assign()、spread 运算符或第三方库)来创建一个新的深层次对象,并将其与当前状态进行合并。

代码示例:

import React from 'react';
//要使用 cloneDeep(),你需要安装 lodash 库并导入相应的方法。
import cloneDeep from 'lodash/cloneDeep';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      person: {
        name: 'John',
        age: 25
      }
    };
  }

  handleClick = () => {
    const updatedPerson = cloneDeep(this.state.person);
    updatedPerson.name = 'Jane';

    this.setState({
      person: updatedPerson
    });
  };

  render() {
    return (
      <div>
        <p>Name: {this.state.person.name}</p>
        <p>Age: {this.state.person.age}</p>
        <button onClick={this.handleClick}>Update</button>
      </div>
    );
  }
}

在上述示例中,我们使用 cloneDeep() 方法从当前状态中创建了一个深层次的副本 updatedPerson,然后修改了副本的 name 属性。最后,我们使用新的副本来更新状态中的 person 对象,实现了深层次的合并。

React要求我们遵从“数据向下流动规则”

  • 组件间的state都是独立的互不干扰的,他们之间的状态都是互不知晓的

  • 我们的父组件的数据更新可以影响我的的子组件,但是子组件的数据更新不能往上传给父组件,并且不能影响父组件。

  • 组件可以选择把它的 state 作为 props 向下传递到它的子组件中,但是组件本身无法知道它是来自于父组件的state还是props

我们如何判断该数据是否应该放入state

通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state:

  • 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
  • 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
  • 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。

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

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

相关文章

mac怎么压缩pdf文件,苹果电脑怎么压缩pdf文件大小

在当今数字化时代&#xff0c;PDF文件已成为广泛使用的文档格式之一。然而&#xff0c;PDF 文件可能会因其包含的图像、图形和其他元素而导致文件较大&#xff0c;这可能会影响文件的传输、存储和共享。因此&#xff0c;对 PDF 文件进行压缩以减小其文件大小是很有必要的。今天…

【详细】一步一步实现一个BP神经网络-逐行代码解说

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ ​ 要如何使用代码实现一个BP神经网络呢&#xff1f; 下面跟随笔者&#xff0c;一步一步详细来实现&#xff0c;再对代码进行详细解说。 通过本文可以详细掌握怎么使用matlab来实现一个BP神经网络。 一、一步一步实…

MFC GDI绘制卡通人物

文章目录 主要代码完整visual studio工程下载主要代码 // DrawFrogView.cpp : implementation of the CDrawFrogView class //#include "stdafx.h" #include "DrawFrog.h"#include "DrawFrogDoc.h" #include "DrawFrogView.h"#includ…

路由器基础配置以及静态路由配置

1、搭建网络 搭建网络拓扑、分配IP地址、划分网段、连接端口 2、配置路由器 路由器基础配置 //进入全局配置模式 Router#enable Router#conf t Enter configuration commands, one per line. End with CNTL/Z.//配置高速同步串口serial2/0 Router(config)#int ser2/0 Route…

html侧导航栏客服栏

ico 替换 ICO <html xmlns"http://www.w3.org/1999/xhtml"><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8"><title>返回顶部</title><script src"js/jquery-2.0.3.min.js"…

gitlab升级16.11.3-ee

背景 这是事后一段时间补充记录的博客。 升级目的&#xff1a;修补漏洞CVE-2024-4835 未经认证的威胁攻击者能够利用该漏洞在跨站脚本 (XSS) 攻击中&#xff0c;轻松接管受害者账户。 gitlab版本为14.6.2-ee升级至16.11.3-ee 思路 翻阅文档找升级方法及升级版本路径。使用…

HarmonyOS父子组件传递参数

HarmonyOS父子组件传递参数 1. 使用State和Prop进行父子组件传递———注意是单向同步 Prop装饰器&#xff1a;父子单向同步 注意&#xff1a;只支持单向同步&#xff0c;同时也只能支持string\number\boolean\enum比较简单的类型。 代码 // 使用 props 进行父子组件传值…

Android翻转动画(卡片翻转效果)

前言 最近好友问计蒙翻转动画&#xff0c;恰好在大二那年看Android Api Demo时记了笔记&#xff0c;由此写一篇文章。 需求 屏幕右滑事件触发卡片的翻转效果 &#xff0c;为了方便&#xff0c;在例子中将右滑事件改成按钮点击事件 老规矩&#xff0c;最后有源码 一、先介绍三…

从 Hadoop 迁移,无需淘汰和替换

我们仍然惊讶于有如此多的客户来找我们&#xff0c;希望从HDFS迁移到现代对象存储&#xff0c;如MinIO。我们现在以为每个人都已经完成了过渡&#xff0c;但每周&#xff0c;我们都会与一个决定进行过渡的主要、高技术性组织交谈。 很多时候&#xff0c;在这些讨论中&#xff…

mac 常用工具命令集合

一、vim 快捷键 1、移动光标 h j k l 左 下 上 右 箭头上 上移一行 箭头下 下移一行 0 跳至行首&#xff0c;不管有无缩进&#xff0c;就是跳到第0个字符 ^ 跳至行首的第一个字符 $ 跳至行尾 gg 跳至文首 G 调至文尾 5gg/5G 调至第5行w 跳到下一个字首&#xff0c;按标点或…

Android修行手册-ImageView的adjustViewBounds和设置透明度

点击跳转>GameFramework文档系列&#xff08;二&#xff09;- 场景相关 点击跳转>GameFramework文档系列&#xff08;三&#xff09;- 日志管理和UI 点击跳转>GameFramework文档系列&#xff08;四&#xff09;- 事件订阅 点击跳转>保姆式Cocos合成大西瓜案例 …

基于LangChain-Chatchat实现的RAG-本地知识库的问答应用[3]-参数配置详细版

基于LangChain-Chatchat实现的RAG-本地知识库的问答应用[3]-参数配置详细版 在开始参数配置之前,先执行以下脚本 python copy_config_example.py该脚本将会将所有config目录下的配置文件样例复制一份到config目录下,方便开发者进行配置。 接着,开发者可以根据自己的需求,对…

LeetCode---402周赛

题目列表 3184. 构成整天的下标对数目 I 3185. 构成整天的下标对数目 II 3186. 施咒的最大总伤害 3187. 数组中的峰值 一、构成整天的下标对数目 I & II 可以直接二重for循环暴力遍历出所有的下标对&#xff0c;然后统计符合条件的下标对数目返回。代码如下 class So…

graalvm jdk和openjdk

下载地址:https://github.com/graalvm/graalvm-ce-builds/releases 官网: https://www.graalvm.org

【AI作曲】毁掉音乐?早该来了!一个网易音乐人对于 AI 大模型音乐创作的思辨

引言&#xff1a;AI在创造还是毁掉音乐&#xff1f; 正如当初 midjourney 和 StableDiffusion 在绘画圈掀起的风波一样&#xff0c;suno 和 各大音乐大模型的来临&#xff0c;其实早该来了。 AI 在毁掉绘画&#xff1f;或者毁掉音乐&#xff1f; 没错&#xff0c;但也错了。…

Android网络收集和ping封装库

功能Stetho 是 Facebook 开源的一个 Android 调试工具。是一个 Chrome Developer Tools 的扩展&#xff0c;可用来检测应用的网络、数据库、WebKit 、SharePreference等方面的功能。开发者也可通过它的 dumpapp 工具提供强大的命令行接口来访问应用内部。 02.stetho大概流程 …

学校图书借阅管理系统(数据库课设)PS:有前端界面

1.课设要求描述 ●实现图书信息、类别、出版社等信息的管理; ●实现读者信息、借阅证信息的管理; ●实现图书的借阅、续借、归还管理; ●实现超期罚款管理、收款管理; ●创建触发器&#xff0c;分别实现借书和还书时自动更新图书信息的在册数量; ●创建视图查询各种图书…

谷歌手机刷机教学

注意&#xff1a;手机已经解开了oem锁和bl 1、adb基础命令 连接设备adb devices&#xff1a;列出当前连接的所有设备。 adb connect <设备IP>&#xff1a;通过IP地址连接设备&#xff08;用于无线连接&#xff09;。 设备信息adb shell getprop&#xff1a;获取设备的所…

数据结构 —— 线索二叉树

数据结构 —— 线索二叉树 线索二叉树结构定义结点类树类 线索化找线索二叉树的后继找线索二叉树的前驱 我们今天来看看线索二叉树。 线索二叉树 线索二叉树&#xff08;Threaded Binary Tree&#xff09;是一种特殊的二叉树结构&#xff0c;它是在二叉树的基础上进行改良的数…

前端 CSS 经典:旋转边框效果

效果&#xff1a; 思路&#xff1a;使用伪元素&#xff0c;给伪元素设置背景色&#xff0c;然后定位&#xff0c;遮盖&#xff0c;旋转。就可以实现旋转边框效果。 实现代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta chars…