-
Notifications
You must be signed in to change notification settings - Fork 0
ErrorBoundary 학습 정리
프로젝트 일정 상 기능 개발에 치중하다 보니 에러 처리에 대한 깊은 고민을 해본적이 없다.
기존 에러 처리 방식은 api 호출 시 에러가 발생하면 컴포넌트 내부에서 조건부 렌더링을 통해 그에 맞는 fallback ui를 보여주는 방식이였다.
하지만 위 개발 방식에서 api를 호출하는 모든 컴포넌트에서 Error 처리 시 불편함을 느꼈다. 기존에 에러를 처리하는 방식은 다음과 같았다.
아래 코드는 기존 에러 처리 로직을 설명하기 위한 매우 간단한 코드로 내부의 자세한 로직은 생략한다. 개발 환경은 React 와 Tanstack Query를 사용했다.
function ExampleComponent() {
const { data, error } = useSuspenseQuery({
queryKey: ['abc'],
queryFn: fetchUserData
});
if (error) {
return <div>{error.message} 에러 발생...!</div>;
}
return (
<div>
<h1>{data.name}님의 프로필</h1>
<div>{data.email}</div>
</div>
);
}
프로젝트를 진행하면 할 수록 위 에러 처리 방식의 다음과 같은 문제점을 팀원 모두 느낄 수 있었다
- 중복 코드 발생: API를 호출하는 모든 컴포넌트에서 동일한 에러 처리 로직을 반복적으로 작성해야 했다.
- 일관성 없는 에러 UI: 각 컴포넌트 마다 서로 다른 방식으로 에러를 표시하며, 통일된 에러 컴포넌트가 없어, 사용자 경험이 일관적이지 않았다
- 유지보수의 어려움: 에러 처리 방식을 변경하려면 모든 컴포넌트를 수정해야 했다.
이러한 문제들로 인해 더 나은 에러 처리 방식의 필요성을 느끼게 되었다. 특히 API 호출이 많은 우리 프로젝트에서 각 컴포넌트마다 반복되는 에러 처리 로직은 개발 생산성을 저해하는 주요 요인이었다. 이에 조금 더 중앙화된 에러 처리 방식을 고민하던 중 React의 Error Boundary를 알게 되었고, 이를 프로젝트에 적용하기로 결정했다.
💡Error Boundary는 React16에서 도입된 기능으로, 하위 컴포넌트 트리에서 발생하는 JavaScript 에러를 감지하고, 에러 발생 시 fallback UI를 보여줄 수 있는 React 컴포넌트이다.
클래스 컴포넌트에서만 구현할 수 있으며, getDerivedStateFromError()
나 componentDidCatch()
생명주기 메서드등을 활용하여 구현한다.
내가 구현한 APIErrorBoundary를 살펴보자.
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: React.ComponentType<{ error: Error }>;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ApiErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error: Error): State {
return {
hasError: true,
error,
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
}
render() {
const { hasError, error } = this.state;
const { children, fallback: Fallback } = this.props;
if (hasError && error) {
return <Fallback error={error} />;
}
return children;
}
}
export const DefaultErrorFallback = ({ error }: { error: Error }) => {
return (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<h2 className="text-lg font-semibold text-red-800">
에러가 발생했습니다
</h2>
<p className="mt-2 text-sm text-red-600">{error.message}</p>
</div>
);
};
내가 만든 Error boundary에서 주목할 만한 점은 fallbackUI를 prop으로 받아 각 컴포넌트마다 자체 에러 UI를 유연하게 처리할 수 있도록 구현한 것이다. 이를 통해 상황과 각 페이지에 맞는 적절한 에러 UI를 제공할 수 있다. 이렇게 구현한 ApiErrorBoundary는 다음과 같이 사용할 수 있다.
<ApiErrorBoundary fallback={DefaultErrorFallback}>
<MyComponent />
</ApiErrorBoundary>
// 커스텀 에러 UI 사용
<ApiErrorBoundary
fallback={({ error }) => (
<div>커스텀 에러 메시지: {error.message}</div>
)}
>
<MyComponent />
</ApiErrorBoundary>
Error Boundary를 적용하고 실제 개발 과정에서 체감한 장점들을 다음과 같다
<ApiErrorBoundary fallback={DefaultErrorFallback}>
<MyComponent />
</ApiErrorBoundary>
Error Boundary를 도입하며 가장 크게 체감한 장점은 선언적인 방식으로 에러를 처리할 수 있다는 점이었다. 기존에는 각 컴포넌트에서 조건문을 통해 명령적으로 에러를 처리했지만, Error Boundary를 사용하면 컴포넌트를 감싸는 것만으로 에러 처리가 가능해졌다. 이로 인해 컴포넌트 내부는 성공 상태에만 집중할 수 있게 되었다.
Error Boundary를 통해 에러 처리를 중앙화함으로써 세 가지 큰 이점을 얻을 수 있었다:
1. 일관된 에러 처리
- 모든 API 에러에 대해 동일한 방식으로 대응
- 에러 메시지 형식 통일
- 재시도 로직과 에러 로깅의 일관성 확보
2. 향상된 유지보수성
- 에러 처리 방식 변경 시 Error Boundary 컴포넌트만 수정
- 관심사의 명확한 분리
- 변경 사항의 영향 범위 최소화
- 코드 중복 제거
- 반복적인 에러 처리 코드 제거
- 더욱 깔끔해진 컴포넌트 코드
- 핵심 로직에 집중할 수 있는 환경
Error Boundary의 도입은 단순한 기능 추가를 넘어 전반적인 코드 품질 향상과 개발 생산성 증가로 이어졌다. 앞으로도 React의 다양한 기능들을 활용해 더 나은 코드를 작성하기 위해 계속 고민해볼 예정이다.