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

react  Suspense工作原理解析

 更新時(shí)間:2022年09月21日 15:31:21   作者:l1n3x  
這篇文章主要為大家介紹了react  Suspense工作原理解析以及基本應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

Suspense 基本應(yīng)用

Suspense 目前在 react 中一般配合 lazy 使用,當(dāng)有一些組件需要?jiǎng)討B(tài)加載(例如各種插件)時(shí)可以利用 lazy 方法來(lái)完成。其中 lazy 接受類(lèi)型為 Promise<() => {default: ReactComponet}> 的參數(shù),并將其包裝為 react 組件。ReactComponet 可以是類(lèi)組件函數(shù)組件或其他類(lèi)型的組件,例如:

 const Lazy = React.lazy(() => import("./LazyComponent"))
 <Suspense fallback={"loading"}>
        <Lazy/> // lazy 包裝的組件
 </Suspense>

由于 Lazy 往往是從遠(yuǎn)程加載,在加載完成之前 react 并不知道該如何渲染該組件。此時(shí)如果不顯示任何內(nèi)容,則會(huì)造成不好的用戶(hù)體驗(yàn)。因此 Suspense 還有一個(gè)強(qiáng)制的參數(shù)為 fallback,表示 Lazy 組件加載的過(guò)程中應(yīng)該顯示什么內(nèi)容。往往 fallback 會(huì)使用一個(gè)加載動(dòng)畫(huà)。當(dāng)加載完成后,Suspense 就會(huì)將 fallback 切換為 Lazy 組件的內(nèi)容。一個(gè)完整的例子如下:

function LazyComp(){
  console.info("sus", "render lazy")
  return "i am a lazy man"
}
function delay(ms){
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms)
  })
}
// 模擬動(dòng)態(tài)加載組件
const Lazy = lazy(() => delay(5000).then(x => ({"default": LazyComp})))
function App() {
  const context = useContext(Context)
  console.info("outer context")
  return (
      <Suspense fallback={"loading"}>
        <Lazy/>
      </Suspense>
  )
}

這段代碼定義了一個(gè)需要?jiǎng)討B(tài)加載的 LazyComp 函數(shù)式組件。會(huì)在一開(kāi)始顯示 fallback 中的內(nèi)容 loading,5s 后顯示 i am a lazy man。

Suspense 原理

雖然說(shuō) Suspense 往往會(huì)配合 lazy 使用,但是 Suspense 是否只能配合 lazy 使用?lazy 是否又必須配合Suspense? 要搞清楚這兩個(gè)問(wèn)題,首先要明白 Suspense 以及 lazy 是在整個(gè)過(guò)程中扮演的角色,這里先給出一個(gè)簡(jiǎn)單的結(jié)論:

  • Suspense: 可以看做是 react 提供用了加載數(shù)據(jù)的一個(gè)標(biāo)準(zhǔn),當(dāng)加載到某個(gè)組件時(shí),如果該組件本身或者組件需要的數(shù)據(jù)是未知的,需要?jiǎng)討B(tài)加載,此時(shí)就可以使用 Suspense。Suspense 提供了加載 -> 過(guò)渡 -> 完成后切換這樣一個(gè)標(biāo)準(zhǔn)的業(yè)務(wù)流程。
  • lazy: lazy 是在 Suspense 的標(biāo)準(zhǔn)下,實(shí)現(xiàn)的一個(gè)動(dòng)態(tài)加載的組件的工具方法。

從上面的描述即可以看出,Suspense 是一個(gè)加載數(shù)據(jù)的標(biāo)準(zhǔn),lazy 只是該標(biāo)準(zhǔn)下實(shí)現(xiàn)的一個(gè)工具方法。那么說(shuō)明 Suspense 除配合了 lazy 還可以有其他應(yīng)用場(chǎng)景。而 lazy 是 Suspense 標(biāo)準(zhǔn)下的一個(gè)工具方法,因此無(wú)法脫離 Suspense 使用。接下來(lái)通過(guò) lazy + Suspense 方式來(lái)給大家分析具體原理,搞懂了這部分,我們利用 Suspense 實(shí)現(xiàn)自己的數(shù)據(jù)加載也不是難事。

基本流程

