Suspense ํ†บ์•„๋ณด๊ธฐ ๐Ÿ”Ž

์šฐํ˜ยท2024๋…„ 8์›” 27์ผ
15

React

๋ชฉ๋ก ๋ณด๊ธฐ
5/19

Suspense๋ž€?

๋ฆฌ์•กํŠธ์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ๋•Œ, ๋กœ๋”ฉ ์ค‘์— fallback UI๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ์ค€๋น„๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

  • children: ๋ Œ๋”๋งํ•˜๋ ค๋Š” ์‹ค์ œ UI์ด๋‹ค. children์˜ ๋ Œ๋”๋ง์ด ์ง€์—ฐ๋˜๋ฉด, Suspense๋Š” fallback UI๋ฅผ ๋Œ€์‹  ๋ Œ๋”๋งํ•œ๋‹ค.

  • fallback: ์‹ค์ œ UI๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „๊นŒ์ง€ ๋Œ€์‹  ๋ Œ๋”๋ง ๋˜๋Š” fallback UI์ด๋‹ค. React Node ํ˜•์‹์€ ๋ฌด์—‡์ด๋“  fallback UI๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

<Suspense fallback={<div>๋กœ๋”ฉ ์ค‘...</div>}>
  <LazyComponent />
</Suspense>

์‚ฌ์šฉ ์กฐ๊ฑด

  • Relay๋‚˜ Next.js์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด Suspense๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • lazy๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉํ•  ๋•Œ Suspense๊ฐ€ ํ™œ์„ฑํ™”๋œ๋‹ค.

  • use ํ›…์„ ์‚ฌ์šฉํ•ด์„œ Promise ๊ฐ’์„ ์ฝ๋Š” ๊ฒฝ์šฐ์—๋„ Suspense๊ฐ€ ํ™œ์„ฑํ™”๋œ๋‹ค.

๐Ÿ’ก use ํ›…์ด๋ž€?
Promise ๊ฐ’์„ ์ฝ๊ณ , ํ•ด๋‹น Promise๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋Œ€๊ธฐ ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค€๋‹ค.
โ†ช ์ด๋ฅผ ํ†ตํ•ด Susepnse์™€ ํ†ตํ•ฉํ•˜์—ฌ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ๋Š” fallback UI๋ฅผ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ

  • ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฒ˜์Œ์œผ๋กœ ๋งˆ์šดํŠธ๋˜๊ธฐ ์ „์— ์ง€์—ฐ๋œ ๋ Œ๋”๋ง์„ ํ•˜๋Š” ๋™์•ˆ ์–ด๋–ค ์ƒํƒœ๋„ ์œ ์ง€ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ๋ฆฌ์•กํŠธ๋Š” ์ผ์‹œ ์ค‘์ง€๋œ ํŠธ๋ฆฌ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๋ Œ๋”๋งํ•œ๋‹ค. ์ด๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์ƒํƒœ๊ฐ€ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€๋˜๋„๋ก ํ•œ๋‹ค.

  • ์ฝ˜ํ…์ธ ๊ฐ€ ์ˆจ๊ฒจ์ ธ์•ผ ํ•  ๊ฒฝ์šฐ, ๋ฆฌ์•กํŠธ๋Š” ๋ ˆ์ด์•„์›ƒ ํšจ๊ณผ๋ฅผ ์ •๋ฆฌํ•œ๋‹ค. ์ด๋Š” ์ฝ˜ํ…์ธ ๊ฐ€ ์ˆจ๊ฒจ์ ธ ์žˆ๋Š” ๋™์•ˆ DOM ๋ ˆ์ด์•„์›ƒ์„ ์ธก์ •ํ•˜๋Š” ํšจ๊ณผ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋„๋ก ๋ณด์žฅํ•œ๋‹ค. ์ฝ˜ํ…์ธ ๊ฐ€ ๋‹ค์‹œ ๋ณด์ผ ์ค€๋น„๊ฐ€ ๋˜๋ฉด, ๋ฆฌ์•กํŠธ๋Š” ๋ ˆ์ด์•„์›ƒ ํšจ๊ณผ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•œ๋‹ค.

  • Suspense๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์„ ๋•Œ ๋˜ ๋‹ค์‹œ ์ง€์—ฐ๋˜๋ฉด startTransition, useDeferredValue๋กœ ์ธํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์•„๋‹Œ ์ด์ƒ fallback UI๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•œ๋‹ค.

  • Suspense๋Š” Effect ๋˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ง€ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ’ก startTransition์ด๋ž€?
