[React] Virtual DOM

김재훈·2023년 4월 19일
0

Virtual DOM의 역할

DOM : Document Object Model의 약자로 문서 객체 모델을 의미한다. 문서의 구조화된 표현을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.

Virtual DOM : 브라우저 DOM을 추상화한 브라우저 메모리상의 자바스크립트 객체다. 리액트 컴포넌트는 Virtual DOM으로 렌더링을 수행하고, Virtual DOM에서는 이전 버전과 새로운 버전을 비교해서 차이가 나는 부분만 브라우저 DOM으로 업데이트를 수행한다.

이러한 방식을 통해 리액트는 선언전 API가 가능하게 된다. 우리가 리액트에게 원하는 UI의 상태를 알려주면(선언적), 그 상태를 기반으로 DOM은 자동적으로 업데이트가 된다. 예를 들어 우리가 JSX 문법을 기반으로 컴포넌트를 작성하면 렌더링은 React에서 알아서 다 처리해준다.

Reconciliation(재조정)이란?

리액트가 선언적 API를 제공하기 때문에 리액트의 사용자는 렌더링 작업을 함에 있어서 매번 무엇이 바뀌었는지 걱정할 필요가 없다. 하지만 내부에서는 기존의 Virtual DOM과 변경사항이 생긴 Virtual DOM의 비교작업이 이루어진다. 이 과정을 Reconciliation(재조정)이라고 한다.

이 과정을 거치려면 실제로는 모든 DOM 트리를 순회하면서 탐색 및 변경하는 과정을 거쳐야 한다. 이런 모든 과정을 거친다면 O(n^3)의 시간복잡도를 가진다. 따라서 리액트는 휴리스틱한 알고리즘을 이용해서 시간복잡도를 O(n)까지 줄이는 방식을 택한다.

DOM 엘리먼트의 타입이 다른 경우

두 루트 엘리먼트의 타입이 다르면 React는 이전의 트리를 버리고 완전히 새로운 트리를 구축한다.

// 바뀌기 이전의 트리
<div>
  <Counter />
</div>

// 바뀐 이후의 트리
<span>
  <Counter />
</span>

바뀐 이후의 트리의 루트 엘리먼트는 div에서 span태그로 바뀌었다. 이 경우에는 새로운 트리를 구축하게 된다.

트리를 버릴 때 이전 DOM 노드들은 모두 삭제된다. 새로운 트리가 만들어 질 때 새로운 DOM 노드들이 DOm에 삽입된다. (이전의 state들은 모두 사라진다.)

DOM 엘리먼트의 타입이 같은 경우

다음과 같이 React DOM 엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신한다.

// 바뀌기 이전의 엘리먼트
<div className="before" title="stuff" />
// 바뀐 이후의 엘리먼트
<div className="after" title="stuff" />

두 엘리먼트를 비교했을 때 className만 변경되었으므로 동일한 속성은 유지하고 className만 변경해 준다.

Key

DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경한다.

// 바뀌기 이전의 트리
<ul>
  <li>first</li>
  <li>second</li>
</ul>

// 바뀐 이후의 트리
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

만약 다음와 같은 트리 구조의 DOM이 바뀐다고 할 때, firstsecond를 비교하여 일치하는 것을 확인하고 마지막 요소에 third를 추가한다고 하면 간단하게 구현할 수 있다. 하지만 첫 번째 요소에 엘리먼트를 추가할 경우 성능이 좋지 않을 것이다.

// 바뀌기 이전의 트리
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

// 바뀐 이후의 트리
<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React는 DukeVillanova 종속 트리를 그대로 유지하는 대신 모든 자식을 변경한다. 이러한 비효율은 문제가 될 수 있다.

이러한 문제를 해결하기 위해, 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를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다.

key props를 선정할 때의 유의사항

  • 두 컴포넌트가 교체하는 상황에서는 그 둘을 같은 타입으로 만드는 것이 더 효율적이다.
  • key값을 선정할 때는, 형제 사이에서만 유일하면 되고, 전역에서 유일한 value일 필요는 없다.
  • 배열의 인덱스는 key로 사용하지 않는 것이 좋다. 항목들이 재배열되면, key값이 변경될 수 있기 때문이다.
  • key는 변하지 않고 예상 가능하면서, 유일해야 한다. (Math.random() 같은 값은 key로 쓰면 안된다.)

참고

profile
김재훈

0개의 댓글