在深入了解細(xì)節(jié)之前,我們先了解一下 lazy + Suspense 的基本原理。這里需要一些 react 渲染流程的基本知識(shí)。為了統(tǒng)一,在后續(xù)將動(dòng)態(tài)加載的組件稱(chēng)為 primary 組件,fallback 傳入的組件稱(chēng)為 fallback 組件,與源碼保持一致。

  • 當(dāng) react 在 beginWork 的過(guò)程中遇到一個(gè) Suspense 組件時(shí),會(huì)首先將 primary 組件作為其子節(jié)點(diǎn),根據(jù) react 的遍歷算法,下一個(gè)遍歷的組件就是未加載完成的 primary 組件。
  • 當(dāng)遍歷到 primary 組件時(shí),primary 組件會(huì)拋出一個(gè)異常。該異常內(nèi)容為組件 promise,react 捕獲到異常后,發(fā)現(xiàn)其是一個(gè) promise,會(huì)將其 then 方法添加一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)的作用是觸發(fā) Suspense 組件的更新。并且將下一個(gè)需要遍歷的元素重新設(shè)置為 Suspense,因此在一次 beginWork 中,Suspense 會(huì)被訪(fǎng)問(wèn)兩次。
  • 又一次遍歷到 Suspense,本次會(huì)將 primary 以及 fallback 都生成,并且關(guān)系如下:

雖然 primary 作為 Suspense 的直接子節(jié)點(diǎn),但是 Suspense 會(huì)在 beginWork 階段直接返回 fallback。使得直接跳過(guò) primary 的遍歷。因此此時(shí) primary 必定沒(méi)有加載完成,所以也沒(méi)必要再遍歷一次。本次渲染結(jié)束后,屏幕上會(huì)展示 fallback 的內(nèi)容

  • 當(dāng) primary 組件加載完成后,會(huì)觸發(fā)步驟 2 中 then,使得在 Suspense 上調(diào)度一個(gè)更新,由于此時(shí)加載已經(jīng)完成,Suspense 會(huì)直接渲染加載完成的 primary 組件,并刪除 fallback 組件。

這 4 個(gè)步驟看起來(lái)還是比較復(fù)雜。相對(duì)于普通的組件主要有兩個(gè)不同的流程:

  • primary 會(huì)組件拋出異常,react 捕獲異常后繼續(xù) beginWork 階段。
  • 整個(gè) beginWork 節(jié)點(diǎn),Suspense 會(huì)被訪(fǎng)問(wèn)兩次

不過(guò)基本邏輯還是比較簡(jiǎn)單,即是:

  • 拋出異常
  • react 捕獲,添加回調(diào)
  • 展示 fallback
  • 加載完成,執(zhí)行回調(diào)
  • 展示加載完成后的組件

整個(gè) beginWork 遍歷順序?yàn)?

 Suspense -> primary -> Suspense -> fallback

源碼解讀 - primary 組件

整個(gè) Suspend 的邏輯相對(duì)于普通流程實(shí)際上是從 primary 組件開(kāi)始的,因此我們也從 react 是如何處理 primary 組件開(kāi)始探索。找到 react 在 beginWork 中處理處理 primary 組件的邏輯的方法 mountLazyComponent,這里我摘出一段關(guān)鍵的代碼:

  const props = workInProgress.pendingProps;
  const lazyComponent: LazyComponentType<any, any> = elementType;
  const payload = lazyComponent._payload;
  const init = lazyComponent._init;
  let Component = init(payload); // 如果未加載完成,則會(huì)拋出異常,否則會(huì)返回加載完成的組件

其中最關(guān)鍵的部分莫過(guò)于這個(gè) init 方法,執(zhí)行到這個(gè)方法時(shí),如果沒(méi)有加載完成就會(huì)拋出 Promise 的異常。如果加載完成就直接返回完成后的組件。我們可以看到這個(gè) init 方法實(shí)際上是掛載到 lazyComponent._init 方法,lazyComponent 則就是 React.lazy() 返回的組件。我們找到 React.lazy() :

