2023-10-03 · 6 min read · 개발
선언적으로 코드 작성하는 Suspense 알아보기
Motivation
Suspense는 기존 비동기 요청 처리 때 발생하는 여러 문제들을 해결한다고 판단되어 등장하게 되었다.
초기 Suspense는 React.lazy를 활용한 코드 스플리팅에만 지원되었지만,
궁극적으로 Suspense는 비동기 작업을 처리하도록 지원하는 것을 목표로 두고 있다.
이에 이번 글에서는 Suspense를 통해 얻을 수 있는 장점으로 선언적 코드 작성과 관련해서 알아보고자 한다.
Description
Suspense 이전에는 하나의 컴포넌트 안에서 많은 분기가 발생했다.
이는 명령적 코드 작성과도 관련이 있다.
이러한 문제점을 해결하기 위한 방안으로 Suspense를 활용할 수 있다.
분기를 처리하는 부분에 대한 책임을 Suspense에게 맡기는 것이다.
이를 통해 선언적인 코드 작성이 가능해진다.
이와 관련해서 코드를 통해 이해해보자.
function List({ pageId }) { return items[pageId].map((item) => <li>{item}</li>)}위의 코드에서 컴포넌트를 해석할 때 위에서부터 아래로 코드를 이해할 수 있다. 이러한 코드 형태는 선언적이라고 볼 수 있다.
하지만 여기서 비동기 코드가 더해지면 어떻게 될까?
function List({ pageId }) { const [items, setItems] = useState()
useEffect(() => { fetchItems(pageId).then(setItems) }, [])
return items[pageId].map((item) => <li>{item}</li>)}더 이상 코드를 위에서 아래로 읽을 수 없게 된다.
또한, pageId가 변경되었을 때를 생각해보면 위와 같은 코드 형태는 문제가 발생하게 된다.
다음으로 데이터 패칭 라이브러리를 사용한 경우를 알아보자.
function List({ pageId }) { const [items, isLoading] = useData(pageId)
if (isLoading) { return <Spinner /> }
return items[pageId].map((item) => <li>{item}</li>)}이러한 코드 형태는 위에서부터 아래로 코드를 편하게 읽을 수 있게 해준다.
하지만 여기서도 개선할 부분이 존재한다.
과연 두 개의 관심사, 즉 로딩과 성공이 같이 있을 필요가 있을까?
두 개의 관심사가 같은 공간에 있으면 다음과 같은 문제가 발생할 수 있다.
function Header() { const [data, isLoading] = useData();
if (isLoading) { return /* ??? */ }
return (...);}다른 컴포넌트에서도 데이터를 사용할 때마다 컴포넌트 내부에서 로딩 상태에 대한 분기 처리가 필요해진다.
이때 로딩 상태에서 <Spinner />를 보여줄 수도 있고 null로 처리할 수도 있다.
이를 매번 상황에 따라 다르게 생각해내는 것은 매우 힘든 일이 될 것이다.
코드를 작성하는 입장에서도 컴포넌트 내부 코드를 작성할 때 성공뿐만 아니라 로딩에 대한 신경도 써야하는 어려움이 발생한다.
이제 로딩과 성공 상태를 분리해보자.
function List({ pageId }) { const items = useData(pageId)
return items[pageId].map((item) => <li>{item}</li>)}코드를 위에서부터 아래로 읽으면서 더 이상 로딩 상태에 관심을 가지지 않는다. 오로지 데이터 그자체에만 집중할 수 있게 되는 것이다.
그러면 로딩 상태는 어디서 어떻게 처리되는 것일까?
<Suspense fallback={<Spinner />}> <List pageId={pageId} /></Suspense>컴포넌트 내부에서 로딩 상태를 처리하는 것이 아닌 컴포넌트 외부인 부모 컴포넌트에서 처리하도록 한다.
그리고 여기서도 앞선 예시와 비교해서 중요하게 생각할 부분은 로딩 상태를 jsx 안에 처리한다는 것이다.
선언적인 방식으로 jsx 안에 UI에 대한 작업을 진행할 수 있게 된다.
Suspense를 활용하여 이전 예시 코드를 변경해보자.
function Header() { const data = useQuery(...);
return ( <> {data.info} </> )}컴포넌트 내부에 로딩 상태를 고려하지 않고 데이터에만 집중한다.
<Suspense fallback={<Skeleton />}> <Header /> <List pageId={pageId} /></Suspense>로딩 상태에서도 어느 데이터를 읽어야 하는지 생각할 필요가 없게 된다.