UI ์—…๋ฐ์ดํŠธ์˜ ์šฐ์„  ์ˆœ์œ„๋ฅผ ์กฐ์ •ํ•˜์—ฌ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•  ๋•Œ ๋Š๋ฆฌ๊ฑฐ๋‚˜ ๋ฐฉํ•ด๋ฐ›์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
โ†ช startTransition ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๊ฐ์‹ธ๋ฉด, ๋ฆฌ์•กํŠธ๋Š” ์ด ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.
์ด๋กœ ์ธํ•ด ๊ธด ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ฆ‰๊ฐ์ ์ธ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•œ๋‹ค.

๐Ÿ’ก useDeferredValue์ด๋ž€?
์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์— ๋Œ€ํ•œ ๋ฐ˜์‘์„ ์ง€์—ฐ์‹œ์ผœ, UI์˜ ๋ถ€๋“œ๋Ÿฌ์›€์„ ์œ ์ง€ํ•˜๋Š” ํ›…์ด๋‹ค. ์ด ํ›…์€ ์ฃผ๋กœ ๊ธด ๋ Œ๋”๋ง ์ž‘์—…์ด๋‚˜ ๋น„์‹ผ ๊ณ„์‚ฐ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์œ ์šฉํ•˜๋‹ค.
โ†ช ์ฃผ์–ด์ง„ ๊ฐ’์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ์ง€์—ฐ์‹œ์ผœ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ๋™์•ˆ UI๊ฐ€ ์ฆ‰์‹œ ๋ฐ˜์‘ํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.


Promise๊ฐ€ Suspense์— ๊ฑธ๋ฆฌ๊ณ  ์—…๋ฐ์ดํŠธ๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š” ๋ฐฉ์‹

function handleError(root, thrownValue): void {
  do { // ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์‹œ์ž‘
    let erroredWork = workInProgress;
    try {
      // ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์‹œ๋„
      resetContextDependencies();
      resetHooksAfterThrow();
      ReactCurrentOwner.current = null;
      // ์˜ˆ์™ธ ๋ฐœ์ƒ
      // ์ด ํ•จ์ˆ˜๋Š” throwValue๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค.
      // ์—ฌ๊ธฐ์„œ throwValue๊ฐ€ Promise์ธ ๊ฒฝ์šฐ, Suspense๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
      throwException( 
        root,
        erroredWork.return,
        erroredWork,
        thrownValue,
        workInProgressRootRenderLanes
      );
      completeUnitOfWork(erroredWork);
    } catch (yetAnotherThrownValue) {
      // throwException์—์„œ ๋˜ ๋‹ค๋ฅธ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด yetAnotherThrownValue์— ์˜ํ•ด ์˜ˆ์™ธ๊ฐ€ ์žกํžŒ๋‹ค.
      thrownValue = yetAnotherThrownValue;
      // ํ˜„์žฌ ์ž‘์—…์ด ์ด๋ฏธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ž‘์—…๊ณผ ๊ฐ™๋‹ค๋ฉด, ์˜ค๋ฅ˜๋ฅผ ๋‹ค์Œ ๊ฒฝ๊ณ„๋กœ ์ „ํŒŒ
      // ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด, erroredWork๋ฅผ ํ˜„์žฌ ์ž‘์—…์„ ์„ค์ •
      if (workInProgress === erroredWork && erroredWork !== null) {
        erroredWork = erroredWork.return;
        workInProgress = erroredWork;
      } else {
        erroredWork = workInProgress;
      }
      continue;
    }
    return;
  } while (true);
}

์ฝ”๋“œ๊ฐ€ ๊ธธ์ง€๋งŒ Suspense๊ฐ€ ๋™์ž‘ํ•˜๋Š” ํ•ต์‹ฌ ๋ถ€๋ถ„์€ throwException, completeUnitOfWork ์ด ๋‘ ํ•จ์ˆ˜๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋œ๋‹ค.

throwException()

1. Incomplete ํ”Œ๋ž˜๊ทธ ์„ค์ •

