SuspenseBoundary & ErrorBoundary
๋ก๋ฉ(Suspense)๊ณผ ์๋ฌ(ErrorBoundary)๋ฅผ ํ๋์ ์ ์ธ์ ์ปดํฌ๋ํธ๋ก ๋ฌถ์ด์ฃผ๋ ์ ํธ๋ฆฌํฐ์ ๋๋ค.
์ธ์ ์ฌ์ฉํ๋์?
- ๐ ๋น๋๊ธฐ ์ปดํฌ๋ํธ ๋ก๋ฉ/์๋ฌ ์ฒ๋ฆฌ
- ๐ ์ฌ์๋ ๋ก์ง ๊ตฌํ
- ๐ ์๋ฌ ์ถ์ ๋ฐ ๋ก๊น
- ๐ฏ ์๋ฌ ๊ฒฉ๋ฆฌ (๋ถ๋ถ ์คํจ ํ์ฉ)
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
SuspenseBoundary
import { SuspenseBoundary } from '@frontend-toolkit-js/components';
<SuspenseBoundary
pendingFallback={<Spinner />}
errorFallback={<ErrorMessage />}
>
<UserProfile userId={userId} />
</SuspenseBoundary>;ErrorBoundary (๋จ๋ )
import { ErrorBoundary } from '@frontend-toolkit-js/components';
<ErrorBoundary fallback={<ErrorMessage />}>
<NonSuspenseWidget />
</ErrorBoundary>;์ฃผ์ ์ฌ์ฉ ์ฌ๋ก
1. ํจ์ํ ์๋ฌ Fallback (์ฌ์๋)
<SuspenseBoundary
pendingFallback={<Loading />}
errorFallback={(error, reset) => (
<div>
<p>{error.message}</p>
<button onClick={reset}>๋ค์ ์๋</button>
</div>
)}
>
<UserProfile userId={userId} />
</SuspenseBoundary>2. resetKeys๋ก ์๋ ์ํ ์ด๊ธฐํ
<SuspenseBoundary
pendingFallback={<Loading />}
errorFallback={<ErrorMessage />}
resetKeys={[userId]}
>
<UserProfile userId={userId} />
</SuspenseBoundary>userId๊ฐ ๋ฐ๋๋ฉด ์๋ฌ ์ํ๊ฐ ์๋์ผ๋ก ๋ฆฌ์
๋ฉ๋๋ค.
3. onError / onReset ํ
<SuspenseBoundary
pendingFallback={<Loading />}
errorFallback={<ErrorMessage />}
onError={(error, info) => {
console.error('Error:', error);
Sentry.captureException(error, { contexts: { react: info } });
}}
onReset={() => {
queryClient.invalidateQueries(['user', userId]);
}}
>
<UserProfile userId={userId} />
</SuspenseBoundary>4. ์ค์ฒฉ ์ฌ์ฉ (์๋ฌ ๊ฒฉ๋ฆฌ)
<SuspenseBoundary pendingFallback={<Spinner />} errorFallback={<ErrorUI />}>
{/* ๋ฉ์ธ ์ฝํ
์ธ */}
<MainContent />
{/* ์์ ฏ ์๋ฌ๊ฐ ์ ์ฒด๋ฅผ ๋ง๊ฐ๋จ๋ฆฌ์ง ์์ */}
<ErrorBoundary fallback={<WidgetError />}>
<AsyncWidget />
</ErrorBoundary>
</SuspenseBoundary>API Reference
SuspenseBoundary
interface SuspenseBoundaryProps {
children: ReactNode;
pendingFallback: ReactNode;
errorFallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
onError?: (error: Error, errorInfo: ErrorInfo) => void;
onReset?: () => void;
resetKeys?: unknown[];
}ErrorBoundary
interface ErrorBoundaryProps {
children: ReactNode;
fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
onError?: (error: Error, errorInfo: ErrorInfo) => void;
onReset?: () => void;
resetKeys?: unknown[];
}Props ์์ธ
pendingFallback (SuspenseBoundary๋ง)
Suspense ๋๊ธฐ ์ํ UI์ ๋๋ค.
<SuspenseBoundary pendingFallback={<Spinner />}>- ํ์
:
ReactNode - ํ์: โ
errorFallback / fallback
์๋ฌ ํ์ UI ๋๋ ํจ์ํ UI์ ๋๋ค.
// ReactNode
<SuspenseBoundary errorFallback={<ErrorMessage />}>
// ํจ์ํ (์๋ฌ + reset)
<SuspenseBoundary
errorFallback={(error, reset) => (
<div>
<p>{error.message}</p>
<button onClick={reset}>์ฌ์๋</button>
</div>
)}
>- ํ์
:
ReactNode | (error: Error, reset: () => void) => ReactNode - ํ์: โ
resetKeys
๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ์๋์ผ๋ก ์๋ฌ ์ํ๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
<SuspenseBoundary
errorFallback={<Error />}
resetKeys={[userId, postId]}
>- ํ์
:
unknown[] - ๊ธฐ๋ณธ๊ฐ:
undefined
onError
์๋ฌ ๋ฐ์ ์ ์ฝ๋ฐฑ์ ๋๋ค.
<SuspenseBoundary
onError={(error, info) => {
console.error('Error:', error);
console.error('Component Stack:', info.componentStack);
}}
>- ํ์
:
(error: Error, errorInfo: ErrorInfo) => void - ๊ธฐ๋ณธ๊ฐ:
undefined
onReset
์๋ฌ ์ํ๊ฐ ๋ฆฌ์ ๋ ๋ ํธ์ถ๋ฉ๋๋ค.
<SuspenseBoundary
onReset={() => {
queryClient.invalidateQueries(['user']);
}}
>- ํ์
:
() => void - ๊ธฐ๋ณธ๊ฐ:
undefined
ํจํด ๊ฐ์ด๋
โ ๊ถ์ฅ
// resetKeys๋ก ์ฌ์ฉ์ ์ ํ ์ ์๋ ์ด๊ธฐํ
<SuspenseBoundary resetKeys={[userId]} />
// ํจ์ํ fallback์์ ์บ์ ์ญ์ ํ reset
<SuspenseBoundary
errorFallback={(error, reset) => {
removeCache(error);
return <RetryButton onClick={reset} />;
}}
/>โ ์ง์
// ๊ธด ๋ก๋ฉ UI๋ฅผ ์ง์ children์์ if/else๋ก ๋ถ๊ธฐ
{
loading ? <Spinner /> : error ? <Error /> : <Content />;
}
// โ SuspenseBoundary๋ก ๊ฐ์ธ์ ์ ์ธ์ ์ผ๋ก ํํ๋ด๋ถ ๋์
React Error Boundary
ErrorBoundary๋ React์ Error Boundary๋ฅผ ์ฌ์ฉํฉ๋๋ค.
class ErrorBoundaryImpl extends Component {
static getDerivedStateFromError(error: Error) {
return { error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.props.onError?.(error, errorInfo);
}
render() {
if (this.state.error) {
return this.props.fallback;
}
return this.props.children;
}
}resetKeys ๋์
componentDidUpdate(prevProps) {
// resetKeys๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์๋ฌ ๋ฆฌ์
if (hasResetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {
this.reset();
}
}์ฑ๋ฅ ํน์ฑ
๋ฒ๋ค ํฌ๊ธฐ
- ~1.5 KB (minified)
- ~0.6 KB (gzipped)
๋ฉ๋ชจ๋ฆฌ
- ์๋ฌ ์ํ 1๊ฐ๋ง ์ ์ง
- ํด๋ฆฐ์ ๋ถํ์
๋๋ฒ๊น ํ
- ๊ฐ๋ฐ ๋ชจ๋์์ ์๋ฌ ์คํ ์๋ ์ถ๋ ฅ
resetKeys๋ฐฐ์ด ์์ ๋ณ๊ฒฝ ์ ๋ฆฌ์ ๋จ- Suspense ๋ฆฌ์์ค์ ํจ๊ป ์ฌ์ฉ ๊ถ์ฅ
TypeScript
<SuspenseBoundary
errorFallback={(error, reset) => {
error.message;
// ^? string
reset();
// ^? () => void
}}
>
<Component />
</SuspenseBoundary>