Suspense
를 데이터 로딩의 핵심 도구로 재정의한 것은 위대한 첫걸음이었다. 하지만 Suspense
가 마법처럼 동작하기 위해서는, 그 배후에서 모든 것을 가능하게 해주는 더 근본적인 엔진이 필요했다. 그 엔진의 이름은 바로 ‘동시성(Concurrency)’이었다.
과거의 React는 ‘블로킹(Blocking)’ 렌더링 방식으로 동작했다. 렌더링이 한번 시작되면, 그 작업이 끝날 때까지 React는 다른 어떤 일도 할 수 없었다. 마치 외길 터널에 들어선 자동차처럼, 중간에 멈추거나 다른 차에게 길을 양보하는 것이 불가능했다. 만약 거대한 컴포넌트 트리를 렌더링하는 데 100밀리초가 걸린다면, 그 100밀리초 동안 사용자의 클릭이나 키보드 입력은 모두 무시되었다. 이것이 바로 UI 버벅임, 즉 ‘프레임 드롭(Frame Drop)’의 주된 원인이었다.
세바스찬 마크바게는 이 문제를 해결하기 위해 수년간 동시성 렌더러 개발에 매달려왔다. React 18에서 실험적으로 도입되었지만, 그 진정한 힘은 서버 컴포넌트와 Suspense
가 결합된 React 19 시대에 비로소 드러날 참이었다.
“동시성이란, React가 여러 상태 업데이트를 동시에 처리할 수 있는 능력을 의미합니다.”
세바스찬은 팀원들에게 동시성의 핵심 개념을 다시 한번 설명했다.
“더 중요한 것은, 렌더링을 ‘중단’하고 ‘재개’할 수 있다는 점입니다. 외길 터널이 아니라, 차선을 변경하며 비상 차량에게 길을 터줄 수 있는 4차선 고속도로가 되는 거죠.”
이것이 어떻게 Suspense
와 연결되는가?
Suspense
로 감싸인 컴포넌트가 데이터 로딩을 위해 렌더링을 ‘일시 중단’시켰다고 가정해보자.
- 블로킹 렌더러 하에서는: React는 그저 데이터가 올 때까지 멍하니 기다려야 한다. 다른 어떤 작업도 할 수 없다.
- 동시성 렌더러 하에서는: React는 데이터 로딩을 기다리는 동안, 그 렌더링 작업을 잠시 옆으로 치워둔다. 그리고 그 사이에 더 긴급한 작업, 예를 들어 사용자의 키보드 입력 같은 것이 들어오면 그 작업을 먼저 처리한다. 사용자는 자신의 입력이 즉각적으로 화면에 반영되는 것을 보며, 앱이 멈추지 않았다고 느끼게 된다.
동시성은 React에게 ‘우선순위’를 판단하는 지능을 부여했다.
모든 상태 업데이트는 더 이상 평등하지 않았다. 타이핑처럼 즉각적인 피드백이 중요한 업데이트는 ‘긴급(Urgent)’으로, 화면 전환처럼 약간의 지연이 허용되는 업데이트는 ‘전환(Transition)’으로 분류될 수 있었다.
“Suspense
가 데이터 로딩이라는 ‘기다림’을 선언적으로 표현하는 방법이라면, 동시성은 그 기다리는 시간 동안에도 앱의 생명력을 유지시켜주는 심장 박동과도 같습니다.” 로렌 탄이 덧붙였다.
이 두 가지가 결합되자, 놀라운 사용자 경험이 가능해졌다.
사용자가 페이지를 전환하는 버튼을 클릭한다. 다음 페이지는 무거운 데이터를 로드해야 해서 Suspense
에 의해 렌더링이 잠시 중단된다. 하지만 동시성 덕분에 앱은 멈추지 않는다. 로딩 스피너는 부드럽게 돌아가고, 사용자가 혹시 다른 버튼을 누르더라도 즉각 반응한다. 데이터가 모두 준비되면, React는 중단했던 페이지 전환 렌더링을 매끄럽게 재개한다.
더 이상 개발자는 복잡한 setTimeout
이나 requestAnimationFrame
을 써가며 렌더링 시점을 조율할 필요가 없었다. 그저 React에게 ‘이 업데이트는 덜 중요하다’고 알려주기만 하면, 동시성 렌더러가 모든 것을 알아서 처리해 주었다.
동시성은 눈에 보이지 않는 무기였다. 하지만 이 강력한 무기가 있었기에, Suspense
는 단순한 로딩 상태 관리 도구를 넘어, 끊김 없는 사용자 경험을 창조하는 React 19의 핵심 철학으로 거듭날 수 있었다.