当前位置: 首页 > news >正文

React的State和setState

如何确地使用 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。

相关文章:

  • Flink——最流批的大数据框架(流批一体)
  • 【Unity学习笔记】第十八 基于物理引擎的日月地系统简单实现
  • java智慧工地系统源码 智慧工地标准之一:环境监测 告别灰头土脸、智慧工地环境监测系统都包括哪些功能?
  • ThinkPHP6图书借阅管理系统
  • 基于uni-app和图鸟UI的智慧农业综合管控平台小程序技术实践
  • vue实现的商品列表网页
  • 第二篇: 掌握Docker的艺术:深入理解镜像、容器和仓库
  • 华为HCIP Datacom H12-821 卷10
  • 2024年华为OD机试真题-万能字符单词拼写-C++-OD统一考试(C卷D卷)
  • Admin
  • 公共网络IP地址不正确?别担心,这里有解决方案
  • 【R语言】地理探测器模拟及分析(Geographical detector)
  • 计算机网络 交换机的VLAN配置
  • 从零到一学FFmpeg:avformat_alloc_output_context2 函数详析与实战
  • 【for循环】最大跨度
  • 30秒的PHP代码片段(1)数组 - Array
  • angular学习第一篇-----环境搭建
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JavaScript设计模式系列一:工厂模式
  • mysql常用命令汇总
  • Netty源码解析1-Buffer
  • Python学习笔记 字符串拼接
  • React中的“虫洞”——Context
  • Service Worker
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • TypeScript实现数据结构(一)栈,队列,链表
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • Web Storage相关
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 聊聊directory traversal attack
  • 聊聊hikari连接池的leakDetectionThreshold
  • 排序(1):冒泡排序
  • 前端技术周刊 2019-01-14:客户端存储
  • 前言-如何学习区块链
  • 《天龙八部3D》Unity技术方案揭秘
  • Java总结 - String - 这篇请使劲喷我
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ## 1.3.Git命令
  • #pragma 指令
  • #window11设置系统变量#
  • (Java企业 / 公司项目)点赞业务系统设计-批量查询点赞状态(二)
  • (k8s)Kubernetes 从0到1容器编排之旅
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (pojstep1.3.1)1017(构造法模拟)
  • (pycharm)安装python库函数Matplotlib步骤
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (十)Flink Table API 和 SQL 基本概念
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (四)js前端开发中设计模式之工厂方法模式
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (万字长文)Spring的核心知识尽揽其中
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化