04. ⚛️ 리액트 렌더링 프로세스와 Fiber 아키텍처

2026년 4월 30일 수정됨

📋 개요

React의 가상 DOM 동작 원리와 Fiber 아키텍처, 그리고 React Compiler가 바꾸는 최적화 관점을 이해합니다.

📌 이 면접 항목의 목표

⏱️ 예상 읽기 시간: 25분 (핵심 요약: 13분)

🗺️ 이 챕터의 흐름
[개념 사전] → [질문 1: 가상 DOM & Fiber] → [질문 2: React Compiler] → [연관 변형 질문]

🎯 이 챕터를 다 읽으면 할 수 있는 것

  • Fiber 아키텍처가 동시성(Concurrency)을 처리하는 내부 메커니즘을 설명합니다.
  • React Compiler가 수동 메모이제이션 부담을 어떻게 줄이는지 기술적으로 분석합니다.
  • 재조정(Reconciliation) 과정에서의 '동일성(Identity)' 체크가 왜 중요한지 이해합니다.

📚 핵심 개념 사전 (Concept Glossary)

1. 가상 DOM (Virtual DOM)

실제 DOM을 조작하기 전, 메모리상에 존재하는 가벼운 JavaScript 객체 복사본입니다. 바뀐 부분만 계산(Diffing)하여 실제 DOM에 적용하는 'Batch Update'를 통해 성능을 최적화합니다.

2. 파이버 (Fiber)

리액트 16부터 도입된 새로운 조정(Reconciliation) 엔진이자 리액트의 가장 작은 실행 단위입니다. 렌더링 작업을 잘게 쪼개어 중단하고 재개하거나(Interruptible), 우선순위를 부여하여 사용자 반응성을 최대로 끌어올립니다.

3. 리액트 컴파일러 (React Compiler)

React Compiler는 컴포넌트와 훅의 의존성을 정적으로 분석해 불필요한 재계산과 리렌더링을 줄이는 도구입니다. useMemo, useCallback을 단순히 대체한다기보다, 리액트 규칙과 순수성을 지킨 코드에서 자동 최적화가 가능하도록 돕습니다.


🗺️ 이 문서의 배경 세계관: '영수네 커뮤니티'

  • 🐣 영철 (중반): "영호 님! '영수네 피드'가 느려서 모든 함수랑 변수에 useMemo, useCallback을 붙였더니 코드가 너무 길어졌어요. 원래 리액트 최적화는 이렇게 하는 건가요?"
  • 🦁 영호 (리드): "영철 님, 메모이제이션도 비용입니다. React Compiler가 도와주는 영역은 늘고 있지만, 컴파일러가 믿을 수 있는 순수한 렌더링 구조를 만드는 건 여전히 개발자의 몫이에요. 먼저 Fiber가 왜 렌더링을 쪼개야 했는지부터 봅시다."

면접 질문 1. 가상 돔(Virtual DOM)의 한계점은 무엇이며, 리액트 파이버(Fiber) 아키텍처는 이를 어떻게 극복했나요?

🎯 출제 의도

리액트가 대규모 업데이트에서도 왜 부드럽게 동작하는지, 그 근본적인 엔진의 구조적 변화(동기식 -> 증분식 렌더링)를 이해하고 있는지 확인합니다.

🐣 영철이의 Naive 구현 (Bad Case)

영철이는 수천 개의 노드를 한 번에 리트리빙하며 메인 스레드를 장악하는 코드를 짰습니다. (Stack 조정 기반의 구형 리액트 방식)

// 🐣 영철: "그냥 전체를 한 번에 비교해서 한꺼번에 바꾸면 되는 거 아닌가요?"
function naiveRender(bigData) {
    // ⚠️ Stack 기반 조정 (리액트 16 이전)
    // 한 번 시작하면 끝날 때까지 멈출 수 없음 (Blocking)
    // 수천 개의 컴포넌트 호출 스택이 쌓이면 브라우저가 프레임을 놓침
    reconcileRecursively(bigData);
}

🦁 영호의 리뷰 포인트
"영철 님, 기존의 스택 기반 조정은 거대한 트리를 비교할 때 브라우저의 제어권을 놓지 않았어요. 그래서 애니메이션이 뚝뚝 끊겼죠. 파이버는 이걸 '할 일 목차'처럼 쪼개서 언제든 멈췄다 다시 시작할 수 있게 만들었습니다."

🦁 영호의 아키텍처 가이드 (Good Case)

영호 리드가 파이버의 '워크 루프(Work Loop)' 개념을 설명합니다.

// 🦁 영호: "Fiber는 렌더링을 '작은 조각(Work)'으로 쪼개어 스케줄링합니다."
 
