当前位置: 首页 > 科技观察

React中的setState是宏任务还是微任务?

时间:2023-03-12 19:00:51 科技观察

最近有个朋友面试,面试官问了一个很精彩的问题,就是我写在标题里的问题。能够问出这个问题,应该是面试官对React了解不多,也可能是面试官在简历中写了熟悉React。面试官想通过这个问题来判断面试官是否真的熟悉React??。面试官的问题是否正确?面试官的问题是setState是宏还是微任务,所以在他的认知中,setState一定是异步操作。为了判断setState是否为异步操作,可以先做个实验,通过CRA新建一个React工程,在工程中编辑如下代码:importReactfrom'react';importlogofrom'./logo.svg';import'。/应用程序。css';classAppextendsReact.Component{state={count:1000}render(){return(

我的关注者数量:{this.state.count}

);}}exportdefaultApp;页面是这样的:上面的ReactLogo绑定了一个点击事件,现在需要实现这个点击事件。点击Logo后,进行一次setState操作,设置完成后打印log,并在设置操作前添加宏任务和微任务。代码如下:handleClick=()=>{constfans=Math.floor(Math.random()*10)setTimeout(()=>{console.log('宏任务触发器')})Promise.resolve().then(()=>{console.log('微任务触发')})this.setState({count:this.state.count+fans},()=>{console.log('微任务数量newfans:',fans)})}显然,点击Logo后,先完成setState操作,然后是微任务的触发和宏任务的触发。所以setState的执行时机早于microtasks和macrotasks。即便如此,也只能说它的执行时机早于Promise.then,并不能证明它是一个同步任务。handleClick=()=>{constfans=Math.floor(Math.random()*10)console.log('开始运行')this.setState({count:this.state.count+fans},()=>{console.log('新增粉丝数:',fans)})console.log('结束运行')}这样看,好像setState又是一个异步操作。主要是因为在React和绑定的事件流的生命周期中,所有的setState操作都会先缓存到一个队列中,等整个事件结束或者mount过程结束后,再把之前缓存的setState队列取出一次计算并触发状态更新。只要我们跳出React的事件流或生命周期,就可以打破React对setState的控制。最简单的方法是将setState放在setTimeout的匿名函数中。handleClick=()=>{setTimeout(()=>{constfans=Math.floor(Math.random()*10)console.log('开始运行')this.setState({count:this.state.count+fans},()=>{console.log('新增粉丝数:',fans)})console.log('结束运行')})}所以setState是一个同步行为,面试官没问。React是如何控制setState的?在前面的例子中,setState只是变得像setTimeout中的一个同步方法。这是如何运作的?handleClick=()=>{//正常运行this.setState({count:this.state.count+1})}handleClick=()=>{//脱离React控制的运行setTimeout(()=>{this.setState({count:this.state.count+fans})})}先回顾一下前面的代码。在这两个操作中,我们在Performance中记录一次调用栈,看看两个调用栈的区别。不受React控制的正常运行在调用栈中,可以看到Component.setState方法最终会调用enqueueSetState方法,enqueueSetState方法内部会调用scheduleUpdateOnFiber方法。不同的是,正常调用时,scheduleUpdateOnFiber方法只会调用ensureRootIsScheduled。事件方法结束后调用flushSyncCallbackQueue方法。离开React事件流时,scheduleUpdateOnFiber会在ensureRootIsScheduled调用完成后直接调用flushSyncCallbackQueue方法。此方法用于更新状态和重新渲染。functionsscheduleUpdateOnFiber(fiber,lane,eventTime){if(lane===SyncLane){//同步操作ensureRootIsScheduled(root,eventTime);//判断是否还在React事件流中//如果不在,直接调用flushSyncCallbackQueue来updateif(executionContext===NoContext){flushSyncCallbackQueue();}}else{//异步操作}}上面的代码可以简单描述一下这个过程,主要是通过判断executionContext是否等于NoContext来判断当前是否更新过程在React事件流中。众所周知,React在绑定事件时,会合成事件并统一绑定到文档上(react@17已经变成绑定事件渲染时指定的DOM元素),最后由React派发。当所有的事件都被触发后,首先会调用batchedEventUpdates$1方法,这里会修改executionContext的值,所以React此时就知道setState在自己的控制之下了。//executionContext的默认状态varexecutionContext=NoContext;functionbatchedEventUpdates$1(fn,a){varprevExecutionContext=executionContext;executionContext|=EventContext;//修改状态try{returnfn(a);}finally{executionContext=prevExecutionContext;//之后调用结束,调用flushSyncCallbackQueueif(executionContext===NoContext){flushSyncCallbackQueue();}}}所以不管是直接调用flushSyncCallbackQueue还是推迟调用,这里本质上是同步的,只是存在顺序问题。以后会有异步的setState。仔细看上面的代码,会发现在scheduleUpdateOnFiber方法中,会判断lane是否同步。是否存在异步情况?functionsscheduleUpdateOnFiber(fiber,lane,eventTime){if(lane===SyncLane){//同步操作ensureRootIsScheduled(root,eventTime);//判断是否还在React事件流中//如果不在,直接调用flushSyncCallbackQueue来更新if(executionContext===NoContext){flushSyncCallbackQueue();}}else{//异步操作}}React在两年前升级fiber架构的时候,就为它的异步做了准备。在React18中,Concurrent模式将正式发布。关于Concurrent模式,官方介绍如下。什么是并发模式?并发模式是一组新的React功能,可帮助应用程序保持响应并根据用户的设备功能和互联网速度进行适当调整。在并发模式下,渲染不会阻塞。它是可中断的。这改善了用户体验。它同时解锁了以前不可能的新功能。现在如果你想使用Concurrent模式,你需要使用React的实验版本。本文转载自微信公众号“自然醒笔记本”,可通过以下二维码关注。转载本文请联系自然觉醒的笔记本公众号。