토스 · SLASH 21 2021
프론트엔드 웹 서비스에서 우아하게 비동기 처리하기
React Suspense와 ErrorBoundary를 활용한 선언적 비동기 UI 패턴 (발표: 박서진)
요약
핵심 토픽
학습 포인트
1. React Suspense의 Promise throw 메커니즘
Suspense는 하위 컴포넌트가 Promise를 throw하면 이를 감지해 fallback을 렌더링하고, Promise가 resolve되면 해당 컴포넌트를 다시 렌더링합니다. 핵심 동작: 컴포넌트가 `throw promise` → React가 가장 가까운 Suspense 경계에서 catch → fallback 표시 → Promise 완료 시 컴포넌트 재시도. 중요한 오해: 이 메커니즘은 React 자체에서 직접 쓰는 것이 아니라, React Query·SWR·Relay 같은 데이터 라이브러리가 구현해 노출하는 방식으로 사용됩니다.
핵심 용어
2. ErrorBoundary 클래스 컴포넌트 패턴
ErrorBoundary는 하위 트리에서 발생하는 에러를 `getDerivedStateFromError`로 감지해 `hasError` 상태를 true로 만들고, `componentDidCatch`로 에러 로깅을 처리합니다. 함수형 컴포넌트로는 구현할 수 없어 클래스 컴포넌트가 필수입니다. Suspense와 중첩 배치하면 `<ErrorBoundary><Suspense fallback={<Spinner/>}><DataComp/></Suspense></ErrorBoundary>` 패턴으로 에러·로딩·성공 세 상태를 각각 분리할 수 있습니다. react-error-boundary 라이브러리를 쓰면 클래스 컴포넌트 없이도 동일한 기능을 사용할 수 있습니다.
핵심 용어
3. 상태 조합 폭발과 선언적 비동기의 장점
API 호출이 n개일 때 명령형(useEffect + isLoading + isError)은 각 3가지 상태이므로 3^n 가지 조합이 생깁니다. 예: API 3개면 27가지 상태 조합을 관리해야 합니다. Suspense + ErrorBoundary는 컴포넌트가 성공 케이스만 다루므로 조합 폭발이 없어집니다. 실제 코드에서 `if (isLoading) return <Spinner/>; if (isError) return <ErrorMsg/>; return <Content/>` 같은 보일러플레이트가 사라지고, 컴포넌트 내부는 데이터가 항상 있다고 가정한 코드만 남게 됩니다.
핵심 용어
4. 대수적 효과(Algebraic Effects)와 React Hooks 철학
대수적 효과는 '함수가 특정 동작을 요청하고, 실제 처리는 호출 컨텍스트(상위)가 담당하는' 프로그래밍 패턴입니다. React Hooks의 설계 철학이 이와 동일합니다: `useState`는 '상태를 달라'고 선언하고 React가 처리하며, `Suspense`는 '이 데이터가 필요해'라고 선언하고 상위 Suspense가 처리합니다. 의존성 주입·제어의 역전(IoC)과 같은 개념으로, 컴포넌트는 구체적인 비동기 처리 방식을 몰라도 되므로 테스트 용이성과 관심사 분리가 향상됩니다.
핵심 용어
면접 질문
Q1. React Suspense의 내부 동작 원리를 설명해 주세요.
힌트
[감점 답변] 정의만 반복하거나 "React Suspense의 내부 동작 원리를 설명해 주세요."에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] Promise throw 메커니즘을 중심으로 설명하세요. '컴포넌트가 Promise를 throw → 가장 가까운 Suspense가 catch → fallback 렌더링 → Promise resolve 시 컴포넌트 재시도' 흐름을 순서대로 설명하면 좋습니다. 직접 throw하는 것이 아니라 React Query 같은 라이브러리가 이 메커니즘을 구현한다는 점도 언급하면 실무 이해도를 어필할 수 있습니다.
Q2. useEffect로 API를 호출하는 방식과 Suspense를 사용하는 방식의 차이점은 무엇인가요?
힌트
[감점 답변] 정의만 반복하거나 "useEffect로 API를 호출하는 방식과 Suspense를 사용하는 방식의 차이점은 무엇인가요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] useEffect 방식은 isLoading/isError/data 세 상태를 컴포넌트 내부에서 관리하는 명령형 코드임을 설명하세요. Suspense 방식은 컴포넌트가 성공 케이스만 담당하는 선언적 코드라는 대비를 보여주세요. 'API 3개면 3^3=27가지 상태 조합'이라는 구체적 수치를 언급하면 설득력이 높아집니다.
Q3. ErrorBoundary를 직접 구현하는 방법을 설명해 주세요.
힌트
[감점 답변] 정의만 반복하거나 "ErrorBoundary를 직접 구현하는 방법을 설명해 주세요."에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] 클래스 컴포넌트로만 구현 가능한 이유(getDerivedStateFromError는 정적 라이프사이클 메서드)를 먼저 언급하고, getDerivedStateFromError → hasError 상태 변경 → render에서 fallback 분기 → componentDidCatch로 에러 로깅 흐름을 설명하세요. react-error-boundary 라이브러리 활용도 언급하면 실무 경험을 어필할 수 있습니다.
Q4. Suspense와 함께 React Query를 사용할 때 주의해야 할 점은 무엇인가요?
힌트
[감점 답변] 정의만 반복하거나 "Suspense와 함께 React Query를 사용할 때 주의해야 할 점은 무엇인가요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] suspense: true 옵션을 활성화하면 React Query가 내부적으로 Promise throw 메커니즘을 사용한다고 설명하세요. 이때 isLoading/isError 상태 대신 Suspense/ErrorBoundary로 처리해야 하며, ErrorBoundary가 없으면 에러가 화면 전체를 깨뜨릴 수 있다는 점을 강조하면 됩니다.
Q5. Suspense를 여러 개 중첩할 때 어떤 전략으로 배치하는 게 좋을까요?
힌트
[감점 답변] 정의만 반복하거나 "Suspense를 여러 개 중첩할 때 어떤 전략으로 배치하는 게 좋을까요?"에 대해 장단점 없이 단편적으로 답하면 감점 포인트입니다. 면접관은 실무 적용 경험이 부족하다고 판단합니다. [좋은 답변] 큰 Suspense 하나로 전체를 감싸면 일부 컴포넌트 로딩 때 전체가 로딩 UI로 바뀌는 문제를 설명하세요. 섹션별로 Suspense를 배치해 부분 로딩(Progressive Loading)을 구현하는 방법, 그리고 워터폴을 방지하기 위해 데이터 페칭을 최상위에서 병렬로 시작하는 Render-as-you-fetch 패턴을 설명하면 좋은 답변이 됩니다.
선행 학습
- React 컴포넌트 라이프사이클과 렌더링 원리
- Promise와 async/await
- React hooks(useState, useEffect, useRef)
핵심 타임스탬프
학습 방법
1단계: useEffect로 API 2개를 병렬 호출하는 컴포넌트를 직접 작성해 isLoading/isError 상태 조합이 얼마나 복잡한지 체감하세요. 2단계: React Query의 suspense 옵션을 활성화하고 동일한 로직을 Suspense + ErrorBoundary로 리팩토링하며 코드량과 가독성 차이를 비교하세요. 3단계: 중첩 Suspense로 페이지의 특정 섹션만 로딩/에러 처리하는 부분 UI 패턴을 구현해보면 실무 활용 능력을 높일 수 있습니다. 4단계: 동료에게 "React Suspense"의 핵심을 5분 안에 설명해보세요. 막히는 부분이 아직 구조적으로 이해되지 않은 지점입니다.