FEInterview Prep

react · high priority

React Error Boundaries

렌더 중 예외를 격리하는 유일한 공식 메커니즘

intermediate 난이도4시간토스당근네이버카카오배민라인쿠팡
시작 전
이해도
매우 낮음

학습 개요

선행 학습

  • react-fiber
  • react-concurrent

탄생 배경

멘탈 모델

쉬운 설명

복잡한 개념을 실생활 비유로 설명합니다.

건물의 방화벽.

건물 한 층에서 불이 나도 전체 빌딩이 전소되지 않도록 방화벽이 층을 나눈다. Error Boundary 는 React 트리의 방화벽이다 — 한 서브트리가 "불탔어도" (렌더 예외), 부모의 경계가 그 범위에서 불길을 끊고 대신 대피 안내도(fallback UI)를 보여준다. 방화벽 자체에 불이 붙으면 무용지물이듯, Boundary 자신의 렌더에서 throw 하면 효과가 없다.

핵심 개념

Error Boundary 의 공식 정의는 "자식 트리의 렌더 중 예외를 잡는 class 컴포넌트" 다. 여전히 **function 컴포넌트로 구현할 수 없다** — hooks 는 렌더 이후의 예외를 후크할 API 를 제공하지 않기 때문이다. 그래서 대부분의 프로젝트는 react-error-boundary 라이브러리의 ErrorBoundary 래퍼를 재사용한다.

최소 구현 — class 로만 가능tsx
1import { Component, type ErrorInfo, type ReactNode } from 'react';
2
3interface Props { fallback: ReactNode; children: ReactNode }
4interface State { hasError: boolean }
5
6export class ErrorBoundary extends Component<Props, State> {
7 state: State = { hasError: false };
8
9 // ① state 를 유도한다. 순수 함수 — side effect 금지.
10 static getDerivedStateFromError(_error: unknown): State {
11 return { hasError: true };
12 }
13
14 // ② side effect — 로깅/트래킹. error 와 info.componentStack 수집.
15 componentDidCatch(error: Error, info: ErrorInfo) {
16 console.error('[ErrorBoundary]', error, info.componentStack);
17 // Sentry.captureException(error, { extra: info });
18 }
19
20 render() {
21 return this.state.hasError ? this.props.fallback : this.props.children;
22 }
23}

getDerivedStateFromError vs componentDidCatch

getDerivedStateFromError
  • **순수 함수** — side effect 불가
  • 반환값이 새 state 로 머지됨
  • SSR 에서도 호출됨
  • "어떤 UI 로 전환할지" 결정
componentDidCatch
  • **side effect 가능** — 로깅 · 분석 전송
  • 두 번째 인자 info.componentStack 제공
  • SSR 에서는 호출되지 않음 (클라이언트만)
  • "무슨 일이 일어났는지" 기록

function 컴포넌트로 못 만드는 이유

React 팀은 "hooks 로 Error Boundary 를 표현하려면 렌더 함수 외부에서 state 를 갱신하는 경로가 필요한데, 이는 hooks 모델과 호환되지 않는다" 고 밝혔다. 대안으로 **react-error-boundary** 패키지가 class 구현을 한 번만 제공하고, 사용은 hook (useErrorBoundary) 으로 하는 하이브리드 API 를 표준처럼 쓰고 있다.

트레이드오프

이상적인 사용 사례

실무 적용

어떤 상황에서 사용하는가

토스/당근 수준의 앱에서는 한 페이지에 여러 독립 위젯(추천 카드 · 최근 주문 · 공지 배너)이 있다. 한 위젯의 API 가 500 을 뱉어도 다른 위젯은 정상 노출돼야 한다. 동시에 전체 앱이 unmount 되는 최악의 케이스는 방지해야 한다.

어떻게 적용하는가

3-계층 Boundary 를 둔다. (1) Root Boundary: 치명적 fallback. (2) Route Boundary: `app/**/error.tsx` (Next.js) 또는 RouterProvider 에 errorElement. (3) Widget Boundary: 각 카드 래퍼에 `react-error-boundary` 의 `<ErrorBoundary>`. 각 위젯의 `useSuspenseQuery` 실패는 해당 카드의 boundary 만 트리거 → 다른 카드는 유지. `onCaughtError` 루트 핸들러로 Sentry 전송. 재시도는 `resetErrorBoundary` + `queryClient.resetQueries(key)` 를 onReset 에 연결.

