首页 > 其他分享 >react中setState是同步的还是异步的

react中setState是同步的还是异步的

时间:2024-03-17 13:29:32浏览次数:22  
标签:count 异步 React 更新 react 组件 isBatchingUpdates setState

首先说一下setState是同步的还是异步的?

1.解读 setState 工作流

在这里插入图片描述
 接下来我们就沿着这个流程,逐个在源码中对号入座。首先是 setState 入口函数:

ReactComponent.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

入口函数在这里就是充当一个分发器的角色,根据入参的不同,将其分发到不同的功能函数中去。这里我们以对象形式的入参为例,可以看到它直接调用了 this.updater.enqueueSetState这个方法:

enqueueSetState: function (publicInstance, partialState) {
  // 根据 this 拿到对应的组件实例
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  // 这个 queue 对应的就是一个组件实例的 state 数组
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  queue.push(partialState);
  //  enqueueUpdate 用来处理当前的组件实例
  enqueueUpdate(internalInstance);
}

将新的 state 放进组件的状态队列里;
用 enqueueUpdate 来处理将要更新的实例对象。

继续往下走,看看 enqueueUpdate 做了什么:

function enqueueUpdate(component) {
  ensureInjected();
  // 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
  if (!batchingStrategy.isBatchingUpdates) {
    // 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

这个 enqueueUpdate非常有嚼头,它引出了一个关键的对象——batchingStrategy,该对象所具备的isBatchingUpdates属性直接决定了当下是要走更新流程,还是应该排队等待;其中的batchedUpdates方法更是能够直接发起更新流程。由此可以大胆推测,batchingStrategy 或许正是 React 内部专门用于管控批量更新的对象。

接下来,我们就一起来研究研究这个 batchingStrategy。

/**
 * batchingStrategy源码
**/
var ReactDefaultBatchingStrategy = {
  // 全局唯一的锁标识
  isBatchingUpdates: false,
  // 发起更新动作的方法
  batchedUpdates: function(callback, a, b, c, d, e) {
    // 缓存锁变量
    var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates
    // 把锁“锁上”
    ReactDefaultBatchingStrategy. isBatchingUpdates = true
    if (alreadyBatchingStrategy) {
      callback(a, b, c, d, e)
    } else {
      // 启动事务,将 callback 放进事务里执行
      transaction.perform(callback, null, a, b, c, d, e)
    }
  }
}

batchingStrategy对象并不复杂,你可以理解为它是一个“锁管理器”。

里的“锁”,是指 React 全局唯一的 isBatchingUpdates变量,isBatchingUpdates的初始值是 false,意味着“当前并未进行任何批量更新操作”。每当 React 调用 batchedUpdate去执行更新动作时,会先把这个锁给“锁上”(置为 true),表明“现在正处于批量更新过程中”。当锁被“锁上”的时候,任何需要更新的组件都只能暂时进入 dirtyComponents里排队等候下一次的批量更新,而不能随意“插队”。此处体现的“任务锁”的思想,是 React 面对大量状态仍然能够实现有序分批处理的基石。

理解了批量更新整体的管理机制,还需要注意 batchedUpdates中,有一个引人注目的调用:

transaction.perform(callback, null, a, b, c, d, e)

这行代码为我们引出了一个更为硬核的概念——React 中的 Transaction(事务)机制

理解 React 中的 Transaction(事务) 机制。

Transaction 在 React 源码中的分布可以说非常广泛。如果在 Debug React 项目的过程中,发现函数调用栈中出现了 initialize、perform、close、closeAll或者 notifyAll这样的方法名,那么很可能当前就处于一个 Trasaction中。

Transaction 在 React 源码中表现为一个核心类,React 官方曾经这样描述它:Transaction 是创建一个黑盒,该黑盒能够封装任何的方法。因此,那些需要在函数运行前、后运行的方法可以通过此方法封装(即使函数运行中有异常抛出,这些固定的方法仍可运行),实例化 Transaction 时只需提供相关的方法即可。

这段话初读有点拗口,结合 React 源码中的一段针对 Transaction 的注释来理解它。

* <pre>
 *                       wrappers (injected at creation time)
 *                                      +        +
 *                                      |        |
 *                    +-----------------|--------|--------------+
 *                    |                 v        |              |
 *                    |      +---------------+   |              |
 *                    |   +--|    wrapper1   |---|----+         |
 *                    |   |  +---------------+   v    |         |
 *                    |   |          +-------------+  |         |
 *                    |   |     +----|   wrapper2  |--------+   |
 *                    |   |     |    +-------------+  |     |   |
 *                    |   |     |                     |     |   |
 *                    |   v     v                     v     v   | wrapper
 *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
 * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
 * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | |   | |   |   |         |   |   | |   | |
 *                    | +---+ +---+   +---------+   +---+ +---+ |
 *                    |  initialize                    close    |
 *                    +-----------------------------------------+
 * </pre>

说白了,Transaction就像是一个“壳子”,它首先会将目标函数用 wrapper(一组 initialize及 close方法称为一个 wrapper) 封装起来,同时需要使用 Transaction 类暴露的 perform方法去执行它。如上面的注释所示,在 anyMethod执行之前,perform会先执行所有 wrapper 的 initialize方法,执行完后,再执行所有 wrapper的 close方法。这就是 React 中的事务机制。

“同步现象”的本质

下面结合对事务机制的理解,继续来看在 ReactDefaultBatchingStrategy这个对象。ReactDefaultBatchingStrategy其实就是一个批量更新策略事务,它的 wrapper有两个:FLUSH_BATCHED_UPDATES和 RESET_BATCHED_UPDATES

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

把这两个 wrapper套进 Transaction的执行机制里,不难得出一个这样的流程:

在callback执行完成后,RESET_BATCHED_UPDATES将isBatchingUpdates置为false,FLUSH_BATCHED_UPDATES执行flusfBatchedUpdates,然后里面会循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法(componentWillReceiveProps=>shouldComponentUpdate=>componentWillUpdate=>render=>componentDidUpdate),最后实现组件更新。

到这里,相信已经对 isBatchingUpdates管控下的批量更新机制已经了然于胸。但是 setState为何会表现同步这个问题,似乎还是没有从当前展示出来的源码里得到根本上的回答。这是因为 batchedUpdates这个方法,不仅仅会在 setState之后才被调用。若我们在 React 源码中全局搜索 batchedUpdates,会发现调用它的地方很多,但与更新流有关的只有这两个地方:

// ReactMount.js
_renderNewRootComponent: function( nextElement, container, shouldReuseMarkup, context ) {
  // 实例化组件
  var componentInstance = instantiateReactComponent(nextElement);
  // 初始渲染直接调用 batchedUpdates 进行同步渲染
  ReactUpdates.batchedUpdates(
    batchedMountComponentIntoNode,
    componentInstance,
    container,
    shouldReuseMarkup,
    context
  );
  ...
}

这段代码是在首次渲染组件时会执行的一个方法,我们看到它内部调用了一次 batchedUpdates,这是因为在组件的渲染过程中,会按照顺序调用各个生命周期函数。开发者很有可能在声明周期函数中调用 setState。因此,我们需要通过开启 batch来确保所有的更新都能够进入 dirtyComponents里去,进而确保初始渲染流程中所有的 setState都是生效的。

下面代码是 React 事件系统的一部分。当我们在组件上绑定了事件之后,事件中也有可能会触发 setState。为了确保每一次 setState都有效,React同样会在此处手动开启批量更新。

// ReactEventListener.js
dispatchEvent: function (topLevelType, nativeEvent) {
  ...
  try {
    // 处理事件
    ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
  } finally {
    TopLevelCallbackBookKeeping.release(bookKeeping);
  }
}

话说到这里,一切都变得明朗了起来:isBatchingUpdates这个变量,在 React 的生命周期函数以及合成事件执行前,已经被 React 悄悄修改为了 true,这时我们所做的 setState操作自然不会立即生效。当函数执行完毕后,事务的 close方法会再把 isBatchingUpdates改为 false。

以开头示例中的 increment 方法为例,整个过程像是这样:

  handleIncrement = () => {
    // 进来先锁上
    isBatchingUpdates = true;
    console.log('increment setState前的count', this.state.count);
    this.setState({
      count: this.state.count + 1
    });
    console.log('increment setState后的count', this.state.count);
    // 执行完函数再放开
    isBatchingUpdates = false;
  };

很明显,在 isBatchingUpdates的约束下,setState只能是异步的。而当 setTimeout从中作祟时,事情就会发生一点点变化:

  handleReduce = () => {
    // 进来先锁上
    isBatchingUpdates = true;
    setTimeout(() => {
      console.log('reduce setState前的count', this.state.count);
      this.setState({
        count: this.state.count - 1
      });
      console.log('reduce setState后的count', this.state.count);
    }, 0);
    // 执行完函数再放开
    isBatchingUpdates = false;
  };

会发现,咱们开头锁上的那个 isBatchingUpdates,对 setTimeout内部的执行逻辑完全没有约束力。因为 isBatchingUpdates是在同步代码中变化的,而 setTimeout的逻辑是异步执行的。当 this.setState调用真正发生的时候,isBatchingUpdates早已经被重置为了 false,这就使得当前场景下的 setState 具备了立刻发起同步更新的能力。所以咱们前面说的没错——setState 并不是具备同步这种特性,只是在特定的情境下,它会从 React 的异步管控中“逃脱”掉。

对整个 setState 工作流做一个总结:

setState并不是单纯同步/异步的,它的表现会因调用场景的不同而不同:在 React 钩子函数及合成事件中,它表现为异步;而在 setTimeout、setInterval等函数中,包括在 DOM 原生事件中,它都表现为同步。这种差异,本质上是由 React 事务机制和批量更新机制的工作方式来决定的。

标签:count,异步,React,更新,react,组件,isBatchingUpdates,setState
From: https://blog.csdn.net/qq_42931285/article/details/136780616

相关文章

  • 【原创】三十分钟实时数据可视化网站前后端教程 Scrapy + Django + React 保姆级教程
    这个本来是想做视频的,所以是以讲稿的形式写的。最后没做视频,但是觉得这篇文还是值得记录一下。真的要多记录,不然一些不常用的东西即使做过几个月又有点陌生了。文章目录爬虫SCRAPYxpath后端DJANGO前端REACTHello大家好这里是小鱼,最近我做了一个前后端分离的实......
  • React — zustand状态管理工具
    zustand是一个用于状态管理的简单而强大的库,它可以与React一起使用。它提供了一种简单的方式来管理组件的状态,并且遵循了ReactHooks的使用规范。使用zustand可以方便地创建和共享状态,同时还能够实现状态的订阅和更新。通过zustand,你可以创建自定义的状态钩子,并在组件中......
  • 前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步
    目录ReactsetState调用的原理ReactsetState调用之后发生了什么?是同步还是异步?ReactsetState调用之后发生了什么?setState是同步还是异步的ReactsetState调用的原理在React中,setState方法是用于更新组件状态的重要方法。当setState被调用时,React会对组件进......
  • 直播带货源码,异步处理中会处理两次请求
    直播带货源码,异步处理中会处理两次请求从序列图上可以看到SpringMVC在处理异步请求时,DispatcherServlet会处理两次请求具体来看HandlerAdapter的处理过程//根据HandlerMethod解析参数并完成过程调用得到一个ModelAndViewprivateModelAndViewinvokeHandleMethod(Ht......
  • 视频直播系统源码,异步处理实现代码分析
    视频直播系统源码,异步处理实现代码分析@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{System.out.println("doget");method3(request,response);}/***使用asyncConte......
  • 微信小程序开发:异步处理接入的生成式图像卡通化
    书接上文,我们完成了对接阿里云人像动漫化接口,现已完成的界面是这样的: 就是效果看着一般,看看效果: 然后我就在阿里云api市场转悠,就想看看还有没有什么其他奇奇怪怪的api,结果就发现了这个:api链接这里:https://help.aliyun.com/zh/viapi/api-generative-image-cartoon ......
  • React — Class类组件
    一、Class类组件基础模板import'./App.css';import{Component}from'react'classCounterextendsComponent{//编写组件的逻辑代码//1.状态变量事件回调UI//2.定义状态变量state={count:0}setCount=()=>{this.setState({c......
  • Django和Fastapi异步性能对比
    突发奇想,我想验证一下Python的异步后端框架的并发能力.目前主流的异步框架有Fastapi,Django和Tornado.顺便我想对比一下它们的性能,但是考虑到Tornado自成一派没有遵循Asgi,没办法屏蔽服务器部分的代码效率,所以我打算只拿Fastapi和Django做下对比.实验设计运行设备:4核8进......
  • iOS端创建ReactNative容器第一步:打出jsbundle和资源包
    react-native的打包流程是通过执行react-nativebundle指令进行的。 添加构建指令修改RN项目中的package.json文件,先其中添加构建命令build-release-ios和build-debug-ios"scripts":{"android":"react-nativerun-android","ios":"react-nativerun-ios"......
  • React-hook-form-mui(二):表单数据处理
    前言在上一篇文章中,我们介绍了react-hook-form-mui的基础用法。本文将着表单数据处理。react-hook-form-mui提供了丰富的表单数据处理功能,可以通过watch属性来获取表单数据。Demo下面是一个使用watch属性的例子:importReactfrom'react';import{useForm}from're......