export function lazy<T>(
  ctor: () => Thenable<{default: T, ...}>,
): LazyComponent<T, Payload<T>> {
  const payload: Payload<T> = {
    // We use these fields to store the result.
    _status: Uninitialized,
    _result: ctor,
  };
  const lazyType: LazyComponent<T, Payload<T>> = {
    $$typeof: REACT_LAZY_TYPE,
    _payload: payload,
    _init: lazyInitializer,
  };

這里的 lazyType 實(shí)際上就是上面的 lazyComponent。那么這里的 _init 實(shí)際上來(lái)自于另一個(gè)函數(shù) lazyInitializer:

function lazyInitializer<T>(payload: Payload<T>): T {
  if (payload._status === Uninitialized) {
    console.info("sus", "payload status", "Uninitialized")
    const ctor = payload._result;
    const thenable = ctor(); // 這里的 ctor 就是我們返回 promise 的函數(shù),執(zhí)行之后得到一個(gè)加載組件的 promise
    // 加載完成后修改狀態(tài),并將結(jié)果掛載到 _result 上
    thenable.then(
      moduleObject => {
        if (payload._status === Pending || payload._status === Uninitialized) {
          // Transition to the next state.
          const resolved: ResolvedPayload<T> = (payload: any);
          resolved._status = Resolved;
          resolved._result = moduleObject;
        }
      },
      error => {
        if (payload._status === Pending || payload._status === Uninitialized) {
          // Transition to the next state.
          const rejected: RejectedPayload = (payload: any);
          rejected._status = Rejected;
          rejected._result = error;
        }
      },
    );
    if (payload._status === Uninitialized) {
      // In case, we're still uninitialized, then we're waiting for the thenable
      // to resolve. Set it as pending in the meantime.
      const pending: PendingPayload = (payload: any);
      pending._status = Pending;
      pending._result = thenable;
    }
  }
  // 如果已經(jīng)加載完成,則直接返回組件
  if (payload._status === Resolved) {
    const moduleObject = payload._result;
    console.info("sus", "get lazy resolved result")
    return moduleObject.default; // 注意這里返回的是 moduleObject.default 而不是直接返回 moduleObject
  } else {
    // 否則拋出異常
    console.info("sus, raise a promise", payload._result)
    throw payload._result;
  }
}

因此執(zhí)行這個(gè)方法大致可以分為兩個(gè)狀態(tài):

  • 未加載完成時(shí)拋出異常
  • 加載完成后返回組件

到這里,整個(gè) primary 的邏輯就搞清楚了。下一步則是搞清楚 react 是如何捕獲并且處理異常的。

源碼解讀 - 異常捕獲

react 協(xié)調(diào)整個(gè)階段都在 workLoop 中執(zhí)行,代碼如下:

  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);

可以看到 catch 了 error 后,整個(gè)處理過(guò)程在 handleError 中完成。當(dāng)然,如果是如果 primary 組件拋出的異常,這里的 thrownValue 就為一個(gè) priomise。在 handleError 中有這樣一段相關(guān)代碼:

throwException(
    root,
    erroredWork.return,
    erroredWork,
    thrownValue,
    workInProgressRootRenderLanes,
);
completeUnitOfWork(erroredWork);

核心代碼需要繼續(xù)深入到 throwException:

// 首先判斷是否是為 promise
if (
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
  ) {
    const wakeable: Wakeable = (value: any);
    resetSuspendedComponent(sourceFiber, rootRenderLanes);
    // 獲取到 Suspens 父組件
    const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
    if (suspenseBoundary !== null) {
      suspenseBoundary.flags &= ~ForceClientRender;
      // 給 Suspens 父組件 打上一些標(biāo)記,讓 Suspens 父組件知道已經(jīng)有異常拋出,需要渲染 fallback
      markSuspenseBoundaryShouldCapture(
        suspenseBoundary,
        returnFiber,
        sourceFiber,
        root,
        rootRenderLanes,
      );
      // We only attach ping listeners in concurrent mode. Legacy Suspense always
      // commits fallbacks synchronously, so there are no pings.
      if (suspenseBoundary.mode & ConcurrentMode) {
        attachPingListener(root, wakeable, rootRenderLanes);
      }
      // 將拋出的 promise 放入Suspens 父組件的 updateQueue 中,后續(xù)會(huì)遍歷這個(gè) queue 進(jìn)行回調(diào)綁定
      attachRetryListener(suspenseBoundary, root, wakeable, rootRenderLanes);
      return;
    } 
  }

