React 18 이전의 렌더링은 동기적(synchronous)으로 동작했습니다. 한 번 렌더링이 시작되면 완료될 때까지 브라우저는 다른 어떤 작업도 처리하지 못했습니다. 컴포넌트 트리가 크거나 연산이 무거운 경우, 사용자의 입력에 반응하지 못하는 UI 멈춤(jank) 현상이 발생하는 이유가 바로 여기에 있습니다.
jank: 애니메이션이나 스크롤이 끊기는 현상으로, 프레임이 일정하게 유지되지 못할 때 발생합니다.
Concurrent Rendering은 이 한계를 극복하기 위해 설계된 렌더링 아키텍처입니다. React가 렌더링 작업을 작은 단위로 분할하고, 더 높은 우선순위의 작업이 들어오면 진행 중인 렌더링을 일시 중단(interrupt)한 뒤 우선순위 작업을 먼저 처리할 수 있습니다.
[무거운 렌더링 작업 시작]━━━━━━━━━━━━━━━━━━━━[완료]
↑
이 구간 동안 사용자 입력 무시됨
기존 방식에서는 무거운 상태 업데이트가 시작되면 브라우저의 메인 스레드가 완전히 점유됩니다. 사용자가 버튼을 클릭하거나 텍스트를 입력해도 렌더링이 끝나기 전까지는 반응이 없습니다.
React 18의 Concurrent 모드에서는 렌더링이 중단 가능(interruptible)해집니다.
[낮은 우선순위 렌더링]━━━┓ ┏━━━━━━━━━━━[재개]━━[완료]
┃ ┃
[높은 우선순위 작업 진입]━━[완료]
React 내부의 Scheduler가 각 작업에 우선순위를 부여하고, 우선순위가 높은 작업(예: 사용자 입력 처리)이 낮은 우선순위 작업(예: 목록 렌더링)을 밀어낼 수 있습니다.
Scheduler: React 내부에서 작업의 실행 순서와 우선순위를 관리하는 모듈.
useTransition은 특정 상태 업데이트를 "전환(Transition)"으로 표시하여 낮은 우선순위로 처리하도록 지시합니다. 사용자 입력처럼 즉각적인 반응이 필요한 업데이트는 우선순위가 높게 유지되고, 전환으로 표시된 렌더링은 그 뒤로 밀립니다.
const [isPending, startTransition] = useTransition();
function handleSearch(value) {
setInputValue(value); // 높은 우선순위 — 즉시 반영
startTransition(() => {
setFilteredList(filter(value)); // 낮은 우선순위 — 여유 있을 때 처리
});
}
isPending은 전환이 진행 중인지 나타내는 불리언 값입니다. 이를 활용해 로딩 인디케이터를 표시할 수 있습니다.
useDeferredValue는 특정 값의 업데이트를 지연시킵니다. useTransition이 업데이트를 시작하는 시점을 제어한다면, useDeferredValue는 이미 존재하는 값을 받아 지연된 버전을 반환합니다.
const deferredQuery = useDeferredValue(query);
const results = useMemo(
() => filter(list, deferredQuery),
[list, deferredQuery]
);
deferredQuery는 query보다 느리게 업데이트됩니다. 입력창은 즉시 반응하고, 무거운 필터링 결과는 여유 있을 때 반영됩니다.
React 18에서 Concurrent 기능을 활성화하려면 createRoot API를 사용해야 합니다.
// 기존 (React 17 이하)
ReactDOM.render(<App />, document.getElementById("root"));
// React 18
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
createRoot를 사용하는 것만으로 자동으로 Concurrent 모드가 활성화되지는 않습니다. useTransition, useDeferredValue, Suspense를 함께 사용할 때 Concurrent 기능이 실제로 동작합니다. 기존 코드와의 하위 호환성은 유지되므로, 기존 프로젝트를 React 18로 업그레이드하더라도 useTransition 등을 명시적으로 사용하기 전까지는 동작 방식이 바뀌지 않습니다.
Concurrent Rendering은 렌더링 자체를 빠르게 만드는 것이 아니라, 중요한 작업을 먼저 처리함으로써 사용자가 느끼는 반응성을 개선하는 것을 목표로 합니다.