// Fiber 아키텍처의 의사 코드
function workLoop(deadline) {
    // 시간이 남거나(Idle), 작업이 남아 있다면 한 단위씩 처리!
    while (nextUnitOfWork && deadline.timeRemaining() > 1) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
 
    // 중요한 입력(Click)이 들어오면 렌더링을 멈추고 브라우저에게 양보함!
    if (!nextUnitOfWork && root.pendingUpdate) {
        commitRoot();
    }
 
    requestIdleCallback(workLoop);
}

📊 레벨별 답변 가이드 (Self-Check)

  • Level 1 (Junior): "가상 DOM은 메모리에서 먼저 비교하고 실제 DOM을 한 번만 수정하는 것입니다. 파이버는 리액트의 성능을 높이기 위한 엔진입니다."
  • Level 2 (Senior): "Stack Reconciler는 재귀적으로 동작하여 렌더링을 중단할 수 없었지만, Fiber는 Linked List 구조를 활용해 증분 렌더링(Incremental Rendering)을 가능하게 했음을 설명합니다. 렌더 단계(비동기)와 커밋 단계(동기)의 차이를 언급합니다."
  • Level 3 (Specialist): "Fiber 아키텍처가 어떻게 '우선순위(Priority Lane)'를 관리하는지 설명합니다. 애니메이션이나 입력은 높은 우선순위로, 데이터 페칭은 낮은 우선순위로 두어 응답성을 최적화하는 동시성 모드(Concurrent Mode)의 근간을 논리적으로 분석합니다."

면접 질문 2. React Compiler 도입으로 useMemo, useCallback과 같은 수동 메모이제이션의 역할은 어떻게 변했나요?

🎯 출제 의도

최신 리액트 생태계의 변화를 정확히 캐치하고 있는지, 컴파일러가 해결하는 '수동 메모이제이션의 페인 포인트'를 이해하고 있는지 확인합니다.

🐣 영철이의 Naive 구현 (Bad Case)

영철이는 습관적으로 모든 것을 useMemo로 감싸서 가독성을 해치고 의존성 배열 관리에 지쳤습니다.

// 🐣 영철: "불안하니까 일단 다 useMemo로 감쌀게요!"
const optimizedList = useMemo(() => {
    return items.filter(item => item.active);
}, [items]); // ⚠️ 의존성 배열을 계속 맞춰야 해 가독성이 떨어짐

🦁 영호의 리뷰 포인트
"영철 님, 컴파일러는 순수한 렌더링 코드와 안정적인 의존성 관계를 분석해 자동 최적화를 시도합니다. 그렇다고 모든 useMemo가 의미 없어지는 건 아니에요. 외부 라이브러리 API와의 참조 동일성 계약처럼 사람이 의도를 명확히 남겨야 하는 곳은 여전히 있습니다."

🦁 영호의 아키텍처 가이드 (Good Case)

영호 리드는 컴파일러 시대의 'Clean React' 코드를 보여줍니다.

// 🦁 영호: "코드는 비즈니스 로직에 집중하세요. 최적화는 컴파일러가 합니다."
 
// React Compiler가 활성화된 환경
function PostList({ items }) {
    // 개발자는 먼저 읽기 쉬운 순수 계산으로 표현한다.
    const activeItems = items.filter(i => i.active);
 
    // 컴파일러는 이 계산이 props인 items에만 의존한다는 사실을 분석해
    // 다시 계산할 필요가 없는 구간을 최적화할 수 있다.
    return (
        <ul>
            {activeItems.map(item => <Item key={item.id} {...item} />)}
        </ul>
    );
}

면접에서는 "컴파일러가 있으니 메모이제이션을 몰라도 된다"가 아니라 컴파일러가 최적화할 수 있는 코드 구조를 만든다고 답해야 합니다. 렌더링 중 부수효과를 만들거나 props를 직접 변경하면 컴파일러가 안전하게 최적화하기 어렵습니다.