sourceFiber.flags |= Incomplete;
  • sourceFiber๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์Œ์„ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด Incomplete ํ”Œ๋ž˜๊ทธ๋ฅผ ์„ค์ •ํ•œ๋‹ค.
    โ†ช ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

2. Thenable ์ฒดํฌ

if (
  value !== null &&
  typeof value === 'object' &&
  typeof value.then === 'function'
) {
  // ์ด ๊ฐ์ฒด๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  const wakeable: Wakeable = (value: any);
  ...
} else {
  // ์ผ๋ฐ˜ ์—๋Ÿฌ
}
  • value๊ฐ€ thenable์ธ์ง€ ํ™•์ธํ•˜๋‹ค. ์ฆ‰ Promise์™€ ๊ฐ™์€ ๊ฐ์ฒด์ธ์ง€ ํ™•์ธํ•˜๋ฉฐ, ๊ทธ๋ ‡๋‹ค๋ฉด ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋Š” ์ผ์‹œ ์ค‘๋‹จ(suspend)๋œ๋‹ค.

  • wakeable์€ Promise๋กœ ๊ฐ„์ฃผํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋งŒ์•ฝ Promise๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์ผ๋ฐ˜์ ์ธ ์˜ค๋ฅ˜๋กœ ์ฒ˜๋ฆฌ๋˜์–ด Error Boundary์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค.

3. Susepnse ๊ด€๋ จ ์ฒ˜๋ฆฌ

const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
  • getNearestSuspenseBoundaryToCapture() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด Suspense ๊ฒฝ๊ณ„๋ฅผ ์ฐพ๋Š”๋‹ค. ์ด ๊ฒฝ๊ณ„๋Š” Error Boundary์™€ ์œ ์‚ฌํ•œ ์—ญํ• ์„ ํ•œ๋‹ค.

4. Suspense ๊ฒฝ๊ณ„๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ

if (suspenseBoundary !== null) {
  suspenseBoundary.flags &= ~ForceClientRender;
  markSuspenseBoundaryShouldCapture(
    suspenseBoundary,
    returnFiber,
    sourceFiber,
    root,
    rootRenderLanes
  );
  // ...
}
  • Suspense ๊ฒฝ๊ณ„๊ฐ€ ์กด์žฌํ•˜๋ฉด ForceClientRender ํ”Œ๋ž˜๊ทธ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , markSuspenseBoundaryShouldCapture๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Suspense๊ฐ€ fallback์„ ๋ Œ๋”๋งํ•˜๋„๋ก ์ค€๋น„ํ•œ๋‹ค.

5. Ping ๋ฐ Retry ๋ฆฌ์Šค๋„ˆ ์ถ”๊ฐ€

if (suspenseBoundary.mode & ConcurrentMode) {
  // Ping Listener ์ถ”๊ฐ€
  attachPingListener(root, wakeable, rootRenderLanes);
}

// Retry Listener ์ถ”๊ฐ€
attachRetryListener(suspenseBoundary, root, wakeable, rootRenderLanes);
return;
  • ConcurrentMode์—์„œ๋งŒ ping ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  attachRetryListener๋ฅผ ํ†ตํ•ด weakable(promise)์™€ ๊ด€๋ จ๋œ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.
    โ†ช ์ด๋Š” Promise๊ฐ€ ํ•ด๊ฒฐ๋  ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

Suspense๋ฅผ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ

else {
  if (!includesSyncLane(rootRenderLanes)) { // ๋น„๋™๊ธฐ ์—…๋ฐ์ดํŠธ๋ผ๋ฉด
    attachPingListener(root, wakeable, rootRenderLanes);
    renderDidSuspendDelayIfPossible();
    return;
  }
  
  const uncaughtSuspenseError = new Error(
    "A component suspended while responding to synchronous input. This " +
      "will cause the UI to be replaced with a loading indicator. To " +
      "fix, updates that suspend should be wrapped " +
      "with startTransition."
  );
  
  value = uncaughtSuspenseError;
}
  • ๋™๊ธฐ ์—…๋ฐ์ดํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋Š” ์˜ค๋ฅ˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋น„๋™๊ธฐ ์—…๋ฐ์ดํŠธ์˜ ๊ฒฝ์šฐ์—๋Š” ๋Œ€๊ธฐ ์ƒํƒœ๋กœ ์ „ํ™˜ํ•˜์—ฌ UI๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.
    โ†ช ๋น„๋™๊ธฐ ์—…๋ฐ์ดํŠธ์—์„œ Suspense๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด fallback UI๋Š” ํ‘œ์‹œ๋˜์ง€ ์•Š์ง€๋งŒ, UI์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ๋Œ€๊ธฐํ•˜๋Š” ์ƒํƒœ๋กœ ์ „ํ™˜๋˜์–ด ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด ์—…๋ฐ์ดํŠธ๊ฐ€ ์ด๋ฃจ์–ด์ง€๊ฒŒ ๋œ๋‹ค.

