【React】深入理解React组件状态State

news2025/1/11 14:56:46

目录

  • 一、何为State
  • 二、如何定义State
  • 三、如何判断是否为State
  • 四、如何正确使用State
    • 1、用setState修改State
    • 2、State的更新是异步的
      • ①、代码示例
    • 3、State更新会被合并
      • ①、组件状态例子
      • ②、当只需要修改状态title时,只需要将修改后的title传给setState
      • ③、React会合并新的title到原来的组件状态中,同时保留原有的状态content
    • 4、setState里顺序更新
  • 五、根据State类型更新
    • 1、状态的类型是不可变类型(数字,字符串,布尔值,null,undefined)
    • 2、状态的类型是数组
      • ①、增加
      • ②、截取
      • ③、条件过滤
    • 3、状态的类型是普通对象(不包含字符串、数组)
      • ①、使用ES6的Object.assgin方法
      • ②、使用对象扩展语法(Object spread properties)
  • 六、State向下流动
  • 七、State与Props区别
    • 1、Props
    • 2、State
    • 3、二者区别
  • 八、Props的使用
    • 1、props(属性)默认为“true”
    • 2、props扩展

一、何为State

React 的核心思想是组件化,而组件中最重要的概念是State(状态),State是一个组件的UI数据模型,是组件渲染时的数据依据。
状态(state) 和 属性(props) 类似,都是一个组件所需要的一些数据集合,但是state是私有的,可以认为state是组件的“私有属性(或者是局部属性)”。

二、如何定义State

定义一个合适的State,是正确创建组件的第一步。State必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从State的变化中反映出来;同时,State还必须是代表一个组件UI呈现的最小状态集,即State中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态

三、如何判断是否为State

组件中用到的一个变量是不是应该作为组件State,可以通过下面的4条依据进行判断:

  1. 这个变量是否是通过Props从父组件中获取?如果是,那么它不是一个状态。
  2. 这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。
  3. 这个变量是否可以通过其他状态(State)或者属性(Props)计算得到?如果是,那么它不是一个状态。
  4. 这个变量是否在组件的render方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。

并不是组件中用到的所有变量都是组件的状态!当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。

四、如何正确使用State

1、用setState修改State

直接修改state,组件并不会重新触发render()

import React, { Component } from 'react'

export default class stateStudy extends Component {
    state = {
        myText: '收藏',
    }
    render() {
        return (
            <div>
                <h1>欢迎来到React开发</h1>
                <button onClick={() => {
                    this.state({
                        myText: '取消收藏'
                    })
                }}>{this.state.myText}</button>
            </div>
        )
    }
}

这时点击收藏时,报:状态不是一个函数

在这里插入图片描述

正确的修改方式是使用setState()

import React, { Component } from 'react'

export default class stateStudy extends Component {
    state = {
        myText: '收藏',
        myTextShow: true
    }
    render() {
        return (
            <div>
                <h1>欢迎来到React开发</h1>
                <button onClick={() => {
                    this.setState({
                        myTextShow: !this.state.myTextShow
                    })
                }}>{this.state.myTextShow ? '收藏' : '取消收藏'}</button>
            </div>
        )
    }
}

在这里插入图片描述

2、State的更新是异步的

  • 调用setState后,setState会把要修改的状态放入一个队列中(因而组件的state并不会立即改变)
  • 之后React会优化真正的执行时机来优化性能,所以优化过程中有可能会将多个setState的状态修改合并为一次状态修改,因而state更新可能是异步的
  • 所以不要依赖当前的state,计算下个State。当真正执行状态修改时,依赖的this.state并不能保证是最新的State,因为React会把多次State的修改合并成一次,这时,this.state将还是这几次State修改前的State

另外需要注意:同样不能依赖当前的Props计算下个状态,因为Props一般也是从父组件的State中获取,依然无法确定在组件状态更新时的值

①、代码示例

现在渲染一个button,想每点击一下,counter就+3

