04. ⚛️ 리액트 렌더링 프로세스와 Fiber 아키텍처
📋 개요
React 19의 가상 DOM 동작 원리와 Fiber 아키텍처, 그리고 새로운 React Compiler가 가져온 패러다임 변화를 정복합니다.
📌 이 면접 항목의 목표
⏱️ 예상 읽기 시간: 25분 (핵심 요약: 13분)
🗺️ 이 챕터의 흐름
[개념 사전] → [질문 1: 가상 DOM & Fiber] → [질문 2: React 19 컴파일러] → [연관 변형 질문]
🎯 이 챕터를 다 읽으면 할 수 있는 것
- Fiber 아키텍처가 동시성(Concurrency)을 처리하는 내부 메커니즘을 설명합니다.
- React 19 컴파일러가 왜
useMemo를 대체하게 되었는지 기술적으로 분석합니다. - 재조정(Reconciliation) 과정에서의 '동일성(Identity)' 체크가 왜 중요한지 이해합니다.
📚 핵심 개념 사전 (Concept Glossary)
1. 가상 DOM (Virtual DOM)
실제 DOM을 조작하기 전, 메모리상에 존재하는 가벼운 JavaScript 객체 복사본입니다. 바뀐 부분만 계산(Diffing)하여 실제 DOM에 적용하는 'Batch Update'를 통해 성능을 최적화합니다.
2. 파이버 (Fiber)
리액트 16부터 도입된 새로운 조정(Reconciliation) 엔진이자 리액트의 가장 작은 실행 단위입니다. 렌더링 작업을 잘게 쪼개어 중단하고 재개하거나(Interruptible), 우선순위를 부여하여 사용자 반응성을 최대로 끌어올립니다.
3. 리액트 컴파일러 (React Compiler)
리액트 19의 핵심 변화로, 개발자가 직접 작성하던 useMemo, useCallback 등을 컴파일 시점에 자동으로 삽입해 주는 도구입니다. 리액트 룰(Rules of React)을 지킨 코드라면 알아서 최적화해 줍니다.
🗺️ 이 문서의 배경 세계관: '영수네 커뮤니티'
- 🐣 영철 ( 신입 ): "영호 님! '영수네 피드'가 너무 느려서 모든 함수랑 변수에
useMemo,useCallback을 덕지덕지 발라놨더니 코드가 너무 길어졌어요. 원래 리액트는 이렇게 짜는 건가요? 🤢" - 🦁 영호 ( 리드 ): "영철 님, 메모이제이션도 비용입니다. 특히 리액트 19부터는 컴파일러가 그 귀찮은 일을 대신 해주기 시작했죠. 하지만 컴파일러가 왜 그렇게 동작하는지, 가상 DOM의 한계를 파이버가 어떻게 메꾸고 있는지 모르면 결국 최적화의 끝판왕은 될 수 없어요. 자, 파이버 아키텍처부터 다시 봅시다."
Q1. 가상 돔(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)의 근간을 논리적으로 분석합니다."
Q2. 리액트 19 컴파일러 도입으로 useMemo, useCallback과 같은 수동 메모이제이션의 역할은 어떻게 변했나요?
🎯 출제 의도
최신 리액트 생태계의 변화를 정확히 캐치하고 있는지, 컴파일러가 해결하는 '수동 메모이제이션의 페인 포인트'를 이해하고 있는지 확인합니다.
🐣 영철이의 Naive 구현 (Bad Case)
영철이는 습관적으로 모든 것을 useMemo로 감싸서 가독성을 해치고 의존성 배열 관리에 지쳤습니다.
// 🐣 영철: "불안하니까 일단 다 useMemo로 감쌀게요!"
const optimizedList = useMemo(() => {
return items.filter(item => item.active);
}, [items]); // ⚠️ 의존성 배열 관리 지옥 시작...🦁 영호의 팩폭 조언
"영철 님, 리액트 19의 컴파일러는 이 코드를 보고 '아, 이건 불변성이 유지되는 연산이네?'라고 스스로 판단해 자동으로 캐싱 코드를 심어줍니다. 이제 사람이useMemo를 일일이 적는 건 '나 일 열심히 해요'라고 생색내는 것밖에 안 됩니다."
🦁 영호의 아키텍처 가이드 (Good Case)
영호 리드는 컴파일러 시대의 'Clean React' 코드를 보여줍니다.
// 🦁 영호: "코드는 비즈니스 로직에 집중하세요. 최적화는 컴파일러가 합니다."
// React 19 Compiler 환경
function PostList({ items }) {
// 🪄 개발자는 평범하게 짭니다.
const activeItems = items.filter(i => i.active);
// 컴파일러가 빌드 타임에 이 함수의 '종속성'을 분석하여
// 내부적으로 메모이제이션 로직을 주입합니다.
return (
<ul>
{activeItems.map(item => <Item key={item.id} {...item} />)}
</ul>
);
}📊 레벨별 답변 가이드 (Self-Check)
- Level 1 (Junior): "리액트 19가 나오면서
useMemo를 덜 써도 됩니다. 무거운 계산은 이제 리액트가 알아서 최적화해 줍니다." - Level 2 (Senior): "React Compiler가 코드의 전체적인 의존성 그래프를 정적 분석하여, 렌더링 결과가 변하지 않는 구간을 알아서 캐싱함을 설명합니다. 이를 통해 'Forget' 접근법(최적화를 까먹어도 괜찮음)이 가능해졌음을 언급합니다."
- Level 3 (Specialist): "컴파일러 시대로 넘어가면서 '참조 동일성(Referential Identity)'의 중요성이 강조됨을 설명합니다. 컴파일러가 코드를 최적화할 수 있도록 규칙(Hooks 규칙, 불변성 등)을 철저히 지켜야 하며, 수동 메모이제이션이 여전히 필요한 예외 케이스(라이브러리 외부 API 연동 등)를 제시합니다."
🔗 연관 변형 질문 (Related Variations)
- Q28. 리액트 19 액션(Actions) API의 장점?
- 💡 핵심:
useTransition을 활용해 로딩/에러 상태를 자동 관리함. 비동기 작업을 '상태 변경'과 일치시킴.
- 💡 핵심:
- Q30. 동시성(Concurrency) 렌더링이란?
- 💡 핵심: 여러 버전의 UI를 메모리에 동시에 준비해 두었다가, 가장 높은 우선순위의 작업부터 화면에 반영하는 기술.
- Q31. 합성 이벤트(Synthetic Event) 시스템의 이유?
- 💡 핵심: 브라우저 간 호환성을 보장하고, 이벤트 위임(Event Delegation)을 통해 메모리 오버헤드를 줄이며 리액트 전역에서 이벤트를 일관되게 관리.
- Q36. 서버 컴포넌트(RSC)에서 함수를 Prop으로 못 넘기는 이유?
- 💡 핵심: RSC는 직렬화(Serialization)된 JSON 형태로 클라이언트에 전달되는데, 자바스크립트 함수는 직렬화가 불가능함.
🐣 영철이의 복기 일기
오늘 리액트 19 컴파일러 소식을 듣고 정말 해방감을 느꼈다. 그동안 useMemo 하나 넣을 때마다 "이게 진짜 비용을 아껴주는 걸까?"라며 고민했던 시간들이 주마등처럼 스쳐 지나간다. 하지만 영호 님 말씀대로, 도구가 좋아질수록 원리를 더 잘 알아야 한다. 파이버라는 근본 아키텍처가 있었기에 이런 마법 같은 컴파일러도 가능한 거니까!
💡 "최신 도구는 게으름의 면죄부가 아니라, 더 중요한 설계에 집중하라는 응원이다."
내일은 리액트의 근육, 'Hooks와 컴포넌트 설계 패턴'을 배워보자. 영철아, 오늘 고생 많았다! 🍻✨