completeUnitOfWork()

function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    if ((completedWork.flags & Incomplete) === NoFlags) {
      ...
    } else {
      const next = unwindWork(current, completedWork, subtreeRenderLanes);

      if (next !== null) {
        next.flags &= HostEffectMask;
        workInProgress = next;
        return;
      }
      
      if (returnFiber !== null) {
        returnFiber.flags |= Incomplete;
        returnFiber.subtreeFlags = NoFlags;
        returnFiber.deletions = null;
      } else {
        workInProgressRootExitStatus = RootDidNotComplete;
        workInProgress = null;
        return;
      }
    }
      
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }

    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
  if (workInProgressRootExitStatus === RootInProgress) {
    workInProgressRootExitStatus = RootCompleted;
  }
}
  • completeUnitOfWork ํ•จ์ˆ˜๋Š” ํ˜„์žฌ ์ž‘์—…์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด unwindWork๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ž‘์—…์„ ์ •๋ฆฌํ•œ๋‹ค.
case SuspenseComponent: {
  popSuspenseContext(workInProgress);
  const flags = workInProgress.flags;
  if (flags & ShouldCapture) {
    workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
    if (
      enableProfilerTimer &&
      (workInProgress.mode & ProfileMode) !== NoMode
    ) {
      transferActualDuration(workInProgress);
    }
    return workInProgress;
  }
  return null;
}
  • ShouldCapture ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ๋‹ค๋ฉด, ์ด๋ฅผ DidCapture๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ํ˜„์žฌ ์ž‘์—…์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    โ†ช ShouldCapture์—์„œ DidCapture๋กœ์˜ ์ „ํ™˜์€ Suspense๊ฐ€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๋‹น ์ž‘์—…์„ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ด์•ผ ํ•จ์„ ์˜๋ฏธํ•œ๋‹ค.

๐Ÿ“ ์ •๋ฆฌํ•˜๊ธฐ

1. Suspense์˜ ์—ญํ• 

  • Suspense๋Š” Didcapture ๋ผ๋Š” ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ๋‚ด์šฉ์„ ๋ Œ๋”๋งํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. ์ด ํ”Œ๋ž˜๊ทธ์— ๋”ฐ๋ผ fallback UI ๋˜๋Š” ๊ธฐ๋ณธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.

2. Offscreen ์ปดํฌ๋„ŒํŠธ

  • Suspense๋Š” ๋‚ด์šฉ์„ Offscreen ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ธ์„œ, fallback์ด ๋ Œ๋”๋ง๋  ๋•Œ๋„ ๊ธฐ๋ณธ ๋‚ด์šฉ์ด Fiber ํŠธ๋ฆฌ์—์„œ ์ œ๊ฑฐ๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค. ์ด๋Š” ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

3. ์žฌ์กฐ์ •(reconciling) ๊ณผ์ •์—์„œ์˜ ํŒ๋‹จ

  • ์žฌ์กฐ์ • ์ค‘์— Suspense๋Š” Didcapture ํ”Œ๋ž˜๊ทธ์— ๋”ฐ๋ผ Offscreen์„ ๊ฑด๋„ˆ๋›ธ์ง€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค. ์ด๋กœ ์ธํ•ด ์ผ๋ถ€ Fiber๋ฅผ ์ˆจ๊ธฐ๋Š” ํšจ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

4. Promise๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ

  • Promise๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด Suspense ๊ฒฝ๊ณ„๋ฅผ ์ฐพ์•„์„œ ShouldCapture ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋œ๋‹ค. Promise๋Š” ping, retry ๋ฆฌ์Šค๋„ˆ์™€ ์—ฐ๊ฒฐ๋œ๋‹ค.

5. ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด Suspense ์™„๋ฃŒ ์‹œ

  • ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด Suspense๋ฅผ ์™„๋ฃŒํ•˜๋ ค ํ•  ๋•Œ ShouldCapture๊ฐ€ Didcapture๋กœ ๋ณ€๊ฒฝ๋˜๊ณ , ํ˜„์žฌ ์ƒํƒœ์—์„œ fallback UI๋ฅผ ๋ Œ๋”๋งํ•  ์ค€๋น„๋ฅผ ํ•œ๋‹ค.

6. ์ž‘์—… ๋ฃจํ”„

  • ์ž‘์—… ๋ฃจํ”„๋Š” Suspense๋ฅผ ๊ณ„์† ์žฌ์กฐ์ •ํ•˜๋ฉฐ, fallback UI๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.

7. Primise๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์„ ๋•Œ

  • Ping ๋ฐ retry ๋ฆฌ์Šค๋„ˆ๋Š” ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค.

๐Ÿ’ก Offscreen ์ปดํฌ๋„ŒํŠธ๋ž€?
๊ธฐ์กด ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ์„œ fallback UI๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ๋™์•ˆ ํ•ด๋‹น ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™”๋ฉด์— ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ•˜๊ณ , ๋™์‹œ์— ๊ทธ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•œ๋‹ค.

Offscreen ์ปดํฌ๋„ŒํŠธ์˜ ์ž‘๋™ ๋ฐฉ์‹

1. ๊ฐ€์‹œ์„ฑ ์ƒํƒœ: Offscreen ์ปดํฌ๋„ŒํŠธ๋Š” visible, hidden์ด๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ์ƒํƒœ๋ฅผ ๊ฐ€์ง„๋‹ค.

2. hidden ์ƒํƒœ: ๋ฆฌ์•กํŠธ๋Š” OffscreenLane์—์„œ ์žฌ์กฐ์ •์„ ๋ฏธ๋ฃจ๊ณ (bail out) ์ฒซ ๋ฒˆ์งธ ํŒจ์Šค์—์„œ ์ฒ˜๋ฆฌ๋ฅผ ๊ฑด๋„ˆ๋›ฐ์–ด ๋ฆฌ์†Œ์Šค๋ฅผ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค.

3. visible ์ƒํƒœ: ์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์žฌ์กฐ์ •์ด ์ง„ํ–‰๋œ๋‹ค.

4. completeWork ๋‹จ๊ณ„: ์ด ๋‹จ๊ณ„์—์„œ ๊ฐ€์‹œ์„ฑ ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐ€์‹œ์„ฑ ์ƒํƒœ๊ฐ€ ์„ค์ •๋œ๋‹ค.
๋งŒ์•ฝ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ณด์ด๋Š” ์ƒํƒœ๋ผ๋ฉด ๋ฆฌ์•กํŠธ๋Š” ์‹ค์ œ๋กœ ํ™”๋ฉด์— ๋ณด์ผ DOM ์š”์†Œ๋ฅผ ์—ฌ๊ธฐ์„œ ์ถ”๊ฐ€ํ•œ๋‹ค.

5. ์ปค๋ฐ‹ ๋‹จ๊ณ„: ์ˆจ๊ฒจ์ง„ DOM ์š”์†Œ๊ฐ€ ์ด ๋‹จ๊ณ„์—์„œ ์ถ”๊ฐ€๋˜๋ฉฐ, ๋งŒ์•ฝ ๊ฐ€์‹œ์„ฑ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ ๋ฆฌ์•กํŠธ๋Š” DOM ๋…ธ๋“œ๋ฅผ ์ˆจ๊ธฐ๊ฑฐ๋‚˜ ๋‹ค์‹œ ํ‘œ์‹œํ•œ๋‹ค.


๐Ÿ™ƒ ๋„์›€์ด ๋˜์—ˆ๋˜ ์ž๋ฃŒ๋“ค

How Suspense works internally in Concurrent Mode 1 - Reconciling flow
How Suspense works internally in Concurrent Mode 2 - Offscreen component
Suspense - React ๊ณต์‹ ๋ฌธ์„œ(v18.3.1)
Suspense ์„ค๋ช…ํšŒ

0๊ฐœ์˜ ๋Œ“๊ธ€