可以看到 throwException 邏輯主要是判斷拋出的異常是不是 promise,如果是的話(huà),就給 Suspens 父組件打上 ShoulCapture 的 flags,具體用處下面會(huì)講到。并且把拋出的 promise 放入 Suspens 父組件的 updateQueue 中。

throwException 完成后會(huì)執(zhí)行一次 completeUnitOfWork,根據(jù) ShoulCapture 打上 DidCapture 的 flags。 并將下一個(gè)需要遍歷的節(jié)點(diǎn)設(shè)置為 Suspense,也就是下一次遍歷的對(duì)象依然是 Suspense。這也是之前提到的 Suspens 在整個(gè) beginWork 階段會(huì)遍歷兩次。

源碼解讀 - 添加 promise 回調(diào)

在 Suspense 的 update queue 中,在 commit 階段會(huì)遍歷這個(gè) updateQueue 添加回調(diào)函數(shù),該功能在 commitMutationEffectsOnFiber 中。找到關(guān)于 Suspense 的部分,會(huì)有以下代碼:

 if (flags & Update) {
        try {
          commitSuspenseCallback(finishedWork);
        } catch (error) {
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
        attachSuspenseRetryListeners(finishedWork);
      }
      return;

主要邏輯在 attachSuspenseRetryListeners 中:

function attachSuspenseRetryListeners(finishedWork: Fiber) {
  const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
  if (wakeables !== null) {
    finishedWork.updateQueue = null;
    let retryCache = finishedWork.stateNode;
    if (retryCache === null) {
      retryCache = finishedWork.stateNode = new PossiblyWeakSet();
    }
    wakeables.forEach(wakeable => {
      // Memoize using the boundary fiber to prevent redundant listeners.
      const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
      // 判斷一下這個(gè) promise 是否已經(jīng)綁定過(guò)一次了,如果綁定過(guò)則可以忽略
      if (!retryCache.has(wakeable)) {
        retryCache.add(wakeable);
        if (enableUpdaterTracking) {
          if (isDevToolsPresent) {
            if (inProgressLanes !== null && inProgressRoot !== null) {
              // If we have pending work still, associate the original updaters with it.
              restorePendingUpdaters(inProgressRoot, inProgressLanes);
            } else {
              throw Error(
                'Expected finished root and lanes to be set. This is a bug in React.',
              );
            }
          }
        }
        // 將 retry 綁定 promise 的 then 回調(diào)
        wakeable.then(retry, retry);
      }
    });
  }
}

attachSuspenseRetryListeners 整個(gè)邏輯就是綁定 promise 回調(diào),并將綁定后的 promise 放入緩存,以免重復(fù)綁定。這里綁定的回調(diào)為 resolveRetryWakeable.bind(null, finishedWork, wakeable),在這個(gè)方法中又調(diào)用了 retryTimedOutBoundary 方法:

 if (retryLane === NoLane) {
    // TODO: Assign this to `suspenseState.retryLane`? to avoid
    // unnecessary entanglement?
    retryLane = requestRetryLane(boundaryFiber);
  }
  // TODO: Special case idle priority?
  const eventTime = requestEventTime();
  const root = markUpdateLaneFromFiberToRoot(boundaryFiber, retryLane);
  if (root !== null) {
    markRootUpdated(root, retryLane, eventTime);
    ensureRootIsScheduled(root, eventTime);
  }

看到 markUpdateLaneFromFiberToRoot 邏輯就比較清晰了,即在 Suspense 的組件上調(diào)度一次更新。也就是說(shuō),當(dāng)動(dòng)態(tài)組件的請(qǐng)求完成后,會(huì)執(zhí)行 resolveRetryWakeable -> retryTimedOutBoundary,并且最終讓 Suspense 進(jìn)行一次更新。

源碼解讀-Suspense

之所以是將 Suspense 放在最后來(lái)分析,是因?yàn)閷?duì) Suspense 的處理涉及到多個(gè)狀態(tài),這些狀態(tài)在之前的步驟中或許會(huì)被修改,因此在了解其他步驟之后再來(lái)看 Suspense 或許更容易理解。對(duì)于 Suspense 來(lái)說(shuō),在 workLoop 中可能會(huì)有 3 種不同的處理方式。每一次 beginWork Suspense 又會(huì)被訪(fǎng)問(wèn)兩次,在源碼中稱(chēng)為 first pass 和 second pass 。這兩次會(huì)根據(jù)在 Suspense 的 flags 上是否存在 DidCapture 來(lái)進(jìn)行不同操作。整個(gè)處理邏輯都在 updateSuspenseComponent 中。

首次渲染

beginWork - first pass,此時(shí) DidCapture 不存在,Suspense 將 primary 組件作為子節(jié)點(diǎn),訪(fǎng)問(wèn)子節(jié)點(diǎn)后會(huì)拋出異常。catch 時(shí)會(huì)設(shè)置 DidCapture 到 flags 上。對(duì)應(yīng)的函數(shù)為 mountSuspensePrimaryChildren:

function mountSuspensePrimaryChildren(
  workInProgress,
  primaryChildren,
  renderLanes,
) {
  const mode = workInProgress.mode;
  const primaryChildProps: OffscreenProps = {
    mode: 'visible',
    children: primaryChildren,
  };
  const primaryChildFragment = mountWorkInProgressOffscreenFiber(
    primaryChildProps,
    mode,
    renderLanes,
  );
  primaryChildFragment.return = workInProgress;
  workInProgress.child = primaryChildFragment; // 子節(jié)點(diǎn)為 primaryChildFragment,下一次訪(fǎng)問(wèn)會(huì)拋出異常
  return primaryChildFragment;
}

beginWork - second pass,由于此時(shí) DidCapture 存在,會(huì)將 primary 組件作為子節(jié)點(diǎn),并將 fallback 組件作為 primary 組件的兄弟節(jié)點(diǎn)。但是直接返回 primary 組件,跳過(guò) fallback 組件。對(duì)應(yīng)的函數(shù)為 mountSuspenseFallbackChildren:

function mountSuspenseFallbackChildren(
  workInProgress,
  primaryChildren,
  fallbackChildren,
  renderLanes,
) {
  const mode = workInProgress.mode;
  const progressedPrimaryFragment: Fiber | null = workInProgress.child;
  const primaryChildProps: OffscreenProps = {
    mode: 'hidden',
    children: primaryChildren,
  };
  let primaryChildFragment;
  let fallbackChildFragment;
  primaryChildFragment.return = workInProgress;
  fallbackChildFragment.return = workInProgress;
  primaryChildFragment.sibling = fallbackChildFragment;
  workInProgress.child = primaryChildFragment; // 注意這里的子節(jié)點(diǎn)是 primaryChildFragment
  return fallbackChildFragment; // 但返回的卻是 fallbackChildFragment,目的是為了跳過(guò) primaryChild 的遍歷
}

commit: 將掛載到 updateQueue 上的 promise 綁定回調(diào),并清除 DidCapture。整個(gè)流程圖如下:

primary 組件加載完成前的渲染

在首次渲染以及 primary 組件加載完成的期間,還可能會(huì)有其他組件更新而觸發(fā)觸發(fā)渲染,其邏輯為:

beginWork - first pass - DidCapture 不存在: 將 primary 組件作為子節(jié)點(diǎn),如果 fallback 組件存在,則將其添加到 Suspense 組件的 deletions 中。訪(fǎng)問(wèn)子節(jié)點(diǎn)后會(huì)拋出異常。catch 時(shí)會(huì)設(shè)置 DidCapture 到 flags 上。 對(duì)應(yīng)的函數(shù)為 updateSuspensePrimaryChildren:

function updateSuspensePrimaryChildren(
  current,
  workInProgress,
  primaryChildren,
  renderLanes,
) {
const currentPrimaryChildFragment: Fiber = (current.child: any);
  const currentFallbackChildFragment: Fiber | null =
    currentPrimaryChildFragment.sibling;
  const primaryChildFragment = updateWorkInProgressOffscreenFiber(
    currentPrimaryChildFragment,
    {
      mode: 'visible',
      children: primaryChildren,
    },
  );
  if ((workInProgress.mode & ConcurrentMode) === NoMode) {
    primaryChildFragment.lanes = renderLanes;
  }
  primaryChildFragment.return = workInProgress;
  primaryChildFragment.sibling = null;
  // 如果 currentFallbackChildFragment 存在,需要添加到 deletions 中
  if (currentFallbackChildFragment !== null) {
    const deletions = workInProgress.deletions;
    if (deletions === null) {
      workInProgress.deletions = [currentFallbackChildFragment];
      workInProgress.flags |= ChildDeletion;
    } else {
      deletions.push(currentFallbackChildFragment);
    }
  }
  workInProgress.child = primaryChildFragment;
  return primaryChildFragment;
}

