DOM : Document Object Model의 약자로 문서 객체 모델을 의미한다. 문서의 구조화된 표현을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.
Virtual DOM : 브라우저 DOM을 추상화한 브라우저 메모리상의 자바스크립트 객체다. 리액트 컴포넌트는 Virtual DOM으로 렌더링을 수행하고, Virtual DOM에서는 이전 버전과 새로운 버전을 비교해서 차이가 나는 부분만 브라우저 DOM으로 업데이트를 수행한다.
이러한 방식을 통해 리액트는 선언전 API
가 가능하게 된다. 우리가 리액트에게 원하는 UI의 상태를 알려주면(선언적), 그 상태를 기반으로 DOM은 자동적으로 업데이트가 된다. 예를 들어 우리가 JSX 문법을 기반으로 컴포넌트를 작성하면 렌더링은 React에서 알아서 다 처리해준다.
리액트가 선언적 API를 제공하기 때문에 리액트의 사용자는 렌더링 작업을 함에 있어서 매번 무엇이 바뀌었는지 걱정할 필요가 없다. 하지만 내부에서는 기존의 Virtual DOM과 변경사항이 생긴 Virtual DOM의 비교작업이 이루어진다. 이 과정을 Reconciliation(재조정)
이라고 한다.
이 과정을 거치려면 실제로는 모든 DOM 트리를 순회하면서 탐색 및 변경하는 과정을 거쳐야 한다. 이런 모든 과정을 거친다면 O(n^3)
의 시간복잡도를 가진다. 따라서 리액트는 휴리스틱한 알고리즘을 이용해서 시간복잡도를 O(n)
까지 줄이는 방식을 택한다.
두 루트 엘리먼트의 타입이 다르면 React는 이전의 트리를 버리고 완전히 새로운 트리를 구축한다.
// 바뀌기 이전의 트리
<div>
<Counter />
</div>
// 바뀐 이후의 트리
<span>
<Counter />
</span>
바뀐 이후의 트리의 루트 엘리먼트는 div
에서 span
태그로 바뀌었다. 이 경우에는 새로운 트리를 구축하게 된다.
트리를 버릴 때 이전 DOM 노드들은 모두 삭제된다. 새로운 트리가 만들어 질 때 새로운 DOM 노드들이 DOm에 삽입된다. (이전의 state
들은 모두 사라진다.)
다음과 같이 React DOM 엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신한다.
// 바뀌기 이전의 엘리먼트
<div className="before" title="stuff" />
// 바뀐 이후의 엘리먼트
<div className="after" title="stuff" />
두 엘리먼트를 비교했을 때 className
만 변경되었으므로 동일한 속성은 유지하고 className
만 변경해 준다.
DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경한다.
// 바뀌기 이전의 트리
<ul>
<li>first</li>
<li>second</li>
</ul>
// 바뀐 이후의 트리
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
만약 다음와 같은 트리 구조의 DOM이 바뀐다고 할 때, first
와 second
를 비교하여 일치하는 것을 확인하고 마지막 요소에 third를 추가한다고 하면 간단하게 구현할 수 있다. 하지만 첫 번째 요소에 엘리먼트를 추가할 경우 성능이 좋지 않을 것이다.
// 바뀌기 이전의 트리
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
// 바뀐 이후의 트리
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
React는 Duke
와 Villanova
종속 트리를 그대로 유지하는 대신 모든 자식을 변경한다. 이러한 비효율은 문제가 될 수 있다.
이러한 문제를 해결하기 위해, React는 key
속성을 지원한다. 자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다. 예를 들어, 위 비효율적인 예시에 key
를 추가하여 트리의 변환 작업이 효율적으로 수행되도록 수정할 수 있다.
// 바뀌기 이전의 트리
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
// 바뀐 이후의 트리
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
이제 React는 "2014"
key를 가진 엘리먼트가 새로 추가되었고, "2015"
와 ""2016""
key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다.