欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解React中Suspense與lazy的原理

 更新時(shí)間:2024年04月22日 09:42:46   作者:Story  
在react中為我們提供了一個(gè)非常有用的組件,那就是Suspense,本文主要介紹了如何使用Suspense?和?react提供的lazy結(jié)合起來(lái)達(dá)到異步加載狀態(tài)的目的,感興趣的可以了解下

一、前面的話

在react中為我們提供了一個(gè)非常有用的組件,那就是<Suspense/>,他可以包裹一個(gè)異步組件,當(dāng)這個(gè)異步組件處于pending狀態(tài)的時(shí)候會(huì)展示一個(gè)過(guò)渡的UI,當(dāng)異步組件處于resolved狀態(tài)的時(shí)候會(huì)顯示真正的UI,我們來(lái)看一下如何使用Suspense 和 react提供的lazy結(jié)合起來(lái)達(dá)到異步加載狀態(tài)的目的

import  { lazy , Suspense } from 'react';

const LazyComponent = React.lazy(() => import('./xxx'));

export default function App() {
  return (
    <Suspense fallback={<span>loading...</span>}>
      <LazyComponent/>
    </Suspense>
  )
}

它的效果如下:

接下來(lái)我們就來(lái)一步一步看一下這究竟是怎么做到這一點(diǎn)的!

二、lazy懶加載組件

要先從lazy這個(gè)api開(kāi)始說(shuō)起,根據(jù)上面的內(nèi)容,LazyComponent是由lazy這個(gè)調(diào)用返回的結(jié)果,它能夠被直接渲染,在沒(méi)有Suspense加持的情況下,也是可以異步渲染出組件的,如下所示

const LazyComponent = React.lazy(() => import('./LazyComponent.js'));

