Skip to Content
Suspensive v3를 준비하고 있습니다. 🚀 더보기
Suspensive with star
React Suspense를 위한 모든 것
모든 선언적 API를 제공

<Suspense/>, <ErrorBoundary/>, <ErrorBoundaryGroup/> 등을 제공합니다. 별 다른 노력없이 쉽게 사용할 수 있습니다.

Zero 의존성, 오직 React

단순히 React의 개념을 확장한 것입니다. <Suspense/>, <ErrorBoundary/>, <ErrorBoundaryGroup/>와 같은 React 개발자에게 친숙한 이름으로 컴포넌트들을 제공합니다.

서버 사이드 렌더링에서도 쉽게

Suspensive는 개발자가 서버 사이드 렌더링 환경에서도 React Suspense를 점진적으로 채택할 수 있도록 clientOnly를 제공합니다.

대표적인 라이브러리인 TanStack Query로 Suspense 없이 코드를 작성한다면 이렇게 작성합니다.

이 경우 isLoadingisError를 체크하여 로딩과 에러 상태를 처리하고 타입스크립트적으로 data에서 undefined를 제거할 수 있습니다.

그런데 만약 조회해야 할 api가 더 많아진다고 가정해봅시다.

조회해야 하는 API가 더 많아진다면 이 로딩상태와 에러상태를 처리하는 코드가 더욱 복잡해집니다.

Suspense를 사용하면 타입적으로 코드가 간결해집니다. 하지만 컴포넌트의 깊이는 깊어질 수 밖에 없습니다.

useSuspenseQuerySuspenseErrorBoundary를 사용하여 외부에서 로딩과 에러 상태를 처리할 수 있습니다. 하지만 useSuspenseQuery는 hook이기 때문에 부모에 SuspenseErrorBoundary를 두기 위해 컴포넌트가 분리되어야만 하기 때문에 뎁스가 깊어지는 문제가 있습니다.

Suspensive의 SuspenseQuery 컴포넌트를 사용하면 hook의 제약을 피해 같은 뎁스에서 더욱 쉽게 코드를 작성할 수 있습니다.

  1. SuspenseQuery를 사용하면 depth를 제거할 수 있습니다.
  2. UserInfo라는 컴포넌트를 제거하고 UserProfile과 같은 Presentational 컴포넌트만 남으므로 테스트하기 쉬워집니다.
const Page = () => {
const userQuery = useQuery(userQueryOptions())
const postsQuery = useQuery({
...postsQueryOptions(),
select: (posts) => posts.filter(({ isPublic }) => isPublic),
})
const promotionsQuery = useQuery(promotionsQueryOptions())
if (
userQuery.isLoading ||
postsQuery.isLoading ||
promotionsQuery.isLoading
) {
return 'loading...'
}
if (userQuery.isError || postsQuery.isError || promotionsQuery.isError) {
return 'error'
}
return (
<Fragment>
<UserProfile {...userQuery.data} />
{postsQuery.data.map((post) => (
<PostListItem key={post.id} {...post} />
))}
{promotionsQuery.data.map((promotion) => (
<Promotion key={promotion.id} {...promotion} />
))}
</Fragment>
)
}

이것이 우리가 Suspensive를 만드는 이유입니다


Suspense, ClientOnly, DefaultProps


Next.js와 같은 프레임워크를 사용하다보면 서버에서 Suspense를 사용하기 어려울 때가 있습니다.

혹은 서버에서 Suspense를 사용하고 싶지 않을 때가 있습니다.

이럴 때 Suspensive의 ClientOnly를 사용하면 쉽게 해결할 수 있습니다.

ClientOnly를 감싸주기만 하면 해결됩니다.

혹은 Suspensive의 Suspense에는 clientOnly prop을 활용해 이러한 경우를 쉽게 대응할 수 있습니다.

간단하죠?

그런데 개발을 하다보면 Suspense에 일일이 fallback을 넣어주기 어려울 때가 있습니다.

특히 Admin과 같은 제품을 할 때 디자이너가 일일이 지정해주지 않는 경우가 있어서 기본값을 주고 싶을 때가 있습니다. 그럴 때 DefaultProps를 활용해보세요.

기본 fallback이 바로 나오는 것보다는 FadeIn과 같은 효과를 주고 싶을 때가 있습니다.

그렇다면 FadeIn을 활용해보면 어떨까요?

당연히 기본 fallback을 Override하고 싶다면 그냥 넣어주면 됩니다.

디자이너가 이 부분에 기본 Spinner보다 Skeleton으로 동작하도록 지원해달라고 하네요~! 그냥 넣으면 되어요.

const Page = () => (
<Suspense fallback={<Spinner />}>
<SuspenseQuery {...notNeedSEOQueryOptions()}>
{({ data }) => <NotNeedSEO {...data} />}
</SuspenseQuery>
</Suspense>
)

ErrorBoundary, ErrorBoundaryGroup


ErrorBoundary의 fallback 외부에서 ErrorBoundary를 reset하고 싶을 때 resetKeys를 사용해야 합니다.

이는 깊은 컴포넌트의 경우 resetKey를 전달해야 하는 문제가 있습니다. 또한 state를 만들어 resetKey를 전달해야 하는 문제가 있습니다.

Suspensive가 제공하는 ErrorBoundary와 ErrorBoundaryGroup을 조합하면 이러한 문제를 매우 단순히 해결할 수 있습니다.

ErrorBoundaryGroup을 사용해보세요.

그런데 ErrorBoundary를 사용하다보면 특정 Error에 대해서만 처리하고 싶을 때가 있습니다.

그럴 때에는 Suspensive가 제공하는 ErrorBoundaryshouldCatch를 써보세요. 이 shouldCatch에 Error Constructor를 넣으면 해당 Error에 대해서만 처리할 수 있습니다.

혹은 반대로 그 Error만 빼고 처리할 수도 있습니다.

그럴 때에는 shouldCatch에 callback을 넣어서 처리할 수 있습니다.

const Page = () => {
const [resetKey, setResetKey] = useState(0)
return (
<Fragment>
<button onClick={() => setResetKey((prev) => prev + 1)}>
error reset
</button>
<ErrorBoundary resetKeys={[resetKey]} fallback="error">
<ThrowErrorComponent />
</ErrorBoundary>
<DeepComponent resetKeys={[resetKey]} />
</Fragment>
)
}
const DeepComponent = ({ resetKeys }) => (
<ErrorBoundary resetKeys={resetKeys} fallback="error">
<ThrowErrorComponent />
<ErrorBoundary resetKeys={resetKeys} fallback="error">
<ThrowErrorComponent />
</ErrorBoundary>
</ErrorBoundary>
)
수정된 날짜: