<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 架構(gòu)的演變 - 從同步到異步

          共 13752字,需瀏覽 28分鐘

           ·

          2020-10-30 11:46

          寫這篇文章的目的,主要是想弄懂 React 最新的 fiber 架構(gòu)到底是什么東西,但是看了網(wǎng)上的很多文章,要不模棱兩可,要不就是一頓復(fù)制粘貼,根本看不懂,于是開始認(rèn)真鉆研源碼。鉆研過程中,發(fā)現(xiàn)我想得太簡單了,React 源碼的復(fù)雜程度遠(yuǎn)超我的想象,于是打算分幾個模塊了剖析,今天先講一講 React 的更新策略從同步變?yōu)楫惒降难葑冞^程。

          從 setState 說起

          React 16 之所以要進(jìn)行一次大重構(gòu),是因?yàn)?React 之前的版本有一些不可避免的缺陷,一些更新操作,需要由同步改成異步。所以我們先聊聊 React 15 是如何進(jìn)行一次 setState 的。

          import?React?from?'react';

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??componentDidMount()?{
          ????//?第一次調(diào)用
          ????this.setState({?val:?this.state.val?+?1?});
          ????console.log('first?setState',?this.state);

          ????//?第二次調(diào)用
          ????this.setState({?val:?this.state.val?+?1?});
          ????console.log('second?setState',?this.state);

          ????//?第三次調(diào)用
          ????this.setState({?val:?this.state.val?+?1?},?()?=>?{
          ??????console.log('in?callback',?this.state)
          ????});
          ??}
          ??render()?{
          ????return?<div>?val:?{?this.state.val?}?div>
          ??}
          }

          export?default?App;

          熟悉 React 的同學(xué)應(yīng)該知道,在 React 的生命周期內(nèi),多次 setState 會被合并成一次,這里雖然連續(xù)進(jìn)行了三次 setState,state.val 的值實(shí)際上只重新計算了一次。

          render結(jié)果

          每次 setState 之后,立即獲取 state 會發(fā)現(xiàn)并沒有更新,只有在 setState 的回調(diào)函數(shù)內(nèi)才能拿到最新的結(jié)果,這點(diǎn)通過我們在控制臺輸出的結(jié)果就可以證實(shí)。

          控制臺輸出

          網(wǎng)上有很多文章稱 setState 是『異步操作』,所以導(dǎo)致 setState 之后并不能獲取到最新值,其實(shí)這個觀點(diǎn)是錯誤的。setState 是一次同步操作,只是每次操作之后并沒有立即執(zhí)行,而是將 setState 進(jìn)行了緩存,mount 流程結(jié)束或事件操作結(jié)束,才會拿出所有的 state 進(jìn)行一次計算。如果 setState 脫離了 React 的生命周期或者 React 提供的事件流,setState 之后就能立即拿到結(jié)果。

          我們修改上面的代碼,將 setState 放入 setTimeout 中,在下一個任務(wù)隊(duì)列進(jìn)行執(zhí)行。

          import?React?from?'react';

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??componentDidMount()?{
          ????setTimeout(()?=>?{
          ??????//?第一次調(diào)用
          ??????this.setState({?val:?this.state.val?+?1?});
          ??????console.log('first?setState',?this.state);
          ??
          ??????//?第二次調(diào)用
          ??????this.setState({?val:?this.state.val?+?1?});
          ??????console.log('second?setState',?this.state);
          ????});
          ??}
          ??render()?{
          ????return?<div>?val:?{?this.state.val?}?div>
          ??}
          }

          export?default?App;

          可以看到,setState 之后就能立即看到state.val 的值發(fā)生了變化。

          控制臺輸出

          為了更加深入理解 setState,下面簡單講解一下React 15 中 setState 的更新邏輯,下面的代碼是對源碼的一些精簡,并非完整邏輯。

          舊版本 setState 源碼分析

          setState 的主要邏輯都在 ReactUpdateQueue 中實(shí)現(xiàn),在調(diào)用 setState 后,并沒有立即修改 state,而是將傳入的參數(shù)放到了組件內(nèi)部的 _pendingStateQueue 中,之后調(diào)用 enqueueUpdate 來進(jìn)行更新。

          //?對外暴露的?React.Component
          function?ReactComponent()?{
          ??this.updater?=?ReactUpdateQueue;
          }
          //?setState?方法掛載到原型鏈上
          ReactComponent.prototype.setState?=?function?(partialState,?callback)?{
          ??//?調(diào)用?setState?后,會調(diào)用內(nèi)部的?updater.enqueueSetState
          ??this.updater.enqueueSetState(this,?partialState);
          ??if?(callback)?{
          ????this.updater.enqueueCallback(this,?callback,?'setState');
          ??}
          };

          var?ReactUpdateQueue?=?{
          ??enqueueSetState(component,?partialState)?{
          ????//?在組件的?_pendingStateQueue?上暫存新的?state
          ????if?(!component._pendingStateQueue)?{
          ??????component._pendingStateQueue?=?[];
          ????}
          ????var?queue?=?component._pendingStateQueue;
          ????queue.push(partialState);
          ????enqueueUpdate(component);
          ??},
          ??enqueueCallback:?function?(component,?callback,?callerName)?{
          ????//?在組件的?_pendingCallbacks?上暫存?callback
          ????if?(component._pendingCallbacks)?{
          ??????component._pendingCallbacks.push(callback);
          ????}?else?{
          ??????component._pendingCallbacks?=?[callback];
          ????}
          ????enqueueUpdate(component);
          ??}
          }

          enqueueUpdate 首先會通過 batchingStrategy.isBatchingUpdates 判斷當(dāng)前是否在更新流程,如果不在更新流程,會調(diào)用 batchingStrategy.batchedUpdates() 進(jìn)行更新。如果在流程中,會將待更新的組件放入 dirtyComponents 進(jìn)行緩存。

          var?dirtyComponents?=?[];
          function?enqueueUpdate(component)?{
          ??if?(!batchingStrategy.isBatchingUpdates)?{
          ???//?開始進(jìn)行批量更新
          ????batchingStrategy.batchedUpdates(enqueueUpdate,?component);
          ????return;
          ??}
          ??//?如果在更新流程,則將組件放入臟組件隊(duì)列,表示組件待更新
          ??dirtyComponents.push(component);
          }

          batchingStrategy 是 React 進(jìn)行批處理的一種策略,該策略的實(shí)現(xiàn)基于 Transaction,雖然名字和數(shù)據(jù)庫的事務(wù)一樣,但是做的事情卻不一樣。

          class?ReactDefaultBatchingStrategyTransaction?extends?Transaction?{
          ??constructor()?{
          ????this.reinitializeTransaction()
          ??}
          ??getTransactionWrappers?()?{
          ????return?[
          ??????{
          ????????initialize:?()?=>?{},
          ????????close:?ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
          ??????},
          ??????{
          ????????initialize:?()?=>?{},
          ????????close:?()?=>?{
          ??????????ReactDefaultBatchingStrategy.isBatchingUpdates?=?false;
          ????????}
          ??????}
          ????]
          ??}
          }

          var?transaction?=?new?ReactDefaultBatchingStrategyTransaction();

          var?batchingStrategy?=?{
          ??//?判斷是否在更新流程中
          ??isBatchingUpdates:?false,
          ??//?開始進(jìn)行批量更新
          ??batchedUpdates:?function?(callback,?component)?{
          ????//?獲取之前的更新狀態(tài)
          ????var?alreadyBatchingUpdates?=?ReactDefaultBatchingStrategy.isBatchingUpdates;
          ??//?將更新狀態(tài)修改為?true
          ????ReactDefaultBatchingStrategy.isBatchingUpdates?=?true;
          ????if?(alreadyBatchingUpdates)?{
          ??????//?如果已經(jīng)在更新狀態(tài)中,等待之前的更新結(jié)束
          ??????return?callback(callback,?component);
          ????}?else?{
          ??????//?進(jìn)行更新
          ??????return?transaction.perform(callback,?null,?component);
          ????}
          ??}
          };

          Transaction 通過 perform 方法啟動,然后通過擴(kuò)展的 getTransactionWrappers 獲取一個數(shù)組,該數(shù)組內(nèi)存在多個 wrapper 對象,每個對象包含兩個屬性:initialize、close。perform 中會先調(diào)用所有的 wrapper.initialize,然后調(diào)用傳入的回調(diào),最后調(diào)用所有的 wrapper.close。

          class?Transaction?{
          ?reinitializeTransaction()?{
          ????this.transactionWrappers?=?this.getTransactionWrappers();
          ??}
          ?perform(method,?scope,?...param)?{
          ????this.initializeAll(0);
          ????var?ret?=?method.call(scope,?...param);
          ????this.closeAll(0);
          ????return?ret;
          ??}
          ?initializeAll(startIndex)?{
          ????var?transactionWrappers?=?this.transactionWrappers;
          ????for?(var?i?=?startIndex;?i???????var?wrapper?=?transactionWrappers[i];
          ??????wrapper.initialize.call(this);
          ????}
          ??}
          ?closeAll(startIndex)?{
          ????var?transactionWrappers?=?this.transactionWrappers;
          ????for?(var?i?=?startIndex;?i???????var?wrapper?=?transactionWrappers[i];
          ??????wrapper.close.call(this);
          ????}
          ??}
          }
          transaction.perform

          React 源代碼的注釋中,也形象的展示了這一過程。

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

          我們簡化一下代碼,再重新看一下 setState 的流程。

          //?1.?調(diào)用?Component.setState
          ReactComponent.prototype.setState?=?function?(partialState)?{
          ??this.updater.enqueueSetState(this,?partialState);
          };

          //?2.?調(diào)用?ReactUpdateQueue.enqueueSetState,將?state?值放到?_pendingStateQueue?進(jìn)行緩存
          var?ReactUpdateQueue?=?{
          ??enqueueSetState(component,?partialState)?{
          ????var?queue?=?component._pendingStateQueue?||?(component._pendingStateQueue?=?[]);
          ????queue.push(partialState);
          ????enqueueUpdate(component);
          ??}
          }

          //?3.?判斷是否在更新過程中,如果不在就進(jìn)行更新
          var?dirtyComponents?=?[];
          function?enqueueUpdate(component)?{
          ??//?如果之前沒有更新,此時的?isBatchingUpdates?肯定是?false
          ??if?(!batchingStrategy.isBatchingUpdates)?{
          ????//?調(diào)用?batchingStrategy.batchedUpdates?進(jìn)行更新
          ????batchingStrategy.batchedUpdates(enqueueUpdate,?component);
          ????return;
          ??}
          ??dirtyComponents.push(component);
          }

          //?4.?進(jìn)行更新,更新邏輯放入事務(wù)中進(jìn)行處理
          var?batchingStrategy?=?{
          ??isBatchingUpdates:?false,
          ??//?注意:此時的 callback 為 enqueueUpdate
          ??batchedUpdates:?function?(callback,?component)?{
          ????var?alreadyBatchingUpdates?=?ReactDefaultBatchingStrategy.isBatchingUpdates;
          ????ReactDefaultBatchingStrategy.isBatchingUpdates?=?true;
          ????if?(alreadyBatchingUpdates)?{
          ??????//?如果已經(jīng)在更新狀態(tài)中,重新調(diào)用?enqueueUpdate,將?component?放入?dirtyComponents
          ??????return?callback(callback,?component);
          ????}?else?{
          ??????//?進(jìn)行事務(wù)操作
          ??????return?transaction.perform(callback,?null,?component);
          ????}
          ??}
          };

          啟動事務(wù)可以拆分成三步來看:

          1. 先執(zhí)行 wrapper 的 initialize,此時的 initialize 都是一些空函數(shù),可以直接跳過;
          2. 然后執(zhí)行 callback(也就是 enqueueUpdate),執(zhí)行 enqueueUpdate 時,由于已經(jīng)進(jìn)入了更新狀態(tài),batchingStrategy.isBatchingUpdates 被修改成了 true,所以最后還是會把 component 放入臟組件隊(duì)列,等待更新;
          3. 后面執(zhí)行的兩個 close 方法,第一個方法的 flushBatchedUpdates 是用來進(jìn)行組件更新的,第二個方法用來修改更新狀態(tài),表示更新已經(jīng)結(jié)束。
          getTransactionWrappers?()?{
          ??return?[
          ????{
          ??????initialize:?()?=>?{},
          ??????close:?ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
          ????},
          ????{
          ??????initialize:?()?=>?{},
          ??????close:?()?=>?{
          ????????ReactDefaultBatchingStrategy.isBatchingUpdates?=?false;
          ??????}
          ????}
          ??]
          }

          flushBatchedUpdates 里面會取出所有的臟組件隊(duì)列進(jìn)行 diff,最后更新到 DOM。

          function?flushBatchedUpdates()?{
          ??if?(dirtyComponents.length)?{
          ????runBatchedUpdates()
          ??}
          };

          function?runBatchedUpdates()?{
          ??//?省略了一些去重和排序的操作
          ??for?(var?i?=?0;?i?????var?component?=?dirtyComponents[i];

          ????//?判斷組件是否需要更新,然后進(jìn)行 diff 操作,最后更新 DOM。
          ????ReactReconciler.performUpdateIfNecessary(component);
          ??}
          }

          performUpdateIfNecessary() 會調(diào)用 Component.updateComponent(),在 updateComponent() 中,會從 _pendingStateQueue 中取出所有的值來更新。

          //?獲取最新的?state
          _processPendingState()?{
          ??var?inst?=?this._instance;
          ??var?queue?=?this._pendingStateQueue;

          ??var?nextState?=?{?...inst.state?};
          ??for?(var?i?=?0;?i?????var?partial?=?queue[i];
          ????Object.assign(
          ??????nextState,
          ??????typeof?partial?===?'function'???partial(inst,?nextState)?:?partial
          ???);
          ??}
          ??return?nextState;
          }
          //?更新組件
          updateComponent(prevParentElement,?nextParentElement)?{
          ??var?inst?=?this._instance;
          ??var?prevProps?=?prevParentElement.props;
          ??var?nextProps?=?nextParentElement.props;
          ??var?nextState?=?this._processPendingState();
          ??var?shouldUpdate?=?
          ??????!shallowEqual(prevProps,?nextProps)?||
          ??????!shallowEqual(inst.state,?nextState);
          ??
          ??if?(shouldUpdate)?{
          ????//?diff?、update?DOM
          ??}?else?{
          ????inst.props?=?nextProps;
          ????inst.state?=?nextState;
          ??}
          ??//?后續(xù)的操作包括判斷組件是否需要更新、diff、更新到?DOM
          }

          setState 合并原因

          按照剛剛講解的邏輯,setState 的時候,batchingStrategy.isBatchingUpdatesfalse 會開啟一個事務(wù),將組件放入臟組件隊(duì)列,最后進(jìn)行更新操作,而且這里都是同步操作。講道理,setState 之后,我們可以立即拿到最新的 state。

          然而,事實(shí)并非如此,在 React 的生命周期及其事件流中,batchingStrategy.isBatchingUpdates 的值早就被修改成了 true??梢钥纯聪旅鎯蓮垐D:

          Mount
          事件調(diào)用

          在組件 mount 和事件調(diào)用的時候,都會調(diào)用 batchedUpdates,這個時候已經(jīng)開始了事務(wù),所以只要不脫離 React,不管多少次 setState 都會把其組件放入臟組件隊(duì)列等待更新。一旦脫離 React 的管理,比如在 setTimeout 中,setState 立馬變成單打獨(dú)斗。

          Concurrent 模式

          React 16 引入的 Fiber 架構(gòu),就是為了后續(xù)的異步渲染能力做鋪墊,雖然架構(gòu)已經(jīng)切換,但是異步渲染的能力并沒有正式上線,我們只能在實(shí)驗(yàn)版中使用。異步渲染指的是 Concurrent 模式,下面是官網(wǎng)的介紹:

          Concurrent 模式是 React 的新功能,可幫助應(yīng)用保持響應(yīng),并根據(jù)用戶的設(shè)備性能和網(wǎng)速進(jìn)行適當(dāng)?shù)恼{(diào)整。

          優(yōu)點(diǎn)

          除了 Concurrent 模式,React 還提供了另外兩個模式, Legacy 模式依舊是同步更新的方式,可以認(rèn)為和舊版本保持一致的兼容模式,而 Blocking 模式是一個過渡版本。

          模式差異

          Concurrent 模式說白就是讓組件更新異步化,切分時間片,渲染之前的調(diào)度、diff、更新都只在指定時間片進(jìn)行,如果超時就暫停放到下個時間片進(jìn)行,中途給瀏覽器一個喘息的時間。

          瀏覽器是單線程,它將 GUI 描繪,時間器處理,事件處理,JS 執(zhí)行,遠(yuǎn)程資源加載統(tǒng)統(tǒng)放在一起。當(dāng)做某件事,只有將它做完才能做下一件事。如果有足夠的時間,瀏覽器是會對我們的代碼進(jìn)行編譯優(yōu)化(JIT)及進(jìn)行熱代碼優(yōu)化,一些 DOM 操作,內(nèi)部也會對 reflow 進(jìn)行處理。reflow 是一個性能黑洞,很可能讓頁面的大多數(shù)元素進(jìn)行重新布局。

          瀏覽器的運(yùn)作流程: 渲染 -> tasks -> 渲染 -> tasks -> 渲染 -> ....

          這些 tasks 中有些我們可控,有些不可控,比如 setTimeout 什么時候執(zhí)行不好說,它總是不準(zhǔn)時;資源加載時間不可控。但一些JS我們可以控制,讓它們分派執(zhí)行,tasks的時長不宜過長,這樣瀏覽器就有時間優(yōu)化 JS 代碼與修正 reflow !

          總結(jié)一句,就是讓瀏覽器休息好,瀏覽器就能跑得更快。

          -- by 司徒正美 《React Fiber架構(gòu)》

          模式差異

          這里有個 demo,上面是一個?圍繞??運(yùn)轉(zhuǎn)的動畫,下面是 React 定時 setState 更新視圖,同步模式下,每次 setState 都會造成上面的動畫卡頓,而異步模式下的動畫就很流暢。

          同步模式:

          同步模式

          異步模式:

          異步模式

          如何使用

          雖然很多文章都在介紹 Concurrent 模式,但是這個能力并沒有真正上線,想要使用只能安裝實(shí)驗(yàn)版本。也可以直接通過這個 cdn :https://unpkg.com/browse/[email protected]/ 。

          npm?install?react@experimental?react-dom@experimental

          如果要開啟 Concurrent 模式,不能使用之前的 ReactDOM.render,需要替換成 ReactDOM.createRoot,而在實(shí)驗(yàn)版本中,由于 API 不夠穩(wěn)定, 需要通過 ReactDOM.unstable_createRoot 來啟用 Concurrent 模式。

          import?ReactDOM?from?'react-dom';
          import?App?from?'./App';

          ReactDOM.unstable_createRoot(
          ??document.getElementById('root')
          ).render(<App?/>);

          setState 合并更新

          還記得之前 React15 的案例中,setTimeout 中進(jìn)行 setState ,state.val 的值會立即發(fā)生變化。同樣的代碼,我們拿到 Concurrent 模式下運(yùn)行一次。

          import?React?from?'react';

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??componentDidMount()?{
          ????setTimeout(()?=>?{
          ??????//?第一次調(diào)用
          ??????this.setState({?val:?this.state.val?+?1?});
          ??????console.log('first?setState',?this.state);
          ??
          ??????//?第二次調(diào)用
          ??????this.setState({?val:?this.state.val?+?1?});
          ??????console.log('second?setState',?this.state);
          ??????
          ??????this.setState({?val:?this.state.val?+?1?},?()?=>?{
          ????????console.log(this.state);
          ??????});
          ????});
          ??}
          ??render()?{
          ????return?<div>?val:?{?this.state.val?}?div>
          ??}
          }

          export?default?App;
          控制臺輸出

          說明在 Concurrent 模式下,即使脫離了 React 的生命周期,setState 依舊能夠合并更新。主要原因是 Concurrent 模式下,真正的更新操作被移到了下一個事件隊(duì)列中,類似于 Vue 的 nextTick。

          更新機(jī)制變更

          我們修改一下 demo,然后看下點(diǎn)擊按鈕之后的調(diào)用棧。

          import?React?from?'react';

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??clickBtn()?{
          ????this.setState({?val:?this.state.val?+?1?});
          ??}
          ??render()?{
          ????return?(<div>
          ??????<button?onClick={()?=>?{this.clickBtn()}}>click?addbutton>

          ??????<div>val:?{?this.state.val?}div>
          ????div>)
          ??}
          }

          export?default?App;
          調(diào)用棧
          調(diào)用棧

          onClick 觸發(fā)后,進(jìn)行 setState 操作,然后調(diào)用 enquueState 方法,到這里看起來好像和之前的模式一樣,但是后面的操作基本都變了,因?yàn)?React 16 中已經(jīng)沒有了事務(wù)一說。

          Component.setState()?=>?enquueState()?=>?scheduleUpdate()?=>?scheduleCallback()
          =>?requestHostCallback(flushWork)?=>?postMessage()

          真正的異步化邏輯就在 requestHostCallbackpostMessage 里面,這是 React 內(nèi)部自己實(shí)現(xiàn)的一個調(diào)度器:https://github.com/facebook/react/blob/v16.13.1/packages/scheduler/index.js。

          function?unstable_scheduleCallback(priorityLevel,?calback)?{
          ??var?currentTime?=?getCurrentTime();
          ??var?startTime?=?currentTime?+?delay;
          ??var?newTask?=?{
          ????id:?taskIdCounter++,
          ????startTime:?startTime,???????????//?任務(wù)開始時間
          ????expirationTime:?expirationTime,?//?任務(wù)終止時間
          ????priorityLevel:?priorityLevel,???//?調(diào)度優(yōu)先級
          ????callback:?callback,?????????????//?回調(diào)函數(shù)
          ??};
          ??if?(startTime?>?currentTime)?{
          ????//?超時處理,將任務(wù)放到?taskQueue,下一個時間片執(zhí)行
          ????//?源碼中其實(shí)是?timerQueue,后續(xù)會有個操作將?timerQueue?的?task?轉(zhuǎn)移到?taskQueue
          ???push(taskQueue,?newTask)
          ??}?else?{
          ????requestHostCallback(flushWork);
          ??}
          ??return?newTask;
          }

          requestHostCallback 的實(shí)現(xiàn)依賴于 MessageChannel,但是 MessageChannel 在這里并不是做消息通信用的,而是利用它的異步能力,給瀏覽器一個喘息的機(jī)會。說起 MessageChannel,Vue 2.5 的 nextTick 也有使用,但是 2.6 發(fā)布時又取消了。

          [email protected]

          MessageChannel 會暴露兩個對象,port1port2,port1 發(fā)送的消息能被 port2 接收,同樣 port2 發(fā)送的消息也能被 port1 接收,只是接收消息的時機(jī)會放到下一個 macroTask 中。

          var?{?port1,?port2?}?=?new?MessageChannel();
          //?port1?接收?port2?的消息
          port1.onmessage?=?function?(msg)?{?console.log('MessageChannel?exec')?}
          //?port2?發(fā)送消息
          port2.postMessage(null)

          new?Promise(r?=>?r()).then(()?=>?console.log('promise?exec'))
          setTimeout(()?=>?console.log('setTimeout?exec'))

          console.log('start?run')
          執(zhí)行結(jié)果

          可以看到,port1 接收消息的時機(jī)比 Promise 所在的 microTask 要晚,但是早于 setTimeout。React 利用這個能力,給了瀏覽器一個喘息的時間,不至于被餓死。

          還是之前的案例,同步更新時沒有給瀏覽器任何喘息,造成視圖的卡頓。

          同步更新

          異步更新時,拆分了時間片,給了瀏覽器充分的時間更新動畫。

          異步更新

          還是回到代碼層面,看看 React 是如何利用 MessageChannel 的。

          var?isMessageLoopRunning?=?false;?//?更新狀態(tài)
          var?scheduledHostCallback?=?null;?//?全局的回調(diào)
          var?channel?=?new?MessageChannel();
          var?port?=?channel.port2;

          channel.port1.onmessage?=?function?()?{
          ??if?(scheduledHostCallback?!==?null)?{
          ????var?currentTime?=?getCurrentTime();
          ????//?重置超時時間
          ????deadline?=?currentTime?+?yieldInterval;
          ????var?hasTimeRemaining?=?true;

          ????//?執(zhí)行?callback
          ????var?hasMoreWork?=?scheduledHostCallback(hasTimeRemaining,?currentTime);

          ????if?(!hasMoreWork)?{
          ??????//?已經(jīng)沒有任務(wù)了,修改狀態(tài)
          ??????isMessageLoopRunning?=?false;
          ??????scheduledHostCallback?=?null;
          ????}?else?{
          ??????//?還有任務(wù),放到下個任務(wù)隊(duì)列執(zhí)行,給瀏覽器喘息的機(jī)會
          ??????port.postMessage(null);
          ????}
          ??}?else?{
          ????isMessageLoopRunning?=?false;
          ??}
          };

          requestHostCallback?=?function?(callback)?{
          ??//?callback?掛載到?scheduledHostCallback
          ??scheduledHostCallback?=?callback;

          ??if?(!isMessageLoopRunning)?{
          ????isMessageLoopRunning?=?true;
          ????//?推送消息,下個隊(duì)列隊(duì)列調(diào)用?callback
          ????port.postMessage(null);
          ??}
          };

          再看看之前傳入的 callback(flushWork),調(diào)用 workLoop,取出 taskQueue 中的任務(wù)執(zhí)行。

          //?精簡了相當(dāng)多的代碼
          function?flushWork(hasTimeRemaining,?initialTime)?{
          ??return?workLoop(hasTimeRemaining,?initialTime);
          }

          function?workLoop(hasTimeRemaining,?initialTime)?{
          ??var?currentTime?=?initialTime;
          ??//?scheduleCallback?進(jìn)行了?taskQueue?的?push?操作
          ??//?這里是獲取之前時間片未執(zhí)行的操作
          ??currentTask?=?peek(taskQueue);

          ??while?(currentTask?!==?null)?{
          ????if?(currentTask.expirationTime?>?currentTime)?{
          ??????//?超時需要中斷任務(wù)
          ??????break;
          ????}

          ????currentTask.callback();?????????//?執(zhí)行任務(wù)回調(diào)
          ????currentTime?=?getCurrentTime();?//?重置當(dāng)前時間
          ????currentTask?=?peek(taskQueue);??//?獲取新的任務(wù)
          ??}
          ?//?如果當(dāng)前任務(wù)不為空,表明是超時中斷,返回?true
          ??if?(currentTask?!==?null)?{
          ????return?true;
          ??}?else?{
          ????return?false;
          ??}
          }

          可以看出,React 通過 expirationTime 來判斷是否超時,如果超時就把任務(wù)放到后面來執(zhí)行。所以,異步模型中 setTimeout 里面進(jìn)行 setState,只要當(dāng)前時間片沒有結(jié)束(currentTime 小于 expirationTime),依舊可以將多個 setState 合并成一個。

          接下來我們再做一個實(shí)驗(yàn),在 setTimeout 中連續(xù)進(jìn)行 500 次的 setState,看看最后生效的次數(shù)。

          import?React?from?'react';

          class?App?extends?React.Component?{
          ??state?=?{?val:?0?}
          ??clickBtn()?{
          ????for?(let?i?=?0;?i?500;?i++)?{
          ??????setTimeout(()?=>?{
          ????????this.setState({?val:?this.state.val?+?1?});
          ??????})
          ????}
          ??}
          ??render()?{
          ????return?(<div>
          ??????<button?onClick={()?=>?{this.clickBtn()}}>click?addbutton>

          ??????<div>val:?{?this.state.val?}div>
          ????div>)
          ??}
          }

          export?default?App;

          先看看同步模式下:

          同步模式

          再看看異步模式下:

          異步模式

          最后 setState 的次數(shù)是 81 次,表明這里的操作在 81 個時間片下進(jìn)行的,每個時間片更新了一次。

          總結(jié)

          這篇文章前后花費(fèi)時間比較久,看 React 的源碼確實(shí)很痛苦,因?yàn)橹皼]有了解過,剛開始是看一些文章的分析,但是很多模棱兩可的地方,無奈只能在源碼上進(jìn)行 debug,而且一次性看了 React 15、16 兩個版本的代碼,感覺腦子都有些不夠用了。

          當(dāng)然這篇文章只是簡單介紹了更新機(jī)制從同步到異步的過程,其實(shí) React 16 的更新除了異步之外,在時間片的劃分、任務(wù)的優(yōu)先級上還有很多細(xì)節(jié),這些東西放到下篇文章來講,不知不覺又是一個新坑。


          ●?你不知道的 React Hooks(萬字長文,快速入門必備)

          ●?前端入門機(jī)器學(xué)習(xí) Tensorflow.js 簡明教程

          ●?如何從 0 到 1 搭建性能檢測系統(tǒng)



          ·END·

          圖雀社區(qū)

          匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程



          關(guān)注公眾號回復(fù) z 拉學(xué)習(xí)交流群


          喜歡本文,點(diǎn)個“在看”告訴我

          瀏覽 58
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  麻豆三级片在线 | 一级片黄色免费 | 麻豆国产| 米奇色色色 | 少女操逼|