const FunctionComponent = () => {
    const [count, setCount] = React.useState(1);

    const onClick = () => {
      setCount(count + 1);
    };


    return (
      <div>
        <button onClick={onClick}>{ count }</button>
        <LazyComponent/> 
      </div>
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<FunctionComponent />);

效果如下:

我們看一下lazy的實(shí)現(xiàn)原理

function lazy(ctor) {
    var payload = { // 創(chuàng)建一個(gè)payload
      _status: Uninitialized, // -1
      _result: ctor, // ctor 就是用戶傳遞的哪個(gè)()=> import("xxxxx") 實(shí)際上等價(jià)于 ()=> Promise<any>
    };
    var lazyType = { // 這是一個(gè)REACT_LAZY_TYPE類型的ReactElement
      $$typeof: REACT_LAZY_TYPE,
      _payload: payload,
      _init: lazyInitializer, // 下面分析一下lazyInitializer
    };
    // 下面是給lazyType做屬性的配置,不重要了解即可
    {
      Object.defineProperties(lazyType, {
        defaultProps: {
          configurable: true,
          get: function () {...},
          set: function (newDefaultProps) { ...},
        },
        propTypes: {
          configurable: true,
          get: function () {... },
          set: function (newPropTypes) {...}
        }
      });
    }

    return lazyType;
}

根據(jù)我提供的注釋我們可以看到,其實(shí)lazy就是返回了一個(gè)REACT_LAZY_TYPE類型的ReactElement節(jié)點(diǎn),并且用一個(gè)狀態(tài)機(jī)記錄了當(dāng)前的這個(gè)節(jié)點(diǎn)處于什么樣的狀態(tài),引用者傳進(jìn)來(lái)的函數(shù)引用

這里要重點(diǎn)分析一下()=> import('xxxx')import('xxx')是ES6提供的一種異步加載模塊的方式,他會(huì)返回一個(gè)Promise,因此可以使用.then獲取異步加載所得到的數(shù)據(jù)

接下來(lái)我們看一下lazyInitializer的實(shí)現(xiàn)

function lazyInitializer(payload) {
    if (payload._status === Uninitialized) { // 如果是初始化狀態(tài)
      var ctor = payload._result; // ()=> import('xxx')
      var thenable = ctor(); // 得到一個(gè)Promise
      thenable.then( // 調(diào)用.then
        function (moduleObject) {
          if (
            payload._status === Pending ||
            payload._status === Uninitialized
          ) { // 標(biāo)記成功
            var resolved = payload;
            resolved._status = Resolved;
            resolved._result = moduleObject;
          }
        },
        function (error) {
          if ( // 標(biāo)記失敗
            payload._status === Pending ||
            payload._status === Uninitialized
          ) {
            var rejected = payload;
            rejected._status = Rejected;
            rejected._result = error;
          }
        }
      );

      if (payload._status === Uninitialized) {// 如果是初始化
        var pending = payload;
        pending._status = Pending; // 標(biāo)記正在進(jìn)行
        pending._result = thenable;
      }
    }

    if (payload._status === Resolved) { // 如果不是初始化
      var moduleObject = payload._result;
      if (moduleObject === undefined) {
         報(bào)錯(cuò)
      }
      if (!("default" in moduleObject)) {
        報(bào)錯(cuò)
      }
      return moduleObject.default;
    } else {
      // 初始化都會(huì)進(jìn)入到這里
      throw payload._result; // 拋出錯(cuò)誤
    }
}

經(jīng)過(guò)分析我們會(huì)發(fā)現(xiàn)lazyInitializer會(huì)根據(jù)payload的狀態(tài)來(lái)采取不同的行為:

  • 如果是初始化狀態(tài) 在這里它會(huì)執(zhí)行用戶傳進(jìn)來(lái)的函數(shù),得到一個(gè)Promise,并且開(kāi)始調(diào)用這個(gè)Promise,得到異步的結(jié)果,并且標(biāo)記自己處于Pedning狀態(tài),然后拋出錯(cuò)誤
  • 如果Resolved的狀態(tài)那么就判斷這個(gè)得到的值是否合法,合法就返回給調(diào)用者

但不用擔(dān)心,此時(shí)我們分析了這個(gè)函數(shù)如果執(zhí)行的話,直到現(xiàn)在用戶只是調(diào)用了lazy,這個(gè)函數(shù)還沒(méi)到執(zhí)行的時(shí)候,現(xiàn)在用戶僅僅只是得到了一個(gè)lazy類型的ReactElement類型的節(jié)點(diǎn)

而真正讓這個(gè)函數(shù)執(zhí)行得地方還是得在render階段,當(dāng)調(diào)和到lazy類型的節(jié)點(diǎn)的時(shí)候,會(huì)執(zhí)行mountLazyComponent

function mountLazyComponent(
    _current,
    workInProgress,
    elementType,
    renderLanes
  ) {
    var props = workInProgress.pendingProps; // lazy的組件一般沒(méi)有props
    var lazyComponent = elementType; // ReactElement
    var payload = lazyComponent._payload; // 這就是上面的payload
    var init = lazyComponent._init; // 獲取那個(gè)init函數(shù),就是我們上面分析的那個(gè)
    var Component = init(payload); // 調(diào)用它,第一次會(huì)拋出錯(cuò)誤
    //芭比Q,下面不用看了
    ...
    
}

根據(jù)我們上面的分析,在調(diào)用lazyInitializer函數(shù)的時(shí)候,如果是第一次調(diào)用,會(huì)進(jìn)入第一種情況,狀態(tài)還是初始化的狀態(tài),因此會(huì)執(zhí)行異步函數(shù),得到一個(gè)正在調(diào)用的Promise,然后會(huì)調(diào)用.then獲取它的結(jié)果,然后將其保存在payload中,然后將狀態(tài)置為Pending,最后拋出錯(cuò)誤,所以后面的邏輯都不用看了,第一次在這里會(huì)拋出錯(cuò)誤,阻塞后面的代碼,整個(gè)render階段被迫提前結(jié)束

如果提前結(jié)束了render階段,那么后面該如何運(yùn)行呢?

原來(lái)當(dāng)lazy類型的render過(guò)程中,準(zhǔn)確的來(lái)說(shuō)應(yīng)該是beginWork中因?yàn)榈谝淮螆?zhí)行init函數(shù)導(dǎo)致拋出錯(cuò)誤,阻塞了后面的過(guò)程,react會(huì)提前結(jié)束beginWork環(huán)節(jié),然后react會(huì)捕獲這個(gè)錯(cuò)誤,還記得那個(gè)workLoop么?它是這樣子的:

