<html>
, <head>
, <body>
와 같은 HTML 문서 태그들을 JavaScript와 같은 스크립팅 언어가 접근하고 조작할 수 있도록 객체(object)로 만든 것한 마디로, DOM은 브라우저가 트리 구조로 만든 객체 모델이라고 할 수 있다.
이렇게 DOM 객체가 트리 구조로 이루어져 있기 때문에, JavaScript가 쉽게 DOM 객체에 접근하고 조작할 수 있는 것이다.
이때, 만약 DOM을 조작하는 정도가 잦아지면 어떻게 될까?
DOM 렌더링은 브라우저의 구동 능력에 의존하기 때문에, 당연히 DOM 조작 속도가 느려진다.
즉, 성능에 영향을 미치게 된다.
이에 따라 DOM 조작을 더 효율적으로 할 수 있게, 최적화하기 위해 등장한 개념이 바로 Virtual DOM이다.
: 가상 DOM 객체로, 실제 DOM과 같은 내용을 담고 있는 복사본
class
, textContent
등)createElement
, getElementById
등)React는 실제 DOM 객체에 접근하여 조작하는 대신, 가상의 DOM 객체를 이용해 변화 전과 변화 후를 비교하고 바뀐 부분을 적용한다.
즉, 전체 실제 DOM을 바꾸지 않고도, 필요한 부분만 업데이트할 수 있다.
Virtual DOM은 추상화된 JavaScript 객체 형태를 가지고 있다.
➡️ 실제 DOM 리렌더링에 비해서 매우 효율적이다.
<ul id="items">
<li>첫 번째 아이템</li>
<li>두 번째 아이템</li>
</ul>
⬇️
const virtualDom = {
tagName: "ul",
attributes: { id: "items" },
children: [
{
tagName: "li",
textContent: "첫 번째 아이템",
},
{
tagName: "li",
textContent: "두 번째 아이템",
},
],
};
: 이전 Virtual DOM 트리와 새로운 Virtual DOM 트리를 비교하여 변경된 부분을 탐색하는 알고리즘
같은 레벨에 있는 노드를 파악한 뒤 다음 자식 세대의 노드를 순차적으로 파악해나간다.
(너비 우선 탐색(BFS)의 일종)
<div>
<Counter />
</div>
⬇️
<span>
<Counter />
</span>
// 부모 태그가 div에서 span으로 바뀌었다.
만약 엘리먼트의 태그나 컴포넌트가 변경된 경우, React는 기존의 트리를 버리고 완전히 새로운 트리를 구축한다.
<div className="before" />
⬇️
<div className="after" />
// 태그는 그대로, className만 바뀌었다.
React는 최대한 렌더링을 적게 하는 방향으로 최소한의 변경 사항만 업데이트한다.
같은 타입의 두 React DOM 엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성만 갱신한다.
/* CSS */
.light {
color: 'red';
fontweight: 'bold';
}
⬇️
.dark {
color: 'green';
fontweight: 'bold';
}
color
속성만 변경된 것을 인식하고, color
속성만 수정하고 fontweight
및 다른 요소는 수정하지 않는다.DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 두 리스트를 순차적으로 순회하고 차이점이 있으면 변경을 생성한다.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li> // 요소를 순차적으로
<li>second</li> // 순회하다가
<li>third</li> // 차이점을 발견하면 변경을 생성한다.
</ul>
React는 두 트리에서,
1. <li>first</li>
가 일치하는 것을 확인한다.
2. <li>second</li>
가 일치하는 것을 확인한다.
3. 마지막으로 <li>third</li>
를 트리에 추가한다.
이렇게 React는 자식 노드를 비교할 때 위에서 아래로 순차적으로 비교하기 때문에, 이러한 동작 방식에 대해 고민하지 않고 리스트의 처음에 엘리먼트를 추가하는 경우, 위의 코드보다 훨씬 더 나쁜 성능을 내게 된다.
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li> // 맨 앞에 추가
<li>Duke</li>
<li>Villanova</li>
</ul>
<li>Duke</li>
와 <li>Connecticut</li>
로 자식 노드가 서로 다르다고 인지하게 된 React는 리스트 전체가 바뀌었다고 받아들인다.<li>Duke</li>
와 <li>Villanova</li>
는 그대로 유지시켜도 된다는 것을 깨닫지 못하고 전부 버리고 새롭게 렌더링해버린다.이는 매우 비효율적인 동작 방식이다.
이러한 문제를 해결하기 위해 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>
key
속성을 통해 2014
라는 자식 엘리먼트가 새로 생겼고, 2015
, 2016
키를 가진 엘리먼트는 그저 위치만 이동했다는 걸 알게 된다.key
속성에는 보통 데이터 베이스 상의 유니크한 값(e.g. id
)을 부여한다.key
는 전역적으로 유일할 필요는 없고, 형제 엘리먼트 사이에서만 유일하면 된다.