React中setState的怪异行为 ——setState没有即时生效

2023-11-01

setState可以说是React中使用频率最高的一个函数了,我们都知道,React是通过管理状态来实现对组件的管理的,当this.setState()被调用的时候,React会重新调用render方法来重新渲染UI

但实际使用的时候,我们会发现,有时候我们setState之后,并没有立刻生效,例如我们看一下以下的示例代码

class Test extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.setState({count: this.state.count + 1});
    console.log(this.state.count); // 输出0
    this.setState({count: this.state.count + 1});
    console.log(this.state.count); // 输出0
    setTimeout(() => {
      this.setState({count: this.state.count + 1});
      console.log(this.state.count); // 输出2
      this.setState({count: this.state.count + 1});
      console.log(this.state.count); // 输出3
    }, 0);
  }

  render() {
    return <div> test </div>;
  }
}

开发过程中我们会发现,在componentDidMount方法中,我们调用setState之后state的值并没有立即改变,但如果我们在setTimeOut里面调用,我们却能立刻就能获得更新,原因就在于react中的使用了基于事务(传送门,关于事务原理的解析)的异步更新机制,但对于这个异步的理解,又跟ajax的异步有所不同,因为毕竟react是一个js框架,所有的操作都是单线程的,所有的操作,都得按顺序来,那么它具体是怎么实现的呢?

我们都知道,对于dom的操作对性能的损耗是非常严重的,所以react为了提高整体的渲染性能,会将一次渲染周期中的state进行合并,在这个渲染周期中你对所有setState的所有调用都会被合并起来之后,再一次性的渲染,这样可以避免频繁的调用setState导致频繁的操作dom,提高渲染性能。具体的实现方面,可以简单的理解为react中存在一个状态变量isBatchingUpdates,当处于渲染周期开始时,这个变量会被设置成true,渲染周期结束时,会被设置成false,react会根据这个状态变量,当出在渲染周期中时,仅仅只是将当前的改变缓存起来,等到渲染周期结束时,再一次性的全部render,,具体的流程可以参照下面的流程图

现在,我们回到最开始的问题,为什么一开始在componentDidMount中直接执行setState会无法立刻得到更新呢,原因就在于,我们在componentDidMount中其实处于首次渲染的事务当中,这次事务的渲染尚未完成,而首次渲染的时候会将isBatchingUpdates设置为true,这是我们在componentDidMount中调用setState,react会发现当前事务尚未完成,只会直接将修改后的state放入到dirtyComponents中,等待最终渲染周期完成时,将所有的state进行合并,一次性render。而当我们放在setTimeOut里面的时候,setTimeOut会将操作放到执行队列的最后方,也就是说会等待渲染周期结束之后再进行setState,这个时候状态变量已经被重置回来了,所以此时我们的每一次setState都会立刻生效

接下来,我们从源码的角度来看看setState是怎么操作的

function enqueueUpdate(component) {
  ensureInjected();

  //不在渲染周期中
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

//渲染周期中,直接缓存state等待下一步更新
  dirtyComponents.push(component);
}

这个逻辑跟上图的逻辑是一样的,当我们调用setState函数的时候,实际上最终会调用到enqueueUpdate函数,整体逻辑上面已经分析过了,就不再赘述,接下来看看setState是如何通过事务来进行渲染的,也就是batchingStrategy.batchedUpdates到底做了些什么,往下走之前,如果对事务不了解建议先看看这篇文章(传送门,关于事务原理的解析

var ReactUpdates = require('ReactUpdates');
var Transaction = require('Transaction');

var emptyFunction = require('emptyFunction');

//第二个wrapper
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};

//第一个wrapper
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

//wrapper列表
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

//事务构造函数
function ReactDefaultBatchingStrategyTransaction() {
//原型中定义的初始化方法
  this.reinitializeTransaction();
}

//继承原型
Object.assign(
  ReactDefaultBatchingStrategyTransaction.prototype,
  Transaction.Mixin,
  {
    getTransactionWrappers: function() {
      return TRANSACTION_WRAPPERS;
    },
  }
);

