๐ ๋ฆฌ์กํธ์์์ ์๋ฌ ํธ๋ค๋ง
๋ฆฌ์กํธ์ ํ ์ปดํฌ๋ํธ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์ ์ ํ ์๋ฌ ํธ๋ค๋ง์ ํ์ง ์๋๋ค๋ฉด
์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด๊ฐ ์ค๋จ๋ ์ ์์ต๋๋ค.
์ ๋ ํ์ API ํธ์ถ ๋ก์ง์ try - catch
๋ฅผ ์ด์ฉํด ๊ตฌ๊ตฌ์ ์ ๋ช
๋ นํ์ผ๋ก ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๊ณค ํ์ต๋๋ค.
์ด์ ๊ฐ์ ๋ฐฉ์์ ํ ์ปดํฌ๋ํธ ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๊ฒฝ์ฐ, ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์์์ ๊ฒฝ์ฐ,
๋ ๊ฐ์ง ์กฐ๊ฑด์ ๋ํ ๋ก์ง์ด ์์ฌ ์ฝ๋ ๋ผ์ธ ์๊ฐ ๊ธธ์ด์ง๊ณ ๊ฐ๋ ์ฑ์ ์ ํดํฉ๋๋ค.
๋ํ ์๋ฌ ํธ๋ค๋ง์ ๋ํ ๋ช ํํ ๊ธฐ์ค์ด ์๋ค๋ฉด ๊ฐ๋ฐ์๋ง๋ค ๋ค๋ฅธ ์๋ฌ ํธ๋ค๋ง์ผ๋ก ์ฝ๋์ ํต์ผ์ฑ์ ์์ด๋ฒ๋ฆด ์ ์์ต๋๋ค.
<ErrorBoundary fallback={ <p>์๋ฌ ๋ฐ์</p> }>
<Component />
</ErrorBoundary>
ํ์ง๋ง ์์ ๊ฐ์ด ErrorBoundary๋ฅผ ์ด์ฉํด ์ ์ธ์ ์ผ๋ก ์๋ฌ ํธ๋ค๋ง์ ํ๊ฒ ๋๋ฉด
์๋ฌ ํธ๋ค๋ง์ ๊ดํ ๋ชจ๋ ๊ถํ์ ์์ํ๊ฒ ๋์ด
ํ์ ์ปดํฌ๋ํธ์์๋ ์๋ฌ ๋ฐ์์ ๋ํ ๋ก์ง์ ๋ฐฐ์ ํ ์ ์๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ผ ErrorBoundary์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
๐ ErrorBoundary
<GlobalErrorBoundary fallback={ <p>์๋ฌ ๋ฐ์</p> }>
<FetchErrorBoundary fallback={ <p>์๋ฌ ๋ฐ์</p> }>
<Component />
</FetchErrorBoundary>
</GlobalErrorBoundary>
React 16 ๋ฒ์ ๋ถํฐ Error Boundary ๊ฐ๋ ์ด ๋์ ๋์์ต๋๋ค.
์ปดํฌ๋ํธ์ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒฝ๊ณ๋ฅผ ๋๋ฌ ์ปดํฌ๋ํธ์์ ๋ฑ์ ์๋ฌ๋ฅผ ErrorBoundary์์ ์ฒ๋ฆฌํ๊ณ ,
๋ก๋ฉ ์ํ์ ๋ํ fallback UI๋ฅผ ๋์ฒดํด์ ๋ณด์ฌ์ฃผ๋ Suspense
์ฒ๋ผ
์๋ฌ ์ํ์ ๋ํ fallback UI๋ฅผ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.
์ด๋ฌํ ๊ฒฝ๊ณ๋ฅผ ๊ณ์ธต๋ณ๋ก ๋๋ ์๋ฌ ์ ํ์ ๋ฐ๋ผ
์ ์ ํ๊ฒ ErrorBoundary๋ฅผ ๋ฐฐ์นํ๋ฉด ํจ์จ์ ์ธ ์๋ฌ ํธ๋ค๋ง์ด ๊ฐ๋ฅํฉ๋๋ค.
๐ ์ปดํฌ๋ํธ
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
// ์ปดํฌ๋ํธ์ ์ด๊ธฐ ์ํ ์ค์
this.state = { hasError: false };
}
// ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ํธ์ถ๋๋ ๋ฉ์๋
static getDerivedStateFromError(error) {
// ์ํ๋ฅผ ์
๋ฐ์ดํธํ์ฌ ์๋ฌ ๋ฐ์ ์ฌ๋ถ๋ฅผ ํ์
return { hasError: true };
}
// componentDidCatch()๋ ์๋ฌ๊ฐ ๋ฐ์ํ ํ ํธ์ถ๋๋ ๋ฉ์๋
componentDidCatch(error, errorInfo) {
// ์๋ฌ ์ ๋ณด์ ํจ๊ป ์๋น์ค์ ์๋ฌ ๋ก๊ทธ๋ฅผ ๊ธฐ๋กํ๋ ํจ์ ํธ์ถ
logErrorToMyService(error, errorInfo);
}
render() {
// ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ์๋ฌ ๋ฉ์์ง๋ฅผ ํ์
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
// ์๋๋ฉด ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง
return this.props.children;
}
}
์ ์ฝ๋๋ ๊ณต์ ๋ฌธ์์์ ๋ฐ์ทํ ErrorBoundary ์ปดํฌ๋ํธ ์ฝ๋์ ๋๋ค.
ํด๋์คํ ์ปดํฌ๋ํธ์ ๊ฐ๋ ฅํ ๋ผ์ดํ ์ฌ์ดํด ๊ธฐ๋ฅ์ผ๋ก ์๋ฌ๋ฅผ ๊ฐ์งํ ์ ์๊ธฐ ๋๋ฌธ์
ํจ์ํ์ผ๋ก๋ ์ฌ์ฉํ ์ ์๋ ๊ฒ ํน์ง์
๋๋ค.
์ฌ๊ธฐ์ ์ฃผ์ ๊น๊ฒ ๋ด์ผ ํ ํจ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๐ static getDerivedStateFromError(error)
- ์ด ๋ฉ์๋๋ ํ์ ์ปดํฌ๋ํธ์์ ๋ฐ์ํ ์๋ฌ๋ฅผ ๊ฐ์งํ๊ณ , ์ํ ์ ๋ฐ์ดํธ๋ฅผ ํตํด ์๋ฌ ๋ฐ์ ์ฌ๋ถ๋ฅผ ์ปดํฌ๋ํธ ์ํ์ ๋ฐ์ํฉ๋๋ค.
- ์ด ํจ์๋ ์ปค๋ฐ ๋จ๊ณ์์ ํธ์ถ๋๊ธฐ ๋๋ฌธ์ ์ฌ์ด๋ ์ดํํธ๊ฐ ๋ฐ์๋๋ ์์ ์ ์ง์ํด์ผ ํฉ๋๋ค.
- ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์ด ๋ฉ์๋๊ฐ ํธ์ถ๋์ด
hasError
์ํ๋ฅผtrue
๋ก ๋ณ๊ฒฝํฉ๋๋ค.
๐ componentDidCatch(error, errorInfo)
- ์ปดํฌ๋ํธ์์ ๋ฐ์ํ ์๋ฌ๋ฅผ ์บ์นํ๊ณ ์ฒ๋ฆฌํ๋ ์ญํ ์ ํฉ๋๋ค.
- ์ด ํจ์๋ ์ปค๋ฐ ๋จ๊ณ์์ ํธ์ถ๋๋ฉฐ, ์ฃผ๋ก ์ฌ์ด๋ ์ดํํธ๊ฐ ๋ฐ์๋๋ ์์ ์ ์ฒ๋ฆฌํฉ๋๋ค.
- ์๋ฌ ๋ฐ์ ์ ํธ์ถ๋๋ฉฐ,
error
๋งค๊ฐ๋ณ์๋ก ๋ฐ์ํ ์๋ฌ ๊ฐ์ฒด,errorInfo
๋งค๊ฐ๋ณ์๋ก ์๋ฌ ์ ๋ณด๋ฅผ ์ ๋ฌ๋ฐ์ต๋๋ค. - ์ผ๋ฐ์ ์ผ๋ก ์ฌ๊ธฐ์์๋ ์๋ฌ๋ฅผ ๋ก๊น ํ๊ฑฐ๋ ์๋น์ค์ ์๋ฌ ์ ๋ณด๋ฅผ ๋ณด๊ณ ํ๋ ๋ก์ง์ ๊ตฌํํฉ๋๋ค.
๐ react-error-boundary
$ npm install react-error-boundary
์์์ ์๊ฐํ ์ฝ๋์ ์ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ์ ๋ถ์ฌ ์ฌ์ฉํ ์ ์์ง๋ง
ํจ์ํ ์ปดํฌ๋ํธ์์ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๊ฐํฉ๋๋ค.
๐ fallback
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary fallback={<p>์๋ฌ ๋ฐ์</p>}>
<Component />
</ErrorBoundary>
๊ธฐ๋ณธ์ ์ธ fallback UI๋ก ๋์ฒดํ๋ ์ฌ์ฉ๋ฐฉ๋ฒ์ ์์์ ์ค๋ช ํ ๊ฒ๊ณผ ๊ฑฐ์ ๋์ผํฉ๋๋ค.
๐ FallbackComponent
export const App = () => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<Component />
</ErrorBoundary>
)
}
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
if(ํด๋ผ์ด์ธํธ ์๋ฌ) return ...
if(์๋ฒ ์๋ฌ) return ...
if(ํ ํฐ ์๋ฌ) return ...
};
FallbackComponent ์์ฑ์ error
์ resetErrorBoundary
๋ ๊ฐ์ ํ๋กํผํฐ๋ฅผ ๋ฐ์ต๋๋ค.
error
๋ ๋ฐ์ํ ์๋ฌ ๊ฐ์ฒด์ด๊ณ , resetErrorBoundary
๋ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ฌ์๋ํ ๋ ์ฌ์ฉํ๋ ํจ์์
๋๋ค.
์ด ์์ฑ์ ์ด์ฉํ ์ปดํฌ๋ํธ๋ฅผ ํตํด ์๋ฌ ๊ฐ์ฒด๋ฅผ ๋ฐ์
์๋ฌ ์ ํ๋ณ๋ก fallback UI๋ฅผ ์ ํ์ ์ผ๋ก ํ์ํ ์ ์๊ฒ ๋๋ฉฐ
์๋ฌ ์ฒ๋ฆฌ์ ๊ดํ ๋ก์ง์ ์ผ๊ด์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ฒ ๋ฉ๋๋ค.
๐ API ํธ์ถ ์ฌ์๋
๋ชจ์ข
์ ์ด์ ๋ก API ํธ์ถ์ ์คํจํ์ ๋ FallbackComponent
์์ฑ์ resetErrorBoundary
๋ฅผ ํตํด
์ฌํธ์ถ ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด๊ณ ์ถ์์ต๋๋ค.
export const App = () => {
return (
<ErrorBoundary onReset={()=>{}} FallbackComponent={ErrorFallback}>
<Component />
</ErrorBoundary>
)
}
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div>
<p>{error.toString()}</p>
<p>์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.</p>
<p>์ฌ์๋ ํด์ฃผ์ธ์.</p>
<button onClick={resetErrorBoundary}>์ฌ์๋</button>
</div>
);
};
์ด ๊ธฐ๋ฅ์ ์ํด์ ErrorBoundary์ onReset
๋ถ๋ถ์ API ํธ์ถ์ ๋ด๋นํ๋ ํจ์๋ฅผ ๋ฃ์ ํ
fallback ์ปดํฌ๋ํธ์์ ์ด๋ฒคํธ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
์ฌ๊ธฐ์ ์ผ๋ฐ์ ์ธ API ํธ์ถ ํจ์๋ฅผ ๋ฃ์ด์ฃผ์ด๋ ๋์ง๋ง
react-query
์์ ๊ธฐ๊ฐ ๋งํ ์ปดํฌ๋ํธ๋ฅผ ์ ๊ณตํ๊ณ ์์์ต๋๋ค.
๐ QueryErrorResetBoundary
<QueryErrorResetBoundary>
{({ reset }) =>
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
{children}
</ErrorBoundary>
)
}
</QueryErrorResetBoundary>
// or
const { reset } = useQueryErrorResetBoundary();
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
{children}
</ErrorBoundary>
react-query ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ด๋ จ๋ ์ปดํฌ๋ํธ์ด๋ฉฐ, ์๋ฌ ์ฒ๋ฆฌ์ ๊ด๋ จ๋ ์ญํ ์ ํฉ๋๋ค.
์ด ์ปดํฌ๋ํธ๋ react-query์ Query ์ปดํฌ๋ํธ์ ํจ๊ป ์ฌ์ฉ๋์ด
ํ์ ์ฟผ๋ฆฌ์์ ๋ฐ์ํ ์๋ฌ๋ฅผ ์์์ ์ฌ์ค์ ํ๊ณ ๊ด๋ฆฌํ๋๋ฐ ๋์์ ์ค๋๋ค.
query๋ฅผ ์ฌ์ค์ ํ๋ reset ํจ์๋ฅผ ErrorBoundary์ ํจ๊ป ์ฌ์ฉํ๋ค๋ฉด
์ฌ์ฌ์ฉ๊ณผ API ์ฌํธ์ถ์ด ๊ฐ๋ฅํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
๐ Suspense + ErrorBoundary
๋ก๋ฉ ์ํ๋ฅผ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํด ์ฃผ๋ Suspense์
์๋ฌ ์ํ๋ฅผ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํด์ฃผ๋ ErrorBoundary๋
ํน์ ์ํ์ ๋ํ ๋์ฒด UI๋ฅผ ํ์ํด ์ค๋ค๋ ๊ณตํต์ ์ด ์์ต๋๋ค.
๋ํ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๋ ๋น์ทํ๊ธฐ ๋๋ฌธ์ ๋์ ํฉ์ณ (๋ก๋ฉ - ์๋ฌ) ์ํ๋ฅผ
ํตํฉ์ ์ด๊ณ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํด ์ฃผ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์์ ๊ฒ ๊ฐ์์ต๋๋ค.
๋ง๋๋ ๋ฐฉ๋ฒ์ ์ ๋ง ๊ฐ๋จํ๊ฒ๋ ๋ค์๊ณผ ๊ฐ์ด Suspense๋ง ์ถ๊ฐํด ์ฃผ๋ฉด ๋ฉ๋๋ค.
export const SuspenseAndErrorBoundary = ({
children
}: {
children: React.ReactNode;
}) => {
const { reset } = useQueryErrorResetBoundary();
return (
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
<Suspense fallback={<Loading />}>{children}</Suspense>
</ErrorBoundary>
);
};
export const FetchComponent = ({ queryKey }: { queryKey: number }) => {
const { data } = useQuery(["data", queryKey], () => getFetchData(queryKey), {
suspense: true,
useErrorBoundary: true, // suspense์ด true์ผ ๊ฒฝ์ฐ default๋ก true
retry: false,
refetchOnWindowFocus: false
});
return (
<div>
<div>ID : {data.id}</div>
<div>Title : {data.title}</div>
</div>
);
};
export default function App() {
return (
<RootContainer>
{[...Array(10)].map((_, idx) => {
return (
<Card key={idx}>
<SuspenseAndErrorBoundary>
<FetchComponent queryKey={idx} />
</SuspenseAndErrorBoundary>
</Card>
);
})}
</RootContainer>
);
}
๋ชจ๋ ๊ธฐ๋ฅ์ ํตํฉํ ์์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
API ํธ์ถ ์ 1/4 ํ๋ฅ ๋ก ์๋ฌ๋ฅผ ๋ฐ์์ํค๊ณ , ์ด๋ฅผ ์ฌํธ์ถ ํ ์ ์๋๋ก ๋ง๋ค์์ต๋๋ค.
๊ฐ๋ฐ์๋ฒ์์ ๋์ํ๋ ์์ ๊ธฐ ๋๋ฌธ์ ํ์๋๋ error overlay๋ ๋ฌด์ํ๊ณ ์คํํ๋ฉด ๋ฉ๋๋ค.
์ค์ ์ ๋ฌด์์๋ ๋ค์ํ ์กฐ๊ฑด์ ๋์ํด ์ํฉ์ ๋ง๊ฒ
Props๋ฅผ ์ถ๊ฐํด์ ์ฌ์ฉํ๋ฉด ๋๋ฌด๋ ํธ๋ฆฌํ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ ํฉ๋๋ค.
๐ ํ๋ก์ ํธ์ ๋์ ํ๊ธฐ ์ํด์
ErrorBoundary๋ฅผ ์ค์ ํ๋ก์ ํธ์ ๋์ ํ๊ธฐ ์ํด์๋ ๋ ๋ค์ํ๊ณ ์ด๋ ค์ด ๋ฌธ์ ๋ค๊น์ง ํจ๊ป ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
๋ ๊ด์ฌ์ด ์์ผ์๋ค๋ฉด ๋ค์ ๊ธ์ ์ฐธ๊ณ ํด์ฃผ์ธ์.
ErrorBoundary & Suspense, ๊ฑฐ์ ์๋ฒฝํ ์ฌ์ฉ๋ฐฉ๋ฒ ๊ฐ์ด๋
Suspense์ ์ฌ์ฉํด ์ ์ธ์ ์ผ๋ก ๋ก๋ฉ ํ๋ฉด ๊ตฌํํ๊ธฐ
๋๊ธ