React 애플리케이션에서 비동기 작업을 더 선언적으로 관리할 수 있게 해준다. 컴포넌트 UI가 로딩 상태일 때 다른 컴포넌트를 렌더링하여 관리한다.
React에서 비동기 데이터를 읽어올 때 패칭이 완료되어야 UI가 나타나고, 폭포수(waterfall)처럼 순차적으로 나타나는 현상이 있다. 특히 한 페이지 상의 여러 컴포넌트에서 동시에 비동기 데이터를 읽어오는 경우 자주 발생한다. 또한, 비동기 통신은 요청한 순서대로 응답되는 보장이 없기 때문에 싱크에 맞지 않는 데이터를 제공하게 될 수도 있다.
이를 개선하기 위해 React 18부터 정식으로 Suspense를 도입하였다.
// ReactFiberWorkLoop
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
// The work loop is suspended. We need to either unwind the stack or
// replay the suspended component.
const unitOfWork = workInProgress;
const thrownValue = workInProgressThrownValue;
resumeOrUnwind: switch (workInProgressSuspendedReason) {
case SuspendedOnError: {
// Unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(unitOfWork, thrownValue);
break;
}
case SuspendedOnData: {
const thenable: Thenable<mixed> = (thrownValue: any);
if (isThenableResolved(thenable)) {
// The data resolved. Try rendering the component again.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
replaySuspendedUnitOfWork(unitOfWork);
break;
}
// The work loop is suspended on data. We should wait for it to
// resolve before continuing to render.
const onResolution = () => {
// Check if the root is still suspended on this promise.
if (
workInProgressSuspendedReason === SuspendedOnData &&
workInProgressRoot === root
) {
// Mark the root as ready to continue rendering.
workInProgressSuspendedReason = SuspendedAndReadyToContinue;
}
// Ensure the root is scheduled. We should do this even if we're
// currently working on a different root, so that we resume
// rendering later.
ensureRootIsScheduled(root);
};
thenable.then(onResolution, onResolution);
break outer;
}
/*** 생략 ***/
} else {
workLoopConcurrent();
}
break;
} catch (thrownValue) {
handleThrow(root, thrownValue);
}
} while (true);
Suspense는 try... catch 기반으로 구현되어 있다. 이때 catch문에서 handleThrow라는 함수로 Promise객체를 throw하고 있다. Suspense는 데이터 패칭이 완료되지 않으면 Promise를 throw하여 Suspense의 fallback 컴포넌트를 렌더링한다. Promise가 fulfilled 상태가 되면 자식 컴포넌트를 렌더링 하게 된다.
fallback에는 실제 UI가 로딩이 끝날 때까지 보여줄 다른 컴포넌트를 넣어준다. 보통 스켈레톤 이미지가 많이 들어간다.
<Suspense fallback={<Loading />}>
<MyComponent />
</Suspense>
참고글: https://www.daleseo.com/react-suspense/
https://velog.io/@shinhw371/React-suspense-throw
https://syjn99.medium.com/react-suspense%EB%9E%80-557a7d3ecd45