기록을 불러오는 중입니다...
Suspense 를 활용하시는 분들이 많은 것 같습니다.Suspense 를 이용해 컴포넌트 내에서 로딩 상태에 대한 맥락을 덜어내고 선언적으로 처리하는 것을 좋아합니다.Suspense 를 쓰게 된 계기는 아무래도 Tanstack Query 덕분이었습니다. useSuspenseQuery (당시에는 useQuery 에 속성으로 설정했었죠)를 사용하면 Suspense 를 이용해 로딩 상태를 선언적으로 처리할 수 있습니다.Suspense 에 걸리는 커스텀 훅을 만들어보고 싶어졌습니다.Suspense 가 ‘프로미스를 던지는 방식으로 작동한다.’ 정도만 알고 있었기에, 한번 코드를 작성해봤습니다.function Component() {
throw new Promise(res => setTimeout(() => res('foo'), 3000));
return ...;
}
function Page() {
return (
<Suspense fallback={<p>loading...</p>}>
<Component />
</Suspense>
);
}Page 컴포넌트는 Suspense 로 Component 컴포넌트를 감싸고 있습니다. 실제로 개발 서버로 돌려보면 로딩 UI가 잘 보이는 것을 확인할 수 있습니다.const fooPromise = new Promise(res => setTimeout(() => res('foo'), 3000));
function Component() {
throw fooPromise;
return ...;
}
function Component() {
promiseChecker(fooPromise);
return ...;
}
function PromiseChecker(promise) {
// 프로미스가 pending 상태일 때, 프로미스를 던집니다.
if(promise.pending) {
throw promise;
}
// 프로미스가 이행된 상태일 때
if(promise.fulfilled) {
return someValue;
}
// 프로미스가 거부된 상태일 때
if(promise.rejected) {
return someError;
}
}function getSuspenser<T>(promise: Promise<T>) {
let status: 'PENDING' | 'FULFILLED' | 'REJECTED' = 'PENDING';
let result: T | undefined = undefined;
const settledPromise = promise.then(
res => {
status = 'FULFILLED';
result = res;
},
err => {
status = 'REJECTED';
result = err;
}
);
return {
read() {
if (status === 'PENDING') {
throw settledPromise;
} else if (status === 'REJECTED') {
throw new Error();
} else if (status === 'FULFILLED') {
return result;
}
}
};
}
const suspenser = getSuspenser(fooPromise);
function Component() {
const result = suspenser.read();
return <p>{result}</p>;
}getSuspenser 는 프로미스를 인자로 받고, 내부적으로 상태를 별도로 관리하며, read 라는 함수가 매 렌더링마다 호출되게끔 하여 프로미스의 상태에 따라 프로미스를 던지거나 값을 반환합니다.Suspense 와 함께 사용할 수 있습니다.use APIuse 를 다음과 같이 소개하고 있습니다. (링크)use는 Promise나 Context와 같은 데이터를 참조하는 React API입니다.Promise와 함께 호출될 때useHook은Suspense및 Error Boundary와 통합됩니다.use에 전달된 Promise가 대기(Pending)하는 동안use를 호출하는 컴포넌트는 Suspend됩니다.use를 호출하는 컴포넌트가 Suspense 경계로 둘러싸여 있으면 Fallback이 표시됩니다. Promise가 리졸브되면 Suspense Fallback은useHook이 반환한 컴포넌트로 대체됩니다.use에 전달된 Promise가 Reject되면 가장 가까운 Error Boundary의 Fallback이 표시됩니다.
function Component() {
const result = use(somePromise);
return ...;
}Suspense 와 프로미스가 어떻게 어울려 동작하는지 가볍게 살펴보고 서스펜서를 활용하는 방법과 최신 API인 use 까지 알아보았습니다. 오랜만에 리액트 공식 문서도 살펴보았네요. 😆