do {
  try {
    workLoopSync(); // 當(dāng)這里拋出錯(cuò)誤時(shí)
    break;
  } catch (thrownValue) {
    handleError(root, thrownValue); // 會(huì)來(lái)到這里
  }
} while (true);

因此實(shí)際上react并不會(huì)因?yàn)閽伋隽诉@個(gè)錯(cuò)誤就完蛋了,甚至這個(gè)錯(cuò)誤是刻意拋出的,為的就是在handleError中捕獲它,然后做不同的邏輯處理

handleError中會(huì)基于拋出錯(cuò)誤的節(jié)點(diǎn)開(kāi)始提前進(jìn)入completeWork,然后將整棵樹(shù)標(biāo)記為未完成的狀態(tài),最后因?yàn)樯蠈雍瘮?shù)拿到這個(gè)是否調(diào)和完整棵樹(shù)的狀態(tài),決定是否進(jìn)行commit流程

結(jié)果就是這棵樹(shù)沒(méi)有完成,因此不會(huì)進(jìn)行commit階段,第一次render因?yàn)?code>lazy類型組件的存在就這樣匆匆結(jié)束了

那現(xiàn)相信大家和我有同樣的問(wèn)題,那react是怎么重啟render的呢? ,因?yàn)樵谄匠i_(kāi)發(fā)中l(wèi)azy組件也是可以渲染出組件的呀,所以一定有一個(gè)重啟render的過(guò)程才能做到。

原來(lái)在handleError的過(guò)程中有一個(gè)這樣的過(guò)程,如果發(fā)現(xiàn)了拋出錯(cuò)誤的參數(shù)是一個(gè)Promise的話,就會(huì)認(rèn)定他是一個(gè)懶加載的情況,然后做出重啟的操作,正巧我們init拋出錯(cuò)誤的信息剛好是一個(gè)Promise,而重啟的操作如下:

function throwException(value){// 這個(gè)value就是錯(cuò)誤信息
  ...
  if (
      value !== null &&
      typeof value === "object" &&
      typeof value.then === "function" // 如果是一個(gè)Promise
    ) {
      var wakeable = value; //
      var suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber); // 如有上層有Suspense包裹的話,這里先不談
      if (suspenseBoundary !== null) {
        ...
      } else{
        attachPingListener(root, wakeable, rootRenderLanes); // 這里就是關(guān)鍵了,它會(huì)監(jiān)聽(tīng)這個(gè)Promise的情況
      }
}

那么attachPingListener發(fā)生了什么呢?

簡(jiǎn)化一下就是這樣的

var ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
weakable.then(ping , ping)

看到了嗎,如果這個(gè)Promise的狀態(tài)一旦從Pending狀態(tài)變成其他狀態(tài),就會(huì)執(zhí)行這個(gè)pingSuspendedRoot,它里面就藏著重新發(fā)起調(diào)度的ensureRootIsScheduled邏輯,然后會(huì)把更新流程重走一遍,從rendercommit,最終就呈現(xiàn)出了UI。

這里需要注意的一點(diǎn)就是當(dāng)重啟的這一次render階段其實(shí)也會(huì)遇到lazy類型的節(jié)點(diǎn),那它還會(huì)拋出錯(cuò)誤嗎?

