React 동시성 렌더링: Fiber가 가능하게 한 혁신
들어가며
대규모 리스트를 렌더링하다가 입력 필드가 먹통이 되던 경험, 애니메이션이 뚝뚝 끊기던 순간들이 있지 않으신가요? 이 문제의 근본 원인은 JavaScript의 단일 스레드 특성과 React의 동기적 렌더링이었습니다.
문제의 본질 - 모든 작업이 평등했던 시대
기존 Stack Reconciler의 한계
React의 초기 아키텍처는 콜 스택을 기반으로 작동했습니다. 이를 비유하자면, 엘리베이터를 타고 건물의 모든 층을 순서대로 방문해야 하는 것과 같았죠. 중간에 멈출 수도, 긴급한 용무로 다른 층으로 먼저 갈 수도 없었습니다.

왜 문제였나?
브라우저는 60fps를 유지하려면 한 프레임당 16.67ms 안에 모든 작업을 마쳐야 합니다:

그런데 React가 50ms짜리 업데이트를 실행하면? 3개의 프레임이 날아가고, 사용자는 “버벅거림”을 체감하게 됩니다.
실제 시나리오로 이해하기
검색 자동완성을 생각해보세요:

- 사용자가 “R”을 입력 → 1000개 결과 렌더링 시작
- 사용자가 “Re”를 입력 → 하지만 이전 렌더링이 끝날 때까지 대기
- 사용자가 “React”까지 입력 → 여전히 “R”의 결과를 그리는 중…
결과: 타이핑은 멈춰있고, 이미 필요 없는 “R”의 결과를 열심히 그리고 있는 React
Fiber - 작업을 “중단 가능”하게 만들다
핵심 아이디어: 재귀에서 반복으로
Fiber의 혁신은 재귀적 처리를 반복적 처리로 바꾼 것입니다.
기존 방식 (Stack): 함수 호출 스택
render(App)
└─ render(Header)
└─ render(Logo)
└─ render(Content) // Logo가 끝날 때까지 대기Fiber 방식: 연결 리스트
App → Header → Logo
↓ ↓
Content ← ← ← ← ←이제 Logo를 그리다가도 멈추고, 더 급한 일을 처리한 후 돌아올 수 있습니다!
Fiber 노드의 구조: 작업의 최소 단위
각 Fiber 노드는 하나의 “작업 패킷”입니다:

{
// 무엇을?
type: 'button',
props: { onClick: handleClick },
// 언제까지?
expirationTime: 성능.now() + 500, // 우선순위
// 어떤 작업을?
effectTag: 'UPDATE', // 또는 'PLACEMENT', 'DELETION'
// 다음은 어디로?
return: 부모Fiber,
child: 첫째자식Fiber,
sibling: 다음형제Fiber
}우선순위 시스템 - 모든 업데이트가 평등하지 않다
우선순위의 계층 구조

React는 이제 작업을 분류합니다:
- 즉시 (Immediate): 사용자 입력 - 타이핑, 클릭
- 사용자 차단 (User-blocking): 호버 효과, 드래그
- 일반 (Normal): 데이터 페칭 결과
- 낮음 (Low): 애널리틱스 로깅
- 유휴 (Idle): 화면 밖 콘텐츠 프리렌더링
실제 작동 방식
타임라인으로 보는 동시성:
시간 →
[사용자 타이핑] [무거운 리스트 렌더링 시작]
↓ ↓
[입력 즉시 반영] [리스트 렌더링 일시정지]
↓
[다음 프레임에서 계속]Time Slicing - 작업을 잘게 나누기

