首页 > 其他分享 >面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”

时间:2022-10-18 12:04:32浏览次数:41  
标签:异步 面试官 批量 DOM 状态 更新 setState

这一次,我将带你一次性搞懂 React 中常见的 setState 原理。

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_流程图

setState 本身的默认行为

在进入主题之前,你肯定需要先学会 React 的基本使用。如果不会,请点赞离开;如果会用 React ,那就点赞收藏后离开(●'◡'●)。

我们在使用 React 的时候,经常会用到 state(一句废话),但是真正能完全搞清楚 setState 的帅哥美女,确实没几个。毕竟程序员都不太可能像我一样博学(和好看)。那么,要搞清楚它,应该去投胎(整容)吗?

不,你需要先搞清楚 setState 本身的默认行为。

其实也很简单,我们都知道,setState可以传递对象形式的状态,也可以传递函数形式的状态。而不论状态是对象形式还是函数形式,它都会先将所有状态保存起来,然后进行状态合并,所有状态合并完成后再进行一次性 DOM 更新。

如果状态是对象形式,后面的状态会直接覆盖前面的状态。类似于 Object.assign() 的合并操作。

对于对象状态这一点,我们有请翠花,上代码:

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_批量更新_02

运行代码,Dom 中展示的结果为 1。很显然两次 setState 只有一次生效了。

真的吗?其实两次都有生效,只不过这两次 setState 在执行前,被合并成了一个。你不能说到底是那个生效,你可以说两个都没生效,因为最终执行的是被合并的那个代码。

如果状态是函数形式,那么依次调用函数进行状态累积,所有函数调用完成后, 得到最终状态,最终进行一次性 DOM 更新。

翠花,再来一段代码……

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_react_03

明显不一样的结果就能说明,两次都执行了,因为函数状态并不会合并,而是以此运行。

好了,翠花可以先下去休息了,前置只是我们已经梳理完了,那么,对于 setState 的研究就结束了吗?当然不是,接下来,让我们换个场子,继续掰滔(battle)。

setState 同步 OR 异步

在面试场景中,只要和 React 相关,面试官一定会舔着脸问你:“ 宝子,setState 是同步还是异步的呀?” 。

面对这样的无耻刁难,我们需要先明确,从 API 层面上说,它就是普通的调用执行的函数,自然是同步 API 。

因此,这里所说的同步和异步指的是 API 调用后更新 DOM 是同步还是异步的。

来,我们有请娜塔莎,上代码……

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_事件处理_04

果然,洋妹子端上来的代码确实不好消化,通过结果我们发现,非常奇怪的一个现象:

  • 第一次事件执行显然为异步的,先打印了两个 0,Dom 随之改变为 1 ;
  • 第二次同样是异步的,但是我们发现多次执行没效果 (异步?);
  • 而第三次又是同步执行的了;

这什么情况,洋妹子给我们下了迷药吗?看我葵花宝典戳破它。

先说结论,首先,同步和异步主要取决于它被调用的环境

  • 如果 setState 在 React 能够控制的范围被调用,它就是异步的。

比如合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行 DOM 更新。

  • 如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。

比如原生事件处理函数中, 定时器回调函数中, Ajax 回调函数中, 此时 setState 被调用后会立即更新 DOM 。

为什么会这样呢?

其实,我们看到的所谓的 “异步”,是开启了 “批量更新” 模式的。

批量更新模式可以减少真实 DOM 渲染的次数,所以只要是 React 能够控制的范围,出于性能因素考虑,一定是批量更新模式。批量更新会先合并状态,再一次性做 DOM 更新。

那么假设没有批量更新呢?

从生命周期的角度来看,每一次的 setState 都是一个完整的更新流程,这里面就包含了重新渲染 (re-render) 在内的很多操作,大体的流程如下:

shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate;

re-render 本身涉及对 DOM 的操作,它会带来较大的性能开销。假如说 “一次 setState 就触发一个完整的更新流程” 这个结论成立,那么每一次 setState的调用都会触发一次 re-render,我们的视图很可能没刷新几次就卡死了,渲染就会出现下面这样的流程:

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_setstate_05

因此,setState 异步(或者说是批量更新)的一个重要动机就是避免频繁的 re-render

在实际的 React 运行时中,setState 异步的实现方式有点类似于浏览器里的 Event-Loop:

每来一个setState,就把它塞进一个队列里。等时机成熟,再把队列里的 state 结果做合并,最后只针对最新的 state 值走一次更新流程。

这个过程,叫作“批量更新”,批量更新的过程正如下面代码中的箭头流程图所示:

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_react_06

只要我们的同步代码还在执行,“进队列” 这个动作就不会停止。因此就算我们在React 中写了一个 N 次的 setState 循环,也只是会增加 state 任务入队的次数,并不会带来频繁的 re-render。当 N 次调用结束后,仅仅是 state 的任务队列内容发生了变化, state 本身并不会立刻改变。