其實(shí)是不會(huì)的,因?yàn)檫@一次來(lái)到lazy節(jié)點(diǎn)時(shí),執(zhí)行的init函數(shù)會(huì)發(fā)現(xiàn)狀態(tài)已經(jīng)被修改為Resolved的狀態(tài)了, 會(huì)直接返回結(jié)果,然后返回的結(jié)果通常來(lái)說(shuō)是一個(gè)組件,就是異步加載的組件,把它作為子組件再繼續(xù)構(gòu)建fiber樹(shù)

function mountLazyComponent(
    _current,
    workInProgress,
    elementType,
    renderLanes
  ) {
    var props = workInProgress.pendingProps; // lazy的組件一般沒(méi)有props
    var lazyComponent = elementType; // ReactElement
    var payload = lazyComponent._payload; // 這就是上面的payload
    var init = lazyComponent._init; // 獲取那個(gè)init函數(shù),就是我們上面分析的那個(gè)
    var Component = init(payload); // 這一次調(diào)用直接獲取到值,而不會(huì)拋出錯(cuò)誤,往下調(diào)和異步組件
    
    workInProgress.type = Component;
    var resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component)); // 獲取對(duì)應(yīng)的fiber類型
    var resolvedProps = resolveDefaultProps(Component, props);
    var child;

    switch (resolvedTag) {
      case FunctionComponent: {
        ...
        child = updateFunctionComponent( // 繼續(xù)調(diào)和
          null,
          workInProgress,
          Component,
          resolvedProps,
          renderLanes
        );
        return child;
      }
      ...
    }
    
}

至此lazy類型的組件原理我們就分析完了,它其實(shí)利用的是react強(qiáng)大的異常捕獲機(jī)制,以及Promise靈敏的狀態(tài)機(jī)來(lái)實(shí)現(xiàn)的,我畫(huà)個(gè)圖給大家總結(jié)一下

三、Suspense原理

當(dāng)我們分析了上面的lazy類型的組件之后Suspense就很好學(xué)習(xí)了

Suspense本質(zhì)上就是一個(gè)ReactElement類型的對(duì)象,沒(méi)啥好說(shuō)的;關(guān)鍵要看在render階段react如何處理這種類型的fiber組件的,下面一起來(lái)看一下

初始化

在初始化時(shí)僅僅只是創(chuàng)建了fiber,然后繼續(xù)調(diào)和子組件,由于他的組件就是lazy類型的組件,因此還是回到上面的邏輯,lazy組件會(huì)拋錯(cuò)啊,因此第一次render階段終止了,但是在handleError處理錯(cuò)誤的時(shí)候,因?yàn)樗?code>Suspense包裹著,因此邏輯會(huì)有不同

function throwException(value){ // 這個(gè)value就是錯(cuò)誤信息
  ...
  if (
      value !== null &&
      typeof value === "object" &&
      typeof value.then === "function" // 如果是一個(gè)Promise
    ) {
      var wakeable = value; //
      var suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber); // 如有上層有Suspense包裹的話,這里會(huì)判定有,實(shí)際上就是遍歷祖先節(jié)點(diǎn),看是否有Suspense類型的fiber
      if (suspenseBoundary !== null) {
        suspenseBoundary.flags &= ~ForceClientRender;
        markSuspenseBoundaryShouldCapture( // 打標(biāo)簽應(yīng)該被捕獲
          suspenseBoundary,
          returnFiber,
          sourceFiber,
          root,
          rootRenderLanes
        ); 
        attachPingListener(root, wakeable, rootRenderLanes); // 監(jiān)聽(tīng)重啟
      } else{
        ...
      }
}

實(shí)際上這個(gè)邏輯和lazy還是一樣的,就是監(jiān)聽(tīng)Promise的狀態(tài),在Promise有結(jié)果的時(shí)候再重啟一次render,這一點(diǎn)是一致的,通過(guò)這個(gè)機(jī)制可以確保當(dāng)異步組件加載完成后react運(yùn)行時(shí)能夠知道在此時(shí)更新頁(yè)面,呈現(xiàn)出最新的UI