beginWork - second pass - DidCapture 存在: 將 primary 組件作為子節(jié)點(diǎn),將 fallback 組件作為 primary 組件的兄弟節(jié)點(diǎn)。并且清除deletions。因?yàn)榇藭r(shí) primary 組件還未加載完成,所以需要確保 fallback 組件不會(huì)被刪除。對(duì)于的函數(shù)為:

function updateSuspenseFallbackChildren(
  current,
  workInProgress,
  primaryChildren,
  fallbackChildren,
  renderLanes,
) {
  const progressedPrimaryFragment: Fiber = (workInProgress.child: any);
    primaryChildFragment = progressedPrimaryFragment;
    primaryChildFragment.childLanes = NoLanes;
    primaryChildFragment.pendingProps = primaryChildProps;
    if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
      primaryChildFragment.actualDuration = 0;
      primaryChildFragment.actualStartTime = -1;
      primaryChildFragment.selfBaseDuration =
        currentPrimaryChildFragment.selfBaseDuration;
      primaryChildFragment.treeBaseDuration =
        currentPrimaryChildFragment.treeBaseDuration;
    }
    // 清除 deletions,確保 fallback 可以展示
    workInProgress.deletions = null;
    let fallbackChildFragment;
    if (currentFallbackChildFragment !== null) {
    fallbackChildFragment = createWorkInProgress(
      currentFallbackChildFragment,
      fallbackChildren,
    );
  } else {
    fallbackChildFragment = createFiberFromFragment(
      fallbackChildren,
      mode,
      renderLanes,
      null,
    );
    fallbackChildFragment.flags |= Placement;
  }
  fallbackChildFragment.return = workInProgress;
  primaryChildFragment.return = workInProgress;
  primaryChildFragment.sibling = fallbackChildFragment;
  workInProgress.child = primaryChildFragment; // 同樣的操作,workInProgress.child 為 primaryChildFragment
  return fallbackChildFragment; // 但是返回 fallbackChildFragment
  }

commit: 清除 DidCapture。 整個(gè)流程圖如下:

primary 組件加載完成時(shí)的渲染

加載完成之后會(huì)觸發(fā) Suspense 的更新,此時(shí)為:

beginWork - first pass - DidCapture 不存在: 將 primary 組件作為子節(jié)點(diǎn),如果 fallback 組件存在,則將其添加到 Suspense 組件的 deletions 中。由于此時(shí) primary 組件加載完成,訪(fǎng)問(wèn)子節(jié)點(diǎn)不會(huì)拋出異常。處理的函數(shù)同樣為 updateSuspensePrimaryChildren,這里就不再貼出來(lái)。

可以看出,primary 組件加載完成后就不會(huì)拋出異常,因此不會(huì)進(jìn)入到 second pass,那么就不會(huì)有清除 deletions 的操作,因此本次完成后 fallback 仍然在刪除列表中,最終會(huì)被刪除。達(dá)到了切換到 primary 組件的目的。整體流程為:

利用 Suspense 自己實(shí)現(xiàn)數(shù)據(jù)加載

在我們明白了 lazy + Suspense 的原理之后,可以自己利用 Suspense 來(lái)進(jìn)行數(shù)據(jù)加載,其無(wú)非就是三種狀態(tài):

  • 初始化:查詢(xún)數(shù)據(jù),拋出 promise
  • 加載中: 直接拋出 promise
  • 加載完成:設(shè)置 promise 返回的數(shù)據(jù)

按照這樣的思路,設(shè)計(jì)一個(gè)簡(jiǎn)單的數(shù)據(jù)加載功能:

