<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React 中 setState 是一個(gè)宏任務(wù)還是微任務(wù)?

          共 4461字,需瀏覽 9分鐘

           ·

          2021-08-05 05:27

          最近有個(gè)朋友面試,面試官問(wèn)了個(gè)奇葩的問(wèn)題,也就是我寫(xiě)在標(biāo)題上的這個(gè)問(wèn)題。

          09f6324cb7e3bfce6b7d5b1d18fd00cc.webp

          能問(wèn)出這個(gè)問(wèn)題,面試官應(yīng)該對(duì) React 不是很了解,也是可能是看到面試者簡(jiǎn)歷里面有寫(xiě)過(guò)自己熟悉 React,面試官想通過(guò)這個(gè)問(wèn)題來(lái)判斷面試者是不是真的熟悉 React ??。

          面試官的問(wèn)法是否正確?

          面試官的問(wèn)題是,setState 是一個(gè)宏任務(wù)還是微任務(wù),那么在他的認(rèn)知里,setState 肯定是一個(gè)異步操作。為了判斷 setState 到底是不是異步操作,可以先做一個(gè)實(shí)驗(yàn),通過(guò) CRA 新建一個(gè) React 項(xiàng)目,在項(xiàng)目中,編輯如下代碼:

          import?React?from?'react';
          import?logo?from?'./logo.svg';
          import?'./App.css';

          class?App?extends?React.Component?{
          ??state?=?{
          ????count:?1000
          ??}
          ??render()?{
          ????return?(
          ??????<div?className="App">
          ????????<img
          ??????????src={logo}?alt="logo"
          ??????????className="App-logo"
          ??????????onClick={this.handleClick}
          ????????/>

          ????????<p>我的關(guān)注人數(shù):{this.state.count}</p>
          ??????</div>

          ????);
          ??}
          }

          export?default?App;

          頁(yè)面大概長(zhǎng)這樣:

          dda017b4a9fb8340bf56cf332a439013.webp

          上面的 React Logo 綁定了一個(gè)點(diǎn)擊事件,現(xiàn)在需要實(shí)現(xiàn)這個(gè)點(diǎn)擊事件,在點(diǎn)擊 Logo 之后,進(jìn)行一次 setState 操作,在 set 操作完成時(shí)打印一個(gè) log,并且在 set 操作之前,分別添加一個(gè)宏任務(wù)和微任務(wù)。代碼如下:

          handleClick?=?()?=>?{
          ??const?fans?=?Math.floor(Math.random()?*?10)
          ??setTimeout(()?=>?{
          ????console.log('宏任務(wù)觸發(fā)')
          ??})
          ??Promise.resolve().then(()?=>?{
          ????console.log('微任務(wù)觸發(fā)')
          ??})
          ??this.setState({
          ????count:?this.state.count?+?fans
          ??},?()?=>?{
          ????console.log('新增粉絲數(shù):',?fans)
          ??})
          }
          a8f0cd79e47056c79da23ffcc2220c17.webp

          很明顯,在點(diǎn)擊 Logo 之后,先完成了 setState 操作,然后再是微任務(wù)的觸發(fā)和宏任務(wù)的觸發(fā)。所以,setState 的執(zhí)行時(shí)機(jī)是早于微任務(wù)與宏任務(wù)的,即使這樣也只能說(shuō)它的執(zhí)行時(shí)機(jī)早于 Promise.then,還不能證明它就是同步任務(wù)。

          handleClick?=?()?=>?{
          ??const?fans?=?Math.floor(Math.random()?*?10)
          ??console.log('開(kāi)始運(yùn)行')
          ??this.setState({
          ????count:?this.state.count?+?fans
          ??},?()?=>?{
          ????console.log('新增粉絲數(shù):',?fans)
          ??})
          ??console.log('結(jié)束運(yùn)行')
          }
          aabe5581f146b2767b54b96a43851709.webp

          這么看,似乎 setState 又是一個(gè)異步的操作。主要原因是,在 React 的生命周期以及綁定的事件流中,所有的 setState 操作會(huì)先緩存到一個(gè)隊(duì)列中,在整個(gè)事件結(jié)束后或者 mount 流程結(jié)束后,才會(huì)取出之前緩存的 setState 隊(duì)列進(jìn)行一次計(jì)算,觸發(fā) state 更新。只要我們跳出 React 的事件流或者生命周期,就能打破 React 對(duì) setState 的掌控。最簡(jiǎn)單的方法,就是把 setState 放到 setTimeout 的匿名函數(shù)中。

          handleClick?=?()?=>?{
          ??setTimeout(()?=>?{
          ????const?fans?=?Math.floor(Math.random()?*?10)
          ????console.log('開(kāi)始運(yùn)行')
          ????this.setState({
          ??????count:?this.state.count?+?fans
          ????},?()?=>?{
          ??????console.log('新增粉絲數(shù):',?fans)
          ????})
          ????console.log('結(jié)束運(yùn)行')
          ??})
          }
          f0d823d09f7231334183dc278082fa94.webp

          所以,setState 就是一次同步行為,根本不存在面試官的問(wèn)題。

          React 是如何控制 setState 的 ?

          前面的案例中,setState 只有在?setTimeout?中才會(huì)變得像一個(gè)同步方法,這是怎么做到的?

          handleClick?=?()?=>?{
          ??//?正常的操作
          ??this.setState({
          ????count:?this.state.count?+?1
          ??})
          }
          handleClick?=?()?=>?{
          ??//?脫離?React?控制的操作
          ??setTimeout(()?=>?{
          ????this.setState({
          ??????count:?this.state.count?+?fans
          ????})
          ??})
          }

          先回顧之前的代碼,在這兩個(gè)操作中,我們分別在 Performance 中記錄一次調(diào)用棧,看看兩者的調(diào)用棧有何區(qū)別。

          d2c1028b91c89e439608e6e5d024b006.webp正常操作ffcfb4163e81811605af2fe327cf6e5d.webp脫離 React 控制的操作

          在調(diào)用棧中,可以看到 Component.setState 方法最終會(huì)調(diào)用?enqueueSetState?方法,而 enqueueSetState 方法內(nèi)部會(huì)調(diào)用 scheduleUpdateOnFiber 方法,區(qū)別就在于正常調(diào)用的時(shí)候,scheduleUpdateOnFiber 方法內(nèi)只會(huì)調(diào)用 ensureRootIsScheduled ,在事件方法結(jié)束后,才會(huì)調(diào)用 ?flushSyncCallbackQueue?方法。而脫離 React 事件流的時(shí)候,scheduleUpdateOnFiberensureRootIsScheduled 調(diào)用結(jié)束后,會(huì)直接調(diào)用 flushSyncCallbackQueue 方法,這個(gè)方法就是用來(lái)更新 state 并重新進(jìn)行 render 。

          afacd0af9074c727e79f792d361b6204.webpcaddc10363e5327233a9f257aedc887d.webp
          function?scheduleUpdateOnFiber(fiber,?lane,?eventTime)?{
          ??if?(lane?===?SyncLane)?{
          ????//?同步操作
          ????ensureRootIsScheduled(root,?eventTime);
          ????//?判斷當(dāng)前是否還在?React?事件流中
          ????//?如果不在,直接調(diào)用?flushSyncCallbackQueue?更新
          ????if?(executionContext?===?NoContext)?{
          ??????flushSyncCallbackQueue();
          ????}
          ??}?else?{
          ????//?異步操作
          ??}
          }

          上述代碼可以簡(jiǎn)單描述這個(gè)過(guò)程,主要是判斷了 executionContext 是否等于 NoContext 來(lái)確定當(dāng)前更新流程是否在 React 事件流中。

          眾所周知,React 在綁定事件時(shí),會(huì)對(duì)事件進(jìn)行合成,統(tǒng)一綁定到?document?上(?react@17?有所改變,變成了綁定事件到?render?時(shí)指定的那個(gè) DOM 元素),最后由 React 來(lái)派發(fā)。

          所有的事件在觸發(fā)的時(shí)候,都會(huì)先調(diào)用?batchedEventUpdates$1?這個(gè)方法,在這里就會(huì)修改?executionContext?的值,React 就知道此時(shí)的?setState?在自己的掌控中。

          //?executionContext?的默認(rèn)狀態(tài)
          var?executionContext?=?NoContext;
          function?batchedEventUpdates$1(fn,?a)?{
          ??var?prevExecutionContext?=?executionContext;
          ??executionContext?|=?EventContext;?//?修改狀態(tài)
          ??try?{
          ????return?fn(a);
          ??}?finally?{
          ????executionContext?=?prevExecutionContext;
          ??//?調(diào)用結(jié)束后,調(diào)用?flushSyncCallbackQueue
          ????if?(executionContext?===?NoContext)?{
          ??????flushSyncCallbackQueue();
          ????}
          ??}
          }

          86b32013c481ed0b7ee64a3c5d2af29b.webp


          所以,不管是直接調(diào)用 flushSyncCallbackQueue ,還是推遲調(diào)用,這里本質(zhì)上都是同步的,只是有個(gè)先后順序的問(wèn)題。

          未來(lái)會(huì)有異步的 setState

          如果你有認(rèn)真看上面的代碼,你會(huì)發(fā)現(xiàn)在 scheduleUpdateOnFiber 方法內(nèi),會(huì)判斷 lane 是否為同步,那么是不是存在異步的情況?

          function?scheduleUpdateOnFiber(fiber,?lane,?eventTime)?{
          ??if?(lane?===?SyncLane)?{
          ????//?同步操作
          ????ensureRootIsScheduled(root,?eventTime);
          ????//?判斷當(dāng)前是否還在?React?事件流中
          ????//?如果不在,直接調(diào)用?flushSyncCallbackQueue?更新
          ????if?(executionContext?===?NoContext)?{
          ??????flushSyncCallbackQueue();
          ????}
          ??}?else?{
          ????//?異步操作
          ??}
          }

          React 在兩年前,升級(jí) fiber 架構(gòu)的時(shí)候,就是為其異步化做準(zhǔn)備的。在 React 18 將會(huì)正式發(fā)布 Concurrent 模式,關(guān)于 Concurrent 模式,官方的介紹如下。

          912987dcd60339199dd66967cdb489d9.webp

          什么是 Concurrent 模式?

          Concurrent 模式是一組 React 的新功能,可幫助應(yīng)用保持響應(yīng),并根據(jù)用戶(hù)的設(shè)備性能和網(wǎng)速進(jìn)行適當(dāng)?shù)恼{(diào)整。在 Concurrent 模式中,渲染不是阻塞的。它是可中斷的。這改善了用戶(hù)體驗(yàn)。它同時(shí)解鎖了以前不可能的新功能。

          現(xiàn)在如果想使用 Concurrent 模式,需要使用 React 的實(shí)驗(yàn)版本。如果你對(duì)這部分內(nèi)容感興趣可以閱讀我之前的文章:《React 架構(gòu)的演變 - 從同步到異步》

          - END -


          瀏覽 39
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久精品国产99国产精品导航 | 苍井空高潮A片 | 国产日韩欧美在线 | 久久对白| 五月成人激情 |