흔한 실수와 안티패턴

  • 이벤트 핸들러에서 throw 했는데 fallback 이 안 나와서 "Boundary 가 안 먹는다" 고 오해 — showBoundary 가 필요.
  • Boundary 자체의 fallback JSX 에서 useQuery 같은 suspense 훅을 또 호출 — 이중 suspense/error 흐름으로 깜빡임 버그.
  • onReset 에서 쿼리 캐시만 초기화하고 state 를 그대로 둬서 "리셋했는데 같은 에러" 가 반복.
  • SSR 중 getDerivedStateFromError 가 호출된다는 걸 잊고 fallback 에 window 참조 코드 삽입 → hydration mismatch.
  • 모든 Boundary 에서 Sentry.captureException 을 중복 호출 — 루트 onCaughtError 로 통합하는 게 표준.

흔한 오해

오해

function 컴포넌트 + hook 으로도 Error Boundary 를 만들 수 있다.

교정

현재 공식 API 는 class 전용이다. `react-error-boundary` 라이브러리도 내부적으로는 class 를 사용한다.

왜 중요

Boundary 는 "자식의 렌더 예외를 부모의 상태 전환으로 바꾸는" 메커니즘이라 hook 실행 모델로는 표현하기 어렵다.

오해

onClick 내부의 예외도 Boundary 가 잡아준다.

교정

이벤트 핸들러는 렌더 페이즈 밖이므로 자동 catch 되지 않는다. `try/catch` 로 잡아 `showBoundary(err)` 를 호출해야 fallback 이 뜬다.

왜 중요

React 는 이벤트 디스패치를 위임받은 후 핸들러를 직접 호출할 뿐, 그 내부 throw 는 React 트리 밖이다.

오해

Suspense fallback 이 보이면 Boundary 는 필요 없다.

교정

Suspense 는 Promise 를 위한 fallback, Boundary 는 Error 를 위한 fallback 이다. 네트워크가 정말 실패하면 Promise 가 reject → Error 가 throw 되므로 두 boundary 가 함께 있어야 한다.

왜 중요

둘을 같은 "선언적 상태 전환" 으로 보면 자연스럽다 — 로딩/에러/성공 중 어느 상태를 선택하느냐의 문제.

면접 질문

중급토스네이버카카오

답변 방향 힌트

"렌더/commit 페이즈가 아니면 못 잡는다" 는 원칙에서 역산. 이벤트 · async · SSR · 자기 자신의 4가지를 꼽고 각 우회책을 제시.

반드시 언급할 키워드

  • 이벤트 핸들러 — try/catch + showBoundary
  • 비동기 (setTimeout · Promise) — await 감싸서 try/catch
  • SSR (renderToString) — 서버 try/catch + 서버 로거
  • Boundary 자신의 render — 상위 Boundary 필요

예상 꼬리 질문

  • `componentDidCatch` 는 SSR 에서 왜 호출되지 않나요?
  • `showBoundary` 를 내부적으로 어떻게 구현하나요?
  • App Router 의 `error.tsx` 는 class 컴포넌트인가요?

자기 점검

Error Boundary 가 catch 하는 페이즈는 무엇이고, 못 잡는 경우 4가지를 열거해보세요.

기대 키워드

렌더commit이벤트 핸들러asyncSSRboundary 자신

자주 하는 오해

"모든 JS 에러를 잡는다" 고 생각하는 것.

getDerivedStateFromError 와 componentDidCatch 의 역할 차이를 한 줄로 설명해보세요.

기대 키워드

state 유도side effect순수 함수로깅SSR 호출 여부

자주 하는 오해

"둘 다 같은 걸 한다" 고 답하는 것.

Suspense 와 ErrorBoundary 의 배치 순서와 그 이유를 말해보세요.

기대 키워드

ErrorBoundary 바깥Suspense 안쪽PromiseError깜빡임

React 19 의 onUncaughtError 가 호출되는 상황을 구체적 예로 설명해보세요.

기대 키워드

Boundary 없음루트 unmount빈 화면fatal

학습 자료