저도 Suspense 를 이용해 컴포넌트 내에서 로딩 상태에 대한 맥락을 덜어내고 선언적으로 처리하는 것을 좋아합니다.
회상해보니, 본격적으로 Suspense 를 쓰게 된 계기는 아무래도 Tanstack Query 덕분이었습니다. useSuspenseQuery (당시에는 useQuery 에 속성으로 설정했었죠)를 사용하면 Suspense 를 이용해 로딩 상태를 선언적으로 처리할 수 있습니다.
이러한 패턴을 사용하다 보니, 직접 Suspense 에 걸리는 커스텀 훅을 만들어보고 싶어졌습니다.
저는 Suspense 가 ‘프로미스를 던지는 방식으로 작동한다.’ 정도만 알고 있었기에, 한번 코드를 작성해봤습니다.
다만 현재 프로미스의 상태를 위 코드와 같이 속성 접근자를 통해 확인할 수는 없습니다. 그래서 조금 방법을 우회하여 분기처리를 해봅시다.
서스펜서 함수
다른 사람들은 뭐라고 부르는지 잘 모르지만, 제가 서스펜서라고 부르는 녀석이 있습니다. 이 녀석을 이용해서 프로미스의 상태를 분기처리할 수 있습니다.
functiongetSuspenser<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;}elseif(status ==='REJECTED'){thrownewError();}elseif(status ==='FULFILLED'){return result;}}};}const suspenser =getSuspenser(fooPromise);functionComponent(){const result = suspenser.read();return<p>{result}</p>;}
간단한 코드라서 금방 이해하실 수 있으실 것입니다. getSuspenser 는 프로미스를 인자로 받고, 내부적으로 상태를 별도로 관리하며, read 라는 함수가 매 렌더링마다 호출되게끔 하여 프로미스의 상태에 따라 프로미스를 던지거나 값을 반환합니다.
3초 후 “foo” 가 보여집니다.
이렇게 서스펜서를 통해 특정 프로미스를 Suspense 와 함께 사용할 수 있습니다.
다만 여전히 아쉬운 점은 프로미스 객체는 함수 컴포넌트 외부에서 선언되어야 한다는 점인데요, 다행히 리액트 v19에서 이를 편하게 구현할 수 있도록 새로운 훅을 만들어주었습니다.