가상 DOM이 다시 한번 활약할 때

612025년 10월 15일4

setStaterender를 자동으로 호출한다는 사실은 이제 팀 모두에게 자명해졌다. 하지만 그 과정에서 정확히 어떤 일이 벌어지는지에 대한 의문은 여전히 남아있었다. 특히, 그들이 가장 자랑스럽게 생각하는 무기, ‘가상 DOM’이 이 과정에서 어떤 역할을 하는지 말이다.

맷은 <CommentBox> 컴포넌트의 시나리오를 통해 그 질문을 구체화했다.
“제가 ‘Hello’라고 입력했을 때, state{ commentText: 'Hello' }가 됩니다. 그리고 !를 하나 더 입력해서 setState({ commentText: 'Hello!' })를 호출했다고 가정해 보죠. 그럼 render 함수가 다시 실행될 텐데, 그 다음은요? 가상 DOM은 정확히 무슨 일을 하는 겁니까?”

그의 질문은 리액트의 핵심 엔진이 어떻게 돌아가는지에 대한 것이었다.

조던 워크는 그 과정을 시각적으로 보여주기 위해 화이트보드로 향했다.
“자, setState가 호출되는 순간, 리액트의 비교 엔진이 깨어납니다. 엔진의 손에는 두 장의 설계도, 즉 두 개의 버추얼 DOM 트리가 들려있게 되죠.”

설계도 A: 이전 버추얼 DOM 트리 (메모리에 이미 존재)

  • commentText가 ‘Hello’였을 때의 모습.

설계도 B: 새로운 버추얼 DOM 트리 (render가 방금 새로 생성)

  • commentText가 ‘Hello!’가 된 후의 모습.

“이제 리액트의 Diffing 알고리즘이 이 두 설계도를 비교하기 시작합니다. 마치 틀린 그림 찾기처럼요.”

조던은 비교 과정을 단계별로 설명했다.

  1. 루트 노드 비교:
  • <CommentBox>의 최상위 <div>를 비교한다. 태그 이름도 같고, className도 같다. 변경 없음.
  1. 자식 노드들 비교 (순서대로):
  • 첫 번째 자식, <Avatar>: 이전 트리와 새 트리 모두 <Avatar> 컴포넌트다. 타입이 같다. props로 받은 imageUrl도 같다. 리액트는 이 하위 트리는 더 이상 깊게 비교할 필요가 없다고 판단하고 넘어간다. 매우 효율적이다.

  • 두 번째 자식, <textarea>: 이전 트리와 새 트리 모두 <textarea>다. 타입이 같다. placeholder 속성도 같다. 하지만 value 속성이 ‘Hello’에서 ‘Hello!’로 바뀌었다. 차이점 발견!

    • 알고리즘은 변경 목록에 기록한다: [UPDATE_VALUE, textarea DOM 노드, 'Hello!']
  • 세 번째 자식, <p> (글자 수): 이전 트리와 새 트리 모두 <p>다. 타입이 같다. 하지만 내부 텍스트 자식이 ‘글자 수: 5’에서 ‘글자 수: 6’으로 바뀌었다. 차이점 발견!

    • 알고리즘은 변경 목록에 기록한다: [UPDATE_TEXT, p DOM 노드, '글자 수: 6']
  • 네 번째 자식, <button>: 타입도 같고, props도 같다. 변경 없음.

  1. 최소한의 실제 DOM 조작:
  • 비교 과정이 모두 끝나면, 리액트의 손에는 아주 짧은 작업 지시서만 남는다.
  • 렌더러는 이 지시서에 따라, 실제 DOM에 단 두 가지 작업만 수행한다.
    1. textarea 요소의 value를 ‘Hello!’로 업데이트한다.
    2. <p> 요소의 textContent를 ‘글자 수: 6’으로 업데이트한다.

“이것이 전부입니다.” 조던이 말했다. “우리는 render 함수를 통째로 다시 실행했지만, 실제 DOM에서는 <div>, <Avatar>, <button>은 전혀 건드리지 않았습니다. 오직 변경이 필요한 두 개의 노드만 정확히 수정했을 뿐이죠.”

그제야 팀원들은 가상 DOM의 진정한 위력을 깨달았다.
가상 DOM은 단순히 초기 렌더링을 위한 기술이 아니었다. 그것은 업데이트를 위한 강력한 완충 장치(Buffer)였다. 상태 변화라는 거대한 파도가 밀려올 때, 가상 DOM이 그 파도를 온몸으로 막아서서, 실제 세계에는 아주 잔잔한 물결(최소한의 DOM 변경)만이 닿도록 만들어주는 정교한 댐과도 같았다.

setState로 시작된 변화의 요청이, render의 자동 재호출을 거쳐, 마침내 가상 DOM의 지능적인 비교를 통해 가장 효율적인 실제 화면의 변화로 귀결되는 완벽한 파이프라인. 리액트의 심장부가 마침내 그 온전한 모습을 드러낸 순간이었다.