但是我們知道從效果上來(lái)看,在有Suspense包裹的時(shí)候,在異步組件加載過(guò)程中應(yīng)該會(huì)立馬展示一個(gè)過(guò)渡UI,也就是fallback對(duì)應(yīng)的參數(shù),而需要做到這一點(diǎn)需要發(fā)起一次調(diào)度啊,也就是說(shuō)需要經(jīng)歷一個(gè)render+commit才能做到啊

過(guò)渡fiber節(jié)點(diǎn)

原來(lái)這一切的一切在第一次render的時(shí)候就有準(zhǔn)備了,在第一次構(gòu)建fiber樹(shù)的時(shí)候,假設(shè)我們的組件是下面這樣的

<Suspense fallback={...}>
  <Lazy/>
</Suspense>

那么實(shí)際上在構(gòu)建fiber樹(shù)的時(shí)候會(huì)有這樣的fiber結(jié)構(gòu)

因此它并不是每個(gè)組件對(duì)應(yīng)一個(gè)fiber節(jié)點(diǎn),Suspense對(duì)應(yīng)的實(shí)際上是有2個(gè)fiber節(jié)點(diǎn),當(dāng)我們知道這一點(diǎn)之后,當(dāng)做了監(jiān)聽(tīng)完的動(dòng)作之后,我們?cè)倩氐酵鈱涌匆幌?,?huì)執(zhí)行一個(gè)completeUnitOfWork的動(dòng)作,這個(gè)動(dòng)作實(shí)際上在上面我們講到的只有lazy的情況也會(huì)執(zhí)行,只不過(guò)在只有lazy組件的時(shí)候它會(huì)一直調(diào)和到root節(jié)點(diǎn),導(dǎo)致workInProgressnull,而在有Suspense會(huì)表現(xiàn)的有所不同

因?yàn)檫@是由于出現(xiàn)了異常導(dǎo)致的completeUnitOfWork,因此不會(huì)走正常的completeWork,而是走unwindWork(current, completedWork);

unwindWork向上歸并的時(shí)候,如果遇到有Suspense節(jié)點(diǎn)的情況會(huì)保留這個(gè)Suspense節(jié)點(diǎn)的信息,實(shí)際上就是不會(huì)一直往上走到root節(jié)點(diǎn),而是將workInProgress指向這個(gè)Suspense的fiber節(jié)點(diǎn),然后就退出completeWork的流程,然后我們?cè)賮?lái)看一下render階段的引擎函數(shù)

do {
  try {
    workLoopSync(); // 這里面需要workInProgress有值才能正常運(yùn)行
    break;
  } catch (thrownValue) {
    handleError(root, thrownValue);
    // 結(jié)束后,還是會(huì)執(zhí)行
  }
} while (true);

handleError結(jié)束后還會(huì)繼續(xù)接著render,在上面提到的只有l(wèi)azy組件的情況下,因?yàn)?code>workInProgress不存在所以直接break退出了render流程,而在Suspense組件存在的情況下,會(huì)繼續(xù)從這個(gè)Suspense開(kāi)始繼續(xù)render

這一次render就會(huì)直接調(diào)和fallback的內(nèi)容,這一次根本就不會(huì)遇到lazy類型的組件了,直到整棵fiber樹(shù)調(diào)和完成,然后接著正常進(jìn)行commit流程,所以用戶看到的就是帶有fallback的UI界面

等到異步組件重新加載完成后,會(huì)重新執(zhí)行一次render + commit 構(gòu)建出含有異步組件的界面

小結(jié): 以上就是Suspense,主要是react在擁有Suspense類型的組件的過(guò)程中做了處理,使其多了一次默認(rèn)的render + commit的流程,從而使用戶能夠看到含有過(guò)渡狀態(tài)的UI,我依然用一個(gè)圖來(lái)給大家總結(jié)一下