// 模擬請(qǐng)求 promise
function mockApi(){
  return delay(5000).then(() => "data fetched")
}
// 處理請(qǐng)求狀態(tài)變更
function fetchData(){
  let status = "uninit"
  let data = null
  let promise = null
  return () => {
    switch(status){
      // 初始狀態(tài),發(fā)出請(qǐng)求并拋出 promise
      case "uninit": {
        const p = mockApi()
          .then(x => {
            status = "resolved"
            data = x
          })
          status = "loading"
          promise = p
        throw promise
      };
      // 加載狀態(tài),直接拋出 promise
      case "loading": throw promise;
      // 如果加載完成直接返回?cái)?shù)據(jù)
      case "resolved": return data;
      default: break;
    }
  }
}
const reader = fetchData()
function TestDataLoad(){
  const data = reader()
  return (
    <p>{data}</p>
  )
}
function App() {
  const [count, setCount] = useState(1)
  useEffect(() => {
    setInterval(() => setCount(c => c > 100 ? c: c + 1), 1000)
  }, [])
  return (
     <>
        <Suspense fallback={"loading"}>
          <TestDataLoad/>
        </Suspense>
        <p>count: {count}</p>
     </>
  )
}

結(jié)果為一開(kāi)始顯示 fallback 中的 loading,數(shù)據(jù)加載完成后顯示 data fetched。你可以在這里進(jìn)行在線(xiàn)體驗(yàn):codesandbox.io/s/suspiciou…

關(guān)于更多使用 Suspense 進(jìn)行數(shù)據(jù)加載這方面的內(nèi)容,可以參考 react 的官方文檔: 17.reactjs.org/docs/concur… 。

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

相關(guān)文章

  • 關(guān)于React中的聲明式渲染框架問(wèn)題

    關(guān)于React中的聲明式渲染框架問(wèn)題

    這篇文章主要介紹了React中的聲明式渲染框架,我們先討論了命令式和聲明式這兩種范式的差異,其中命令式更加關(guān)注過(guò)程,而聲明式更加關(guān)注結(jié)果,對(duì)React渲染框架知識(shí)感興趣的朋友跟隨小編一起看看吧
    2022-06-06
  • React事件處理超詳細(xì)介紹

    React事件處理超詳細(xì)介紹

    這篇文章主要介紹了React事件處理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • React中的useEffect useLayoutEffect到底怎么用

    React中的useEffect useLayoutEffect到底怎么用

    這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2023-02-02
  • forwardRef?中React父組件控制子組件的實(shí)現(xiàn)代碼

    forwardRef?中React父組件控制子組件的實(shí)現(xiàn)代碼

    forwardRef 用于拿到父組件傳入的 ref 屬性,這樣在父組件便能通過(guò) ref 控制子組件,這篇文章主要介紹了forwardRef?-?React父組件控制子組件的實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2024-01-01
  • react常見(jiàn)的ts類(lèi)型實(shí)踐解析

    react常見(jiàn)的ts類(lèi)型實(shí)踐解析

    這篇文章主要為大家介紹了react常見(jiàn)的ts類(lèi)型實(shí)踐解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • react如何向數(shù)組中追加值

    react如何向數(shù)組中追加值

    這篇文章主要介紹了react如何向數(shù)組中追加值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • react.js使用webpack搭配環(huán)境的入門(mén)教程

    react.js使用webpack搭配環(huán)境的入門(mén)教程

    本文主要介紹了react 使用webpack搭配環(huán)境的入門(mén)教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-08-08
  • React創(chuàng)建組件的三種方式及其區(qū)別是什么

    React創(chuàng)建組件的三種方式及其區(qū)別是什么

    在React中,創(chuàng)建組件的三種主要方式是函數(shù)式組件、類(lèi)組件和使用React Hooks的函數(shù)式組件,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下
    2023-08-08
  • Redux thunk中間件及執(zhí)行原理詳細(xì)分析

    Redux thunk中間件及執(zhí)行原理詳細(xì)分析

    redux的核心概念其實(shí)很簡(jiǎn)單:將需要修改的state都存入到store里,發(fā)起一個(gè)action用來(lái)描述發(fā)生了什么,用reducers描述action如何改變state tree,這篇文章主要介紹了Redux thunk中間件及執(zhí)行原理分析
    2022-09-09
  • React實(shí)現(xiàn)下拉框的key,value的值同時(shí)傳送

    React實(shí)現(xiàn)下拉框的key,value的值同時(shí)傳送

    這篇文章主要介紹了React實(shí)現(xiàn)下拉框的key,value的值同時(shí)傳送方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08

最新評(píng)論