为了更好地让你吃下娜塔莎,哦不对,是娜塔莎端上来的美食,我帮你梳理了 setState 的执行流程图:

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_流程图_07

当然,你可能看不懂这个流程图(是有多笨啊),没关系,下面还会有的。

如果为非批量更新模式,调用多少次 setState 就会渲染多少次真实 DOM,性能较低。

但是我们在某些条件下需要对 JS 控制的区域实现批量更新 ( 异步更新 DOM ) ,那应该怎么做呢?

强制批量更新

其实很简单,我都不好意思说 so easy ,因为这玩意简直就是 so TM 的 easy 。

我们只需要将代码包裹在 unstable_batchedUpdates 方法的回调函数中就可以实现强制批量更新。

具体使用方式也很简单,从 react-dom 中引入进来,然后将代码放入调用函数中就可以了。

(翠花和娜塔莎结婚了,我来给大家上代码)

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_事件处理_08

截止到现在,我们成就了一对完美的爱情,啊,呸~

我们基本搞清楚了原理流程,那具体的代码是如何实现的呢?

在 setState 的调用中,有一个合成事件起到了关键性的作用。接下来,我们先去搞清楚这个小可爱,再来看具体的 setState 的代码实现。

合成事件

首先明确定义,在 React 中为元素添加的事件被叫做合成事件。

合成事件的好处有两个:

  • 一是屏蔽了浏览器之间关于事件处理的兼容性问题,为合成事件对象内部提供了统一的 API;
  • 二是性能的提升, 事件都被委托给 document 。

React 并不会将事件添加到真正的 DOM 元素身上,它会将所有事件委托给 document 执行。如下图所示:

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_流程图_09

React 会在拥有事件的 DOM 对象身上添加一个 store 对象,在 store 对象中存储事件名称及事件处理函数,然后通过 document 分发事件。

当事件被触发后,通过获取事件源对象,查看事件源对象中是否存在 store 对象,获取 store 对象中事件处理函数,执行事件处理函数。

合成事件的事件对象在使用完成以后会被销毁。我长得帅,所以,我写了一段模拟性的代码,你看不看就随意了:

面试官:“年轻人,我看你很懂setState原理,你来说说是同步还是异步的?”_批量更新_10

代码的注释中说的已经非常清楚了,爱看不看吧,就这样……

setState 实现原理

接着,我们再把上面的图拿出来,我分为了四段,进行了具体的梳理。先看图,再看字,最后上代码

标签:异步,面试官,批量,DOM,状态,更新,setState
From: https://blog.51cto.com/u_15771948/5765707

相关文章

  • vue3+vite 使用defineAsyncComponent动态异步引入组件出错时的解决办法
    constname='c1'constcurrentComponent=shallowRef()constcomponents=import.meta.glob("./a/*.vue");currentComponent.value=defineAsyncComponent(compon......
  • SpringBoot异步线程,父子线程数据传递的5种方案
    背景在上一篇《SpringBoot+@Async开启异步,快的飞起(https://blog.51cto.com/u_15339304/5715380)》文章种我们介绍了使用springboot自定义线程池的方式实现多线程的异步......
  • 使用async与await的异步函数同步化
    async与await当ajax需要按顺序出场时,需要用到async与await方法了async放在函数的前面表示此函数是一个异步函数  await放在async的内部 公示当前代码应该做......
  • 面试官:深度不够,建议回去深挖
    作者:小傅哥博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!......
  • 面试官:ArrayList扩容机制,你了解吗?
    最近耗时5个月整理的10w字Java面试手册,涵盖了Java面试几乎都会问的面试题目,直达链接​​10w字Java面试手册​​小熊学Java在线地址:https://javaxiaobear.gitee.io/1、底层......
  • ACTION方法的异步
    1、Action方法既可以同步也可以异步2、异步Action方法的名字一般不需要一Async结尾3、WebAPI中Action方法的返回值如果是普通数据类型,那么返回值就会默认被序列化为Json格......
  • Muduo库之异步日志
    该框架中的日志为诊断日志,用于将代码运行时的重要信息进行保存,方便故障诊断和追踪。日志通常分为如下两种:同步日志:当需要写出一条日志消息时,只有等到这条日志消息完全写......
  • 一个优雅的异步爬虫基类
    这是我编写的一个异爬虫基类,有以下几点优点使用接口继承的思想继承Crawler类后,必须实现parse和handle方法,否则程序报错,虽然Python没有接口的特性,但是使用raise方法抛出......
  • 利用for循环同步执行异步方法
    //定义一个异步函数constfoo1=(i)=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{console.log(I)resolve(i)},3000......
  • SpringBoot(三) - Slf4j+logback 日志,异步请求,定时任务
    1、Slf4j+logback日志SpringBoot框架的默认日志实现:slf4j+logback;默认日志级别:info,对应了实际生产环境日志级别;1.1日志级别#常见的日志框架中,日志级别都包含五种,......