input 하나로 10,000개 항목을 필터링할 때 왜 타이핑이 버벅일까? Stack Reconciler의 한계부터 Fiber 아키텍처, Lane 모델, Event-to-Priority 매핑까지 React 우선순위 시스템을 정리합니다.
React는 UI의 부드러운 반응성을 보장하기 위해 작업마다 '우선순위'를 부여하고, 더 중요한 작업이 들어오면 진행 중인 작업을 중단할 수 있도록 설계되어 있습니다.
“사용자 입력은 즉시 처리하고, 화면에 당장 보이지 않는 작업은 나중에 처리한다. React의 우선순위 시스템은 이 단순한 원칙을 구현합니다.
input 하나로 10,000개 항목을 필터링하는 시나리오를 생각해봅니다.
const [query, setQuery] = useState("");
const filtered = items.filter((item) => item.includes(query)); // 10,000개 순회
return (
<>
<input onChange={(e) => setQuery(e.target.value)} />
{filtered.map((item) => (
<Item key={item} value={item} />
))}
</>
);
일반적인 동기 렌더링에서는 키를 누를 때마다 10,000개 컴포넌트가 리렌더됩니다. 브라우저는 이 작업이 끝날 때까지 다음 키 입력을 처리하지 못하고, 사용자는 타이핑이 지연되는 것처럼 느낍니다.
React의 우선순위 시스템은 이 문제를 해결하기 위해 만들어졌습니다.
과거 React의 Stack Reconciler 방식은 한 번 렌더링을 시작하면 끝날 때까지 멈출 수 없는 동기 구조였습니다.
Stack Reconciler (React 15 이하)
렌더링 시작 → ... → 렌더링 완료
↑ 이 구간은 중단 불가
↑ 사용자 클릭, 타이핑 모두 대기
리스트 10,000개 렌더링이 시작되면 브라우저는 그 작업을 마칠 때까지 사용자의 클릭과 타이핑을 처리하지 못합니다. 화면이 멈추는 Jank 현상과 프레임 드롭의 주 원인이었습니다.
React 팀은 렌더링 작업을 잘게 쪼개고, 작업마다 중요도를 부여해 가로채기(Interruption) 가 가능한 구조를 설계했습니다. 이것이 2017년 React 16에 도입된 Fiber 아키텍처입니다.
Fiber (React 16+)
작업 A 시작 → [중단 가능 지점] → 고우선순위 작업 B 등장
→ B 먼저 처리 → A 재개 → 완료
React 18부터는 Lane 모델을 사용해 우선순위를 관리합니다. 32비트 정수를 활용해 각 작업이 어떤 '차선'을 달릴지 결정하는 방식입니다.
React 스케줄러는 다섯 단계의 우선순위를 정의합니다.
| Level | 이름 | 해당 작업 |
|---|---|---|
| 1 | Immediate | 이벤트를 처리하는 동안 발생하는 업데이트 |
| 2 | UserBlocking | 클릭, 입력에 의한 상태 변화 |
| 3 | Normal | 네트워크 응답, 데이터 로딩 |
| 4 | Low | 분석 도구 로깅, 백그라운드 작업 |
| 5 | Idle | 화면에 보이지 않는 컴포넌트 렌더링 |
Immediate(1)가 가장 높은 우선순위입니다. Idle(5)은 시스템이 한가할 때만 처리됩니다.
개발자가 명시적으로 지정하지 않아도, React는 어떤 이벤트가 업데이트를 발생시켰느냐 에 따라 자동으로 우선순위를 결정합니다.
사용자의 명확한 의도가 담긴 개별적 클릭과 입력입니다.
click, mousedown, keydown, touchstart버튼을 눌렀는데 화면이 즉각 반응하지 않으면 사용자는 앱이 고장났다고 느낍니다.
우선순위가 낮은 작업은 계속 밀릴 수 있습니다. React는 이를 방지하기 위해 각 우선순위에 만료 시간을 설정합니다.
NormalPriority 만료 시간: ~5,000ms
0ms ─────────────────────────── 5,000ms
↑
이 구간 동안 계속 뒤로 밀림
↑
5초 초과 시 → Immediate로 격상 → 강제 렌더링
특정 작업이 무한히 밀리는 기아(Starvation) 상태를 방지하기 위한 설계입니다. 아무리 우선순위가 낮더라도 5초가 지나면 즉시 처리됩니다.
| 우선순위 | 만료 시간 |
|---|---|
| Immediate | 즉시 (만료 없음) |
| UserBlocking | 250ms |
| Normal | 5,000ms |
| Low | 10,000ms |
| Idle | 만료 없음 (시스템이 한가할 때만) |
React의 우선순위 시스템은 세 가지 개념이 맞물려 동작합니다.
startTransition이나 useDeferredValue 같은 React 18의 동시성 API는 이 시스템 위에서 동작합니다. 개발자가 직접 "이 업데이트는 낮은 우선순위"라고 React에 알려주는 방법입니다.