//新建一个事务
var transaction = new ReactDefaultBatchingStrategyTransaction();

var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) {
      callback(a, b, c, d, e);
    } else {
      //在这个地方调用事务,callback是从外部传入的方法
      transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

上面这个就是react渲染所使用的事务,react就是用这个事务来处理setState引起的渲染,根据我们刚刚的解释,我们可以看到,事务开始时就把isBatchingUpdates设置成了true,防止在一次渲染周期中重复渲染,我们还可以看到这个事务定义了两个wrapper,其出口方法close分别用于执行渲染和设置状态变量,而执行渲染的FLUSH_BATCHED_UPDATES 要先于执行设置状态变量的RESET_BATCHED_UPDATES ,也就是说,执行渲染之后,才会通过RESET_BATCHED_UPDATES的close方法执行这句代码

ReactDefaultBatchingStrategy.isBatchingUpdates = false;

之后整个渲染周期结束。这时候当我们执行setState的时候,重新进入一个新的渲染周期

那么,问题来了,当我们在渲染周期中执行了setState之后,我们要如何获取到最新的state的值呢,setTimeOut是一个方案,但是不太优雅,有没有其他方法呢,我们注意到,setState提供了一个回调函数,我们只需要在回调里面获取更新后的state即可,像这样

componentDidMount() {
    this.setState({count: this.state.count + 1},()=>{
    console.log(this.state.count);//该是啥就是是啥
    }));
  }

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React中setState的怪异行为 ——setState没有即时生效 的相关文章

  • B - Polycarp‘s Practice

    Polycarp is practicing his problem solving skill He has a list of nn problems with difficulties a 1 a 2 dots a na1 a2 an
  • 【蓝桥杯】01背包问题

    一 问题描述 有 N 个物品 并且每个物品都有一个重量 W 和一个价值 V 你有一个能装 M 重量的背包 问怎么装才能使所装的价值最大 每个物品只有一个 输入 输入的第一行包含两个整数 n m 分别表示物品的个数和背包能装的重量 以后N行每

