무엇
createRoot는 브라우저의 DOM 노드 하나를 React 루트로 만들어, 그 안에 컴포넌트를 그리게 해 주는 API다. 서버 렌더를 쓰지 않는 순수 클라이언트 앱의 시작점이다.
첫 인자는 React가 관리할 domNode, 둘째는 선택 옵션이다. 돌려받는 root 객체에는 render와 unmount 두 메서드가 있다.
import { createRoot } from 'react-dom/client'
const root = createRoot(document.getElementById('root')!)
root.render(<App />)
React로만 지은 앱은 보통 createRoot 호출이 하나뿐이다. 옵션으로는 onCaughtError, onUncaughtError, onRecoverableError(에러 콜백)와 useId가 만드는 ID 접두사인 identifierPrefix를 줄 수 있다.
무슨 일이 일어나나
root.render(reactNode)를 처음 부르면 React가 루트 안의 기존 HTML을 모두 지우고 컴포넌트를 그린다. 같은 root에 render를 다시 부르면 새로 그리는 게 아니라, 이전 트리와 맞춰 가며 DOM을 필요한 만큼만 고치고 컴포넌트 state를 보존한다.
렌더는 비동기다. root.render() 다음 줄의 코드가 Effect(useLayoutEffect, useEffect)보다 먼저 실행될 수 있다. 그 순간 동기 동작이 필요하면 flushSync로 감싼다.
root.unmount()는 루트 안 모든 컴포넌트를 걷어내고 React를 그 DOM 노드에서 떼어낸다. 한번 unmount한 root에는 다시 render할 수 없다("Cannot update an unmounted root" 에러). 같은 DOM 노드로 새 root를 만드는 건 가능하다.
사용법
앱 진입 파일에서 한 번 부른다. HTML에 있는 빈 컨테이너를 루트로 삼아 최상위 컴포넌트를 그린다.
import { createRoot } from 'react-dom/client'
import { App } from './App'
const container = document.getElementById('root')
if (!container) throw new Error('root 컨테이너가 없다')
const root = createRoot(container)
root.render(<App />)
한 페이지에 여러 루트를 둘 수도 있다. React가 페이지 일부에만 얹혀 있을 때, 조각마다 루트를 따로 만든다.
실무 예시
여러 루트를 한 페이지에 둘 때는 identifierPrefix로 useId가 만드는 ID가 겹치지 않게 한다. 위젯 형태로 기존 페이지에 React를 심는 상황에서 유용하다.
import { createRoot } from 'react-dom/client'
import { Widget } from './Widget'
// 같은 페이지의 두 위젯 — ID 충돌을 접두사로 막는다
const a = createRoot(document.getElementById('widget-a')!, {
identifierPrefix: 'a-',
})
const b = createRoot(document.getElementById('widget-b')!, {
identifierPrefix: 'b-',
})
a.render(<Widget />)
b.render(<Widget />)
왜 중요한가
클라이언트 렌더 React 앱의 문이다. React 18에서 동시성 기능과 함께 옛 ReactDOM.render를 대체했고, 지금은 이 방식이 기본이다. Next.js처럼 프레임워크를 쓰면 프레임워크가 대신 불러 주므로 직접 볼 일이 적지만, 프레임워크 없이 React를 붙이거나 위젯을 심을 때는 반드시 손대는 진입점이다.
Reference