class App extends React.Component {
  state = {
    counter: 0,
  }
  handleClick = () => {
    const { counter } = this.state;
    //或者 const counter = this.state.counter;
    this.setState({ counter: counter + 1 });
    this.setState({ counter: counter + 1 });
    this.setState({ counter: counter + 1 });
  }
  render() {
    return (
      <div>
        counter is: {this.state.counter}
        <button onClick={this.handleClick} >点我</button>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('root'));

在这里插入图片描述

之所以+1,不是+3,是因为state的更新可能是异步的,React会把传入多个setState的多个Object”batch“起来合并成一个。合并成一个就相当于把传入setState的多个Object进行shallow merge,像这样:

const update = {
    counter: counter + 1,
    counter: counter + 1,
    counter: counter + 1
    //因为上面三句话都一样,所以会当一句话执行
 }

想要实现+3的效果,就可以按照下面的方式进行实现:

class App extends React.Component {
  state = {
    counter: 0,
  }
  handleClick = () => {
    this.setState(prev => ({ counter: prev.counter + 1 }));
    this.setState(prev => ({ counter: prev.counter + 1 }));
    this.setState(prev => ({ counter: prev.counter + 1 }));
    //这样是错的 this.setState(prev => {counter: prev.counter + 1});
    //这样是错的 this.setState(prev => {counter:++prev.counter});
    //这样是错的 this.setState(prev => {counter:prev.counter++});
  }
  render() {
    return (
      <div>
        counter is: {this.state.counter}
        <button onClick={this.handleClick} >点我</button>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('root'));

之所以成功是因为:传入多个 setState 的多个 Object 会被 shallow Merge,而传入多个 setState 的多个 function 会被 “queue” 起来,queue 里的 function 接收到的 state(上面是 prev )都是前一个 function 操作过的 state。

3、State更新会被合并

①、组件状态例子

this.state = {
  title : 'React',
  content : 'React is an wonderful JS library!'
}

②、当只需要修改状态title时,只需要将修改后的title传给setState

this.setState({title: 'Reactjs'});

③、React会合并新的title到原来的组件状态中,同时保留原有的状态content

合并后的State为:

{
  title : 'Reactjs',
  content : 'React is an wonderful JS library!'
}

4、setState里顺序更新

  //history 为数组
   this.setState({
       history: history.concat([1]),  //(1)
       current: history.length,       //(2)
       nextPlayer: !nextPlayer,       //(3)
  });

执行setState时:先更新history,然后再用更新改变后的history计算current的值,最后再更新nextPlayer

五、根据State类型更新

当状态发生变化时,如何创建新的状态?根据状态的类型,分为以下三种情况:

1、状态的类型是不可变类型(数字,字符串,布尔值,null,undefined)

这种情况最简单,直接给要修改的状态赋一个新值即可

//原state
this.state = {
  count: 0,
  title : 'React',
  success:false
}
//改变state
this.setState({
  count: 1,
  title: 'bty',
  success: true
})

2、状态的类型是数组

数组是一个引用,React 执行 diff 算法时比较的是两个引用,而不是引用的对象。所以直接修改原对象,引用值不发生改变的话,React 不会重新渲染。因此,修改状态的数组或对象时,要返回一个新的数组或对象。

①、增加

如有一个数组类型的状态books,当向books中增加一本书(chinese)时,使用数组的concat方法或ES6的数组扩展语法

// 方法一:将state先赋值给另外的变量,然后使用concat创建新数组
let books = this.state.books; 
this.setState({
  books: books.concat(['chinese'])
})

// 方法二:使用preState、concat创建新数组
this.setState(preState => ({
  books: preState.books.concat(['chinese'])
}))

// 方法三:ES6 spread syntax
this.setState(preState => ({
  books: [...preState.books, 'chinese']
}))

②、截取

当从books中截取部分元素作为新状态时,使用数组的slice方法:

// 方法一:将state先赋值给另外的变量,然后使用slice创建新数组
let books = this.state.books; 
this.setState({
  books: books.slice(1,3)
})
// 
// 方法二:使用preState、slice创建新数组
this.setState(preState => ({
  books: preState.books.slice(1,3)
}))

③、条件过滤

当从books中过滤部分元素后,作为新状态时,使用数组的filter方法:

// 方法一:将state先赋值给另外的变量,然后使用filter创建新数组
var books = this.state.books; 
this.setState({
  books: books.filter(item => {
    return item != 'React'; 
  })
})

// 方法二:使用preState、filter创建新数组
this.setState(preState => ({
  books: preState.books.filter(item => {
    return item != 'React'; 
  })
}))

注意:不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。

3、状态的类型是普通对象(不包含字符串、数组)

对象是一个引用,React执行diff算法时比较的是两个引用,而不是引用的对象,所以直接修改原对象,引用值不发生改变的话,React不会重新渲染。因此,修改状态的数组或对象时,要返回一个新的对象

①、使用ES6的Object.assgin方法

// 方法一:将state先赋值给另外的变量,然后使用Object.assign创建新对象
var owner = this.state.owner;
this.setState({
  owner: Object.assign({}, owner, {name: 'Jason'})
})

// 方法二:使用preState、Object.assign创建新对象
this.setState(preState => ({
  owner: Object.assign({}, preState.owner, {name: 'Jason'})
}))

②、使用对象扩展语法(Object spread properties)

// 方法一:将state先赋值给另外的变量,然后使用对象扩展语法创建新对象
var owner = this.state.owner;
this.setState({
  owner: {...owner, name: 'Jason'}
})

// 方法二:使用preState、对象扩展语法创建新对象
this.setState(preState => ({
  owner: {...preState.owner, name: 'Jason'}
}))

综上所述:创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。

六、State向下流动

我们说 props 是组件对外的接口,state 是组件对内的接口。
一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):

<MyComponent title={this.state.title}/>

这通常称为一个“从上到下”,或者“单向”的数据流。任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件。

如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。

七、State与Props区别

1、Props

props是指组件间传递的一种方式,,props自然也是可以传递state的。因为React的数据流是自上而下的,所以是从父组件向子组件进行的传递,另外组件内部的this.props属性是只读的不可修改

2、State

state不同于props的一点是,state是可以被改变的。不过,不可以直接通过this.state=的方式来修改,而是需要通过this.setState()方法来修改state

3、二者区别

  • props是传递给组件的(类似于函数的形参)、是不可修改的
  • state是在组件内被组件自己管理的(类似于在一个函数内声明的变量),是多变的、可修改的,每次setState都异步更新

八、Props的使用

当一个组件被注入一些属性(Props )值时,属性值来源于它的父级元素,所以人们常说,属性在 React 中是单向流动的:从父级到子元素

1、props(属性)默认为“true”

如果你没给 prop(属性) 传值,那么他默认为 true 。下面两个 JSX 表达式是等价的:

<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />

通常情况下,我们不建议使用这种类型,因为这会与ES6中的对象shorthand混淆 。ES6 shorthand 中 {foo} 指的是 {foo: foo} 的简写,而不是 {foo: true} 。这种行为只是为了与 HTML 的行为相匹配。

举个例子:在 HTML 中,< input type=“radio” value=“1” disabled /> 与 < input
type=“radio” value=“1” disabled=“true” /> 是等价的。JSX 中的这种行为就是为了匹配 HTML
的行为。

2、props扩展

如果你已经有一个 object 类型的 props,并且希望在 JSX 中传入,你可以使用扩展操作符 … 传入整个 props 对象。这两个组件是等效的:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

显然下面的方法更方便:因为它将数据进行了包装,而且还简化了赋值的书写

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

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

相关文章

Go 语言高级网络编程

深入探讨 Go 语言的网络编程 简介 Go&#xff08;Golang&#xff09;中的网络编程具有易用性、强大性和乐趣。本指南深入探讨了网络编程的复杂性&#xff0c;涵盖了协议、TCP/UDP 套接字、并发等方面的内容&#xff0c;并附有详细的注释。 关键概念 1. 网络协议 TCP&#x…

代码小王子:国庆后上班的『表情包』神器

引言 哎哟&#xff0c;国庆七天乐就像手里握着的沙子&#xff0c;一不小心就从指间滑落了。&#x1f914; 小伙伴们&#xff0c;是不是感觉还没玩儿够&#xff0c;就要面对冷酷的现实——上班了&#xff01;&#x1f61f; 但是&#xff0c;咱们要以最饱满的热情&#xff0c;最…

力扣 53. 最大子数组和(C语言+分治递归、动态规划)

1. 题目 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。子数组 是数组中的一个连续部分。 2. 输入输出样例 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1…

java进阶-第8章-IO流

一、File类 概念&#xff1a;代表物理盘符中的一个文件或者文件夹。 常见方法&#xff1a; 方法名描述createNewFile()创建一个新文件。mkdir()创建一个新目录。delete()删除文件或空目录。exists()判断File对象所对象所代表的对象是否存在。getAbsolutePath()获取文件的绝对…

最新 SpringCloud微服务技术栈实战教程 微服务保护 分布式事务 课后练习等

SpringCloud微服务技术栈实战教程&#xff0c;涵盖springcloud微服务架构Nacos配置中心分布式服务等 SpringCloud及SpringCloudAlibaba是目前最流行的微服务技术栈。但大家学习起来的感受就是组件很多&#xff0c;不知道该如何应用。这套《微服务实战课》从一个单体项目入手&am…

【Python】下载和安装

【Python】下载和安装 下载 进入 Python官网&#xff0c;下载最新版本&#xff0c;如下图所示&#xff1a; 如果需要下载其他版本&#xff0c;请下滑该页面&#xff0c;找到如下位置&#xff0c;选择需要的版本进行下载&#xff08;以3.8.10为例&#xff09;&#xff1a; 点击…

运行软件找不到mfc140u.dll怎么解决,mfc140u.dll是什么文件

"找不到 mfc140u.dll"是一条错误信息&#xff0c;表示您的计算机上缺少一个名为 mfc140u.dll 的动态链接库&#xff08;DLL&#xff09;文件。这个文件通常与 Microsoft Visual C Redistributable 相关。Mfc140u.dll 是 Microsoft 基础类库&#xff08;MFC&#xff0…

当 FineReport 遇见 CnosDB

随着大数据和物联网应用的快速发展&#xff0c;时序数据库成为了一种关键的数据存储和分析工具。而 FineReport 作为一款流行的商业智能工具&#xff0c;与时序数据库 CnosDB 的集成可以为企业提供更强大的数据分析和可视化功能。本博客将介绍如何将 FineReport 与 CnosDB 集成…

CSS点击切换或隐藏盒子的卷起、展开效果

<template><div class"main"><el-button click"onCllick">切换</el-button><transition name"slideDown"><div class"info" v-if"isShow">1111</div></transition></di…

云数据库保护需要注意哪些事项?

云数据库保护是在云计算环境中对数据库进行保护和安全管理的重要措施。随着云计算的普及和应用&#xff0c;云数据库的保护也变得尤为重要。以下是安策分享的关于云数据库保护需要注意的一些事项。 云数据库的访问控制是保护数据库的首要任务。只有授权的用户或应用程序才能访问…

win11系统下,将WSL2从系统盘(C盘)迁移到迁移到数据盘(D盘)

WSL2迁移磁盘 网上的一些方法 今天希望把WSL迁移到D盘&#xff0c;原因就是C盘剩余空间太少了&#xff0c;系统有一点卡顿&#xff0c;然后百度了一下迁移的方法&#xff0c;发现真的是八仙过海&#xff0c;各显神通啊&#xff0c;改注册表、exclude为.tar然后重新导入等等&a…

MVCC和BufferPool缓存机制

文章目录 1. MVCC多版本并发控制机制2. BufferPool缓存机制 1. MVCC多版本并发控制机制 Mysql可以在可重复读隔离级别下可以保证事务较高的隔离性&#xff0c;这个隔离性是由MVCC机制来保证的&#xff0c;对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性&#…

alsa pcm接口之pcm设备的状态STATE

应用和库之间的协作: ALSA pcm api设计使用状态来确定应用程序和库之间的通信阶段,实际的状态可以被决定通过使用snd_pcm_state调用,下面列举出来状态: SND_PCM_STATE_OPEN: 表示pcm设备被打开的状态,使用了snd_pcm_open()之后进入该状态,并且让snd_pcm_hw_params()调用失败后,…

Safran助力dSPACE实现基于GNSS驾驶功能的HIL仿真

概述 世界知名的模拟与验证方案厂商dSPACE借助虹科Safran GNSS模拟器实现了一套基于GNSS的驾驶功能HIL仿真系统&#xff0c;该系统可以用于自动驾驶、车联网、智能座舱等各类汽车行业应用&#xff0c;并具备极大的灵活性与多功能&#xff0c;适用于各类复杂场景测试。 方案介绍…

vscode更改为中文版本

方式一 在扩展里安装chinese插件 方式二 1.Ctrl&#xff0b; Shift &#xff0b;P&#xff08;commandshiftP&#xff09; 2.输入Configure display Language 3.选择zh-cn 这时候vscode会提示需要重启&#xff0c;点击restart重启vscode&#xff0c;重启后vscode就会显示中…

找不到msvcp140_1.dll怎么办,快速解决msvcp140_1.dll问题的方法分享

在日常使用计算机的过程中&#xff0c;经常会遇到一些程序无法正常运行的问题&#xff0c;其中最常见的就是“msvcp140_1.dll丢失”。这是一个典型的DLL文件丢失问题&#xff0c;但背后的原因却往往并不简单。通过深入研究这个问题&#xff0c;我对其有了更深入的理解。 首先&a…

【沐风老师】3DMAX彩灯串灯生成器使用方法详解

3DMAX彩灯串灯生成器使用教程 3DMAX彩灯串灯生成器&#xff0c;可以让你毫不费力地从场景中选定的线图形创建令人惊叹的串灯模型。可以在线条中单独生成灯泡和电线&#xff0c;有九种不同类型的灯泡可供用户选择&#xff0c;以实现各种照明效果和装饰。该工具具有一系列可自定义…

漏洞复现-易思无人值守智能物流文件上传

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

Rust Http 性能测试框架/工具

在Rust中&#xff0c;有几个常用的性能测试框架和工具可用于对HTTP性能进行测试。以下是其中一些&#xff1a; 1、Criterion&#xff1a;Criterion是一个通用的性能测试框架&#xff0c;可以用于测试各种类型的代码性能&#xff0c;包括HTTP性能。你可以使用Criterion来编写和运…

pnpm、npm、yarn 包管理工具『优劣对比』及『环境迁移』

前言 博主在开发前端网站的时候&#xff0c;发现随着开发的项目的逐渐增多&#xff0c;安装的依赖包越来越臃肿&#xff0c;依赖包的安装速度也是非常越来越慢&#xff0c;多项目开发管理也是比较麻烦。之前我就了解过 pnpm&#xff0c;但是当时担心更换包管理环境可能会出现的…