随机推荐

  • xcode iphone messagebox

    Show a main window message box Alert View will automatically adjust for landscape orientation UIAlertView alert UIAlertV
  • crm客户管理是什么 好的销售crm具备哪些特点

    近年来crm日益流行 也得到了越来越多企业的重视 crm的C是Customer R代表Relationship M是Management 也就是客户关系管理 什么是crm客户管理呢 crm也是一种管理软件 是一种以信息技术为手段 有效提高企
  • ExMobi文档

    EXmobi官方文档 ExMobi 从入门到精通 本书电子版和示例代码请访问GIT仓库 https github com nandy007 ExMobiBeginnerBook ExMobi门户 http www exmobi cn ExM
  • Django 简单教程(入门级)

    一 Django简介 1 web框架介绍 具体介绍Django之前 必须先介绍WEB框架等概念 web框架 别人已经设定好的一个web网站模板 你学习它的规则 然后 填空 或 修改 成你自己需要的样子 一般web框架的架构是这样的 其它基于
  • 数字电路设计之加法器的实现

    今天在看博客的时候看到有一种新颖的加法器写法 这和之前的超前进位加法器 二进制加法器不同 这个加法器应用了循环 我觉得应该会综合出来一个很差的东西 然后我试了一下 代码 module adder x y cin sum cout param
  • ubuntu登录输入密码后无法进入系统,重新返回输入密码界面(已解决)

    修改 etc profile时 由于未知问题导致无法使用ls等命令 重启后输入密码无法进入Ubuntu系统 反复回到输入密码界面 解决方法 1 按下Ctrl Alt F1 输入用户名 密码 2 输入 export PATH usr loca
  • 9 QT的窗口系统-QMainWindow

    QMainWindow是一个为用户提供主窗口程序的类 包含一个菜单栏 menu bar 多个工具栏 tool bars 多个浮动窗口 dock widgets 一个状态栏 status bar 及一个中心部件 central widget
  • Taro多端开发实现原理与项目实战(一)

    Taro 多端开发实现原理与项目实战 前端多端统一开发背景与趋势介绍 背景 大前端 不仅会成为移动开发与 Web 前端的发展趋势 也会是未来的显示设备终端的开发技术趋势 越来越多的业内人士对此表示肯定 因为终端碎片化和 Serverless
  • matlab运行sph法,SPH法一维实例:启发

    左志华 在读硕士 zuo zhihua qq com 深圳市罗湖区 2021 1 12 ThinkPad S2 2018 Windows10 Home 关键词 SPH法 一维 启发 B样条核函数 Octave 6 1 1 背景 从2020年
  • 个人网银系统平台搭建教程

    本教程目标 熟悉并了解EMP开发平台 学会自行搭建个人网银平台 EMP平台 1 何为EMP EMP平台是北京宇信集团开发的一个基于J2EE体系的 WEB应用的 基础框架平台 具有如下特性 表现逻辑框架 MVCFrameWork 与业务逻辑框
  • Tracy JS 小笔记 - 浏览器,JS 特性, JS 引入,变量

    浏览器 主流浏览器 拥有独立内壳的 IE 内壳是 Trident Chrome Blink Webkit的分支 Firefox Gecko Opera Blink Opera内核原为 Presto 现为 Blink Safari Webki
  • VMware Fusion 13在M2芯片的Mac上安装 Windows 11

    首先需要下载Windows 11镜像 以下给出一种官方方法 当然也可以自己去网上搜索 有很多资源 注册微软账号 使用注册的账号登录 访问 https www microsoft com en us windowsinsider regist
  • curl: (7) Failed to connect to raw.github.com port 443: 拒绝连接

    在使用快捷命令安装oh my zsh时出现如题错误 原因 你机器所配置的DNS服务器无法解析raw github com 也就是我们经常说的被墙了 可以通过科学上网解决 本文附上另外一种解决方法 如果你不满足科学上网的条件 你可以采用这种方
  • 华为鸿蒙系统支持什么手机_目前鸿蒙系统并不支持低端手机,反而支持的都是华为高端机...

    鸿蒙OS支持的手机名单 最新 华为P40 ANA AN00 华为P40Pro ELS AN00 华为Mate30 TAS AL00 TAS AN00 华为Mate30Pro UO AL00 UO AN00 从上面的名单可以看出 这四款机型都
  • Matlab求解数学问题

    Matlab 求解数学问题 如果看完之后还是对相关函数的运用不甚了解 请务必使用 help 查看更详细的帮助文档 求解一元 n 次方程 例 求解一元二次方程 x 2 2 x 1 0 clc clear 定义函数 syms f x f x 2
  • CSS快速记忆笔记(一)

    CSS层叠样式表用来美化网页 被称为网页的美术师 非常有趣和好玩 一起来学习吧 知识点01 CSS 层叠样式表 HTML只关注内容的语义 不能满足设计者的需要 操作HTML属性不方便 给代码带来更多的繁琐和臃肿 不易维护 所以它有一定的局限
  • Qt如何隐藏ListWidget的滚动条

    为了界面的美观 我们有时会需要隐藏掉QListWidget的水平滚动条和垂直滚动条 可以使用以下代码实现 QListWidget lw lyricList new QListWidget ui gt lw lyricList gt setV
  • SpringMVC框架

    一 SpringMVC框架概述 1 简介 Spring为构建Web应用提供了一个功能全面的MVC框架 虽然Spring可以很容易地与其它MVC框架集成 例如Struts 但Spring的MVC框架使用IOC对控制逻辑和业务对象提供了完全的分
  • Redis基础学习

    作用 内存存储 持久化 内存中是断电即失 持久化很重要 rdb aof 效率高 高速缓存 发布订阅系统 地图信息分析 计时器 计数器 特点 开源 持久化 多种数据类型 基础知识 redis中有16个数据库 默认使用第一个数据库 select
  • React中setState的怪异行为 ——setState没有即时生效

    setState可以说是React中使用频率最高的一个函数了 我们都知道 React是通过管理状态来实现对组件的管理的 当this setState 被调用的时候 React会重新调用render方法来重新渲染UI 但实际使用的时候 我们会