📊 레벨별 답변 가이드 (Self-Check)

  • Level 1 (Junior): "React Compiler를 쓰면 반복적인 useMemo 사용을 줄일 수 있습니다. 하지만 렌더링 코드는 순수하게 작성해야 합니다."
  • Level 2 (Senior): "React Compiler가 코드의 전체적인 의존성 그래프를 정적 분석하여, 렌더링 결과가 변하지 않는 구간을 알아서 캐싱함을 설명합니다. 이를 통해 'Forget' 접근법(최적화를 까먹어도 괜찮음)이 가능해졌음을 언급합니다."
  • Level 3 (Specialist): "컴파일러 시대로 넘어가면서 '참조 동일성(Referential Identity)'의 중요성이 강조됨을 설명합니다. 컴파일러가 코드를 최적화할 수 있도록 규칙(Hooks 규칙, 불변성 등)을 철저히 지켜야 하며, 수동 메모이제이션이 여전히 필요한 예외 케이스(라이브러리 외부 API 연동 등)를 제시합니다."

  • Q28. 리액트 19 액션(Actions) API의 장점?
    • 💡 핵심: useTransition을 활용해 로딩/에러 상태를 자동 관리함. 비동기 작업을 '상태 변경'과 일치시킴.
  • Q30. 동시성(Concurrency) 렌더링이란?
    • 💡 핵심: 여러 버전의 UI를 메모리에 동시에 준비해 두었다가, 가장 높은 우선순위의 작업부터 화면에 반영하는 기술.
  • Q31. 합성 이벤트(Synthetic Event) 시스템의 이유?
    • 💡 핵심: 브라우저 간 호환성을 보장하고, 이벤트 위임(Event Delegation)을 통해 메모리 오버헤드를 줄이며 리액트 전역에서 이벤트를 일관되게 관리.
  • Q36. 서버 컴포넌트(RSC)에서 함수를 Prop으로 못 넘기는 이유?
    • 💡 핵심: RSC는 직렬화(Serialization)된 JSON 형태로 클라이언트에 전달되는데, 자바스크립트 함수는 직렬화가 불가능함.

📝 마무리 퀴즈

Q1. Fiber가 기존 스택 기반 렌더링의 한계를 해결한 핵심 방식은 무엇인가요?

정답: 렌더링 작업을 작은 단위로 쪼개고 우선순위를 부여해 중단, 재개, 양보가 가능하게 만든 것

💡 상세 해설:

  • 원리 설명: Fiber는 "가상 DOM을 더 빠르게 만든 기능" 정도가 아니라, 렌더링 작업을 스케줄링 가능한 단위로 바꾼 구조입니다. 그래서 사용자 입력처럼 우선순위가 높은 작업에 브라우저가 반응할 수 있습니다.
  • 오답 피드백: 단순히 diff 알고리즘을 쓰기 때문이라고 답하면 리액트 16 이후의 핵심 변화인 interruptible rendering을 놓칩니다.
  • 📌 핵심 기억법: Fiber는 렌더링을 한 덩어리 일이 아니라 스케줄 가능한 일감으로 바꿉니다.

Q2. React Compiler가 등장해도 렌더링 원리를 알아야 하는 이유는 무엇인가요?

정답: 컴파일러는 규칙을 지킨 코드를 자동 최적화하지만, 동일성/부수효과/렌더링 경계 설계는 여전히 개발자의 책임이기 때문

💡 상세 해설:

  • 원리 설명: 컴파일러는 불필요한 메모이제이션 부담을 줄여주지만, 컴포넌트가 왜 다시 렌더링되는지, key와 identity가 왜 중요한지, side effect가 어디에 있어야 하는지는 여전히 설계 문제입니다.
  • 오답 피드백: "이제 useMemo를 몰라도 된다"는 답변은 도구의 편리함을 원리 이해의 대체물로 착각한 것입니다.
  • 📌 핵심 기억법: 자동 최적화가 생길수록, 최적화가 가능한 코드 구조를 더 잘 알아야 합니다.

Q3. 영철이의 테스트 타임: 영수네 피드가 느릴 때 무작정 useMemo를 넣기 전에 해야 할 일은 무엇인가요?

정답: React Profiler로 어떤 컴포넌트가 왜 다시 렌더링되는지 확인하고, props identity와 렌더링 경계를 먼저 점검한다

💡 상세 해설:

  • 원리 설명: 메모이제이션은 비용이 있는 선택입니다. 실제 병목이 렌더링인지, 계산인지, props 참조 변화인지 측정한 뒤 적용해야 합니다.
  • 오답 피드백: 모든 함수와 값을 감싸는 방식은 코드 복잡도만 늘리고, React Compiler와도 어긋난 습관이 될 수 있습니다.
  • 📌 핵심 기억법: 렌더링 최적화는 먼저 측정하고, 그다음 경계를 좁힙니다.

🐣 영철이의 퇴근 일기

오늘은 리액트를 "state가 바뀌면 다시 그리는 라이브러리" 정도로만 설명하던 단계에서 조금 벗어난 것 같다. Fiber가 렌더링을 쪼개고 양보할 수 있게 만든 구조라는 걸 알고 나니, 왜 대규모 UI에서도 입력 반응성을 지킬 수 있는지 말로 설명할 수 있게 됐다.

💡 "렌더링 최적화는 훅을 많이 쓰는 기술이 아니라, 어떤 일이 언제 중단되어도 되는지 설계하는 기술이다."

다음에는 useMemo를 붙이기 전에 Profiler를 먼저 열어야겠다. 영호 님이 말한 것처럼 컴파일러가 도와주는 시대일수록, 나는 컴파일러가 믿을 수 있는 순수한 컴포넌트와 안정적인 렌더링 경계를 만드는 사람이 되어야 한다.