以上就是深入理解React中Suspense與lazy的原理的詳細(xì)內(nèi)容,更多關(guān)于React Suspense lazy的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React中使用外部樣式的3種方式(小結(jié))

    React中使用外部樣式的3種方式(小結(jié))

    這篇文章主要介紹了React中使用外部樣式的3種方式(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • Webpack3+React16代碼分割的實(shí)現(xiàn)

    Webpack3+React16代碼分割的實(shí)現(xiàn)

    這篇文章主要介紹了Webpack3+React16代碼分割的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • React、Vue中key的作用詳解 (key的內(nèi)部原理解析)

    React、Vue中key的作用詳解 (key的內(nèi)部原理解析)

    key是虛擬DOM對(duì)象的標(biāo)識(shí),當(dāng)狀態(tài)中的數(shù)據(jù)發(fā)生變化時(shí),Vue會(huì)根據(jù)[新數(shù)據(jù)]生成[新的虛擬DOM],本文給大家介紹React、Vue中key的作用詳解 (key的內(nèi)部原理解析),感興趣的朋友一起看看吧
    2023-10-10
  • Vite搭建React項(xiàng)目的方法步驟

    Vite搭建React項(xiàng)目的方法步驟

    這篇文章主要介紹了Vite搭建React項(xiàng)目的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • React如何使用Portal實(shí)現(xiàn)跨層級(jí)DOM渲染

    React如何使用Portal實(shí)現(xiàn)跨層級(jí)DOM渲染

    Portal 就像是一個(gè)“傳送門”,能讓你把組件里的元素“傳送到”其他 DOM 節(jié)點(diǎn)下面去渲染,下面小編就來(lái)和大家簡(jiǎn)單介紹一下具體的使用方法吧
    2025-04-04
  • React使用Ant Design方式(簡(jiǎn)單使用)

    React使用Ant Design方式(簡(jiǎn)單使用)

    文章介紹了AntDesign組件庫(kù),它是基于AntDesign設(shè)計(jì)體系的ReactUI組件庫(kù),主要用于研發(fā)企業(yè)級(jí)中后臺(tái)產(chǎn)品,文章詳細(xì)講解了如何下載和按需引入antd組件庫(kù),并通過(guò)一個(gè)小案例展示了如何使用antd進(jìn)行布局和改造,最后,文章提醒大家在使用過(guò)程中可以參考官網(wǎng)的屬性介紹
    2024-11-11
  • React Antd中如何設(shè)置表單只輸入數(shù)字

    React Antd中如何設(shè)置表單只輸入數(shù)字

    這篇文章主要介紹了React Antd中如何設(shè)置表單只輸入數(shù)字問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • React Native之TextInput組件解析示例

    React Native之TextInput組件解析示例

    本篇文章主要介紹了React Native之TextInput組件解析示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • 使用React制作一個(gè)貪吃蛇游戲的代碼詳解

    使用React制作一個(gè)貪吃蛇游戲的代碼詳解

    Snake?Game?使用?ReactJS?項(xiàng)目實(shí)現(xiàn)功能組件并相應(yīng)地管理狀態(tài),開(kāi)發(fā)的游戲允許用戶使用箭頭鍵控制蛇或觸摸屏幕上顯示的按鈕來(lái)收集食物并增長(zhǎng)長(zhǎng)度,本文給大家詳細(xì)講解了如何使用?React?制作一個(gè)貪吃蛇游戲,需要的朋友可以參考下
    2023-11-11
  • Unity?RectTransform詳解

    Unity?RectTransform詳解

    unity中的ui元素是有嚴(yán)格的父子關(guān)系的,子物體的位置是根據(jù)父物體的變化而變化的,而子物體和父物體聯(lián)系的橋梁就是Anchor,本文重點(diǎn)介紹Unity?RectTransform的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2024-01-01

最新評(píng)論