기록을 불러오는 중입니다...
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 를 다음과 같이 소개하고 있습니다. (링크)function Component() {
const result = use(somePromise);
return ...;
}Suspense 와 프로미스가 어떻게 어울려 동작하는지 가볍게 살펴보고 서스펜서를 활용하는 방법과 최신 API인 use 까지 알아보았습니다. 오랜만에 리액트 공식 문서도 살펴보았네요. 😆