브라우저와의 협업
Fiber는 requestIdleCallback의 개념을 차용했습니다:
한 프레임 (16ms)의 구성:
┌─────┬──────┬──────┬──────┬────┐
│입력 │React │스타일│레이아웃│페인트│
│처리 │ 5ms │계산 │ │ │
└─────┴──────┴──────┴──────┴────┘
↑
"5ms만 쓰고 양보"중단과 재개의 메커니즘
- 작업 시작: 우선순위 큐에서 작업 선택
- 시간 체크: 5ms마다 “더 급한 일 있나?” 확인
- 양보 결정: 급한 일 있으면 현재 진행상황 저장
- 재개: 나중에 저장된 지점부터 계속
실무에서의 동시성 기능들
useTransition - 상태 업데이트의 우선순위 조절
언제 쓰나요?
- 무거운 계산이 UI 반응성을 해치는 경우
- 사용자 입력과 결과 표시를 분리하고 싶을 때
작동 원리:
startTransition(() => {
// 이 안의 setState는 "낮은 우선순위"로 표시됨
setExpensiveState(newValue);
});useDeferredValue - 값의 지연된 버전
언제 쓰나요?
- props로 받은 값에 대한 무거운 계산
- 디바운싱과 유사하지만 더 스마트한 처리
작동 원리:
입력값이 빠르게 변할 때, UI는 최신값을 표시하지만 무거운 계산은 이전 값으로 수행될 수 있습니다.
// useDeferredValue: 값을 지연시킬 때
const SearchResults = ({ query }) => {
const deferredQuery = useDeferredValue(query);
// deferredQuery를 사용한 무거운 계산
};
// useTransition: 상태 업데이트를 지연시킬 때
const SearchInput = () => {
const [isPending, startTransition] = useTransition();
const handleSearch = (value) => {
startTransition(() => {
setSearchResults(computeResults(value));
});
};
};Suspense - 비동기의 선언적 처리
혁신적인 점:
- 로딩 상태를 컴포넌트 경계에서 관리
- 여러 비동기 작업의 조율이 자동화
Suspense가 가능한 이유:
Fiber는 컴포넌트 렌더링을 “일시정지”하고 나중에 “재개”할 수 있기 때문!
동시성이 가져온 패러다임 변화

Before: 동기적 사고
“모든 업데이트를 즉시, 완전히 처리해야 한다”
After: 동시적 사고
“급한 것부터, 나머지는 시간 날 때”
실무에서의 영향
- UX 설계의 변화
- 즉각적 피드백과 최종 결과를 분리
- 로딩 상태의 계층적 관리
- 성능 최적화 전략
- 메모이제이션 의존도 감소
- 작업 우선순위로 최적화
- 아키텍처 패턴
- Suspense 경계 설계
- 점진적 향상 전략
실전 가이드라인
동시성 기능을 써야 할 때
✅ 필수적인 경우:
- 검색 자동완성
- 실시간 필터링
- 대용량 데이터 시각화
- 복잡한 폼 유효성 검사
❌ 과도한 사용 주의:
- 단순한 상태 업데이트
- 이미 빠른 작업
- 소규모 리스트
성능 측정과 검증
// React DevTools Profiler로 확인할 점
1. Commit 단계가 여러 개로 나뉘는지
2. 사용자 인터랙션 중 프레임 드롭이 없는지
3. 우선순위가 의도대로 작동하는지미래를 향해
React Server Components와의 시너지
동시성 렌더링은 RSC와 만나 더욱 강력해집니다:
- 서버에서 스트리밍되는 컴포넌트
- 클라이언트 렌더링과의 매끄러운 통합
사용자 경험 중심의 렌더링
Fiber와 동시성 렌더링은 단순한 성능 개선이 아닙니다. 사용자 경험을 최우선으로 하는 렌더링 패러다임의 전환입니다.
핵심 메시지:
- 모든 작업이 똑같이 중요하지 않다
- 중단 가능한 아키텍처가 유연성을 만든다
- 선언적 API로 복잡성을 숨긴다
이제 우리는 “빠른 앱”이 아닌 “반응적인 앱”을 만들 수 있습니다. 사용자가 무언가를 입력할 때, 클릭할 때, 그 순간의 반응이 전체 성능보다 중요하다는 것을 React가 이해하게 된 것입니다.
여러분의 앱에서 사용자가 “버벅거림”을 느끼는 순간이 있다면, 이제 그 해답을 알고 계실 겁니다. 동시성 렌더링으로 사용자에게 마법 같은 경험을 선사하세요! 🚀