Virtual DOM

지은·2022년 11월 28일
0

⚛️ React

목록 보기
13/23

DOM (Document Object Model)

  • 문서 객체 : <html>, <head>, <body>와 같은 HTML 문서 태그들을 JavaScript와 같은 스크립팅 언어가 접근하고 조작할 수 있도록 객체(object)로 만든 것
  • 문서 객체 모델 : 이러한 문서 객체들을 계층화시켜 트리 구조로 표현한 모델

한 마디로, DOM은 브라우저가 트리 구조로 만든 객체 모델이라고 할 수 있다.
이렇게 DOM 객체가 트리 구조로 이루어져 있기 때문에, JavaScript가 쉽게 DOM 객체에 접근하고 조작할 수 있는 것이다.


브라우저의 동작 과정

  1. 렌더링 엔진이 HTML파일을 파싱하여, 🌳 DOM 트리를 생성한다.
  2. CSS 파일을 파싱하여 🌲 CSSOM 트리를 생성한다.
  3. DOM 트리와 CSSOM 트리를 결합해 🎄 렌더 트리를 생성한다.
  4. 렌더 트리를 기반으로 레이아웃 결정
  5. 구성된 레이아웃을 실제 화면에 그린다.
    ⬇️
    이후 JavaScript가 DOM을 조작해 DOM에 변화가 생길 때마다 렌더 트리가 재생성되고, 레이아웃(Reflow)과 페인팅(Repaint) 과정을 다시 거친다. (재연산..🕑)

이때, 만약 DOM을 조작하는 정도가 잦아지면 어떻게 될까?
DOM 렌더링은 브라우저의 구동 능력에 의존하기 때문에, 당연히 DOM 조작 속도가 느려진다.
즉, 성능에 영향을 미치게 된다.
이에 따라 DOM 조작을 더 효율적으로 할 수 있게, 최적화하기 위해 등장한 개념이 바로 Virtual DOM이다.


Virtual DOM

: 가상 DOM 객체로, 실제 DOM과 같은 내용을 담고 있는 복사본

  • 실제 DOM 객체와 같은 속성들을 가지고 있다. (class, textContent 등)
  • 하지만 실제 DOM이 가지고 있는 (DOM을 조작할 수 있는) API들은 가지고 있지 않다. (createElement, getElementById 등)

React는 실제 DOM 객체에 접근하여 조작하는 대신, 가상의 DOM 객체를 이용해 변화 전과 변화 후를 비교하고 바뀐 부분을 적용한다.

Virtual DOM 동작 과정

  1. 데이터가 변경되면 새로운 Virtual DOM 트리가 만들어진다.
  2. 이전 Virtual DOM과 새로운 Virtual DOM을의 차이를 비교한다.
    + Virtual DOM은 실제 DOM에 변경을 수행할 수 있는 최상의 방법을 계산한다.
  3. 바뀐 부분만 실제 DOM에 적용시킨다.

즉, 전체 실제 DOM을 바꾸지 않고도, 필요한 부분만 업데이트할 수 있다.


Virtual DOM 형태

Virtual DOM은 추상화된 JavaScript 객체 형태를 가지고 있다.

  • Virtual DOM은 실제 DOM이 아닌, 메모리 상에서 동작하기 때문에 훨씬 더 빠르게 동작한다.
  • Virtual DOM 트리는 실제 렌더링되지 않기 때문에, 연산 비용이 적다.

➡️ 실제 DOM 리렌더링에 비해서 매우 효율적이다.

<ul id="items">
  <li>첫 번째 아이템</li>
  <li>두 번째 아이템</li>
</ul>

⬇️

const virtualDom = {
  tagName: "ul",
  attributes: { id: "items" },
  children: [
    {
      tagName: "li",
      textContent: "첫 번째 아이템",
    },
    {
      tagName: "li",
      textContent: "두 번째 아이템",
    },
  ],
};

재조정 (Reconcilation)

React Diffing Algorithm

: 이전 Virtual DOM 트리와 새로운 Virtual DOM 트리를 비교하여 변경된 부분을 탐색하는 알고리즘

같은 레벨에 있는 노드를 파악한 뒤 다음 자식 세대의 노드를 순차적으로 파악해나간다.
(너비 우선 탐색(BFS)의 일종)

Reconcilation - React 공식문서


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

<div>
	<Counter />
</div>
           ⬇️
<span>
	<Counter />
</span>
// 부모 태그가 div에서 span으로 바뀌었다.

만약 엘리먼트의 태그나 컴포넌트가 변경된 경우, React는 기존의 트리를 버리고 완전히 새로운 트리를 구축한다.

  • 트리를 버릴 때 이전 DOM 노드들은 모두 파괴되고, 새로운 트리가 만들어지며 새로운 DOM 노드들이 삽입된다. 이전 트리와 연관된 모든 state는 사라진다.
  • 해당 엘리먼트 아래의 모든 컴포넌트도 언마운트(unmount)되고 그 컴포넌트가 가지고 있던 state도 사라진다.

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

<div className="before" />
           ⬇️
<div className="after" />
// 태그는 그대로, className만 바뀌었다.

React는 최대한 렌더링을 적게 하는 방향으로 최소한의 변경 사항만 업데이트한다.

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

/* CSS */
.light {
  color: 'red';
  fontweight: 'bold';
}
           ⬇️
.dark {
  color: 'green';
  fontweight: 'bold';
}
  • 이때 React는 정확히 color 속성만 변경된 것을 인식하고, color 속성만 수정하고 fontweight 및 다른 요소는 수정하지 않는다.
  • 이렇게 하나의 DOM 노드 처리가 끝나면, React는 이어서 해당 노드 자식들을 재귀적으로 처리한다.

자식에 대한 재귀적 처리

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

<ul>
  <li>first</li>
  <li>second</li>
</ul>
           
<ul>
  <li>first</li>  // 요소를 순차적으로 
  <li>second</li> // 순회하다가
  <li>third</li>  // 차이점을 발견하면 변경을 생성한다.
</ul>

이때 React의 동작 과정

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>

이때 React의 동작 과정

  1. 첫 번째 자식 노드를 비교할 때, <li>Duke</li><li>Connecticut</li>자식 노드가 서로 다르다고 인지하게 된 React는 리스트 전체가 바뀌었다고 받아들인다.
  2. <li>Duke</li><li>Villanova</li>는 그대로 유지시켜도 된다는 것을 깨닫지 못하고 전부 버리고 새롭게 렌더링해버린다.

이는 매우 비효율적인 동작 방식이다.
이러한 문제를 해결하기 위해 React는 key 속성을 지원한다.


Keys

자식들이 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의 동작 과정

  1. React는 key 속성을 통해 2014라는 자식 엘리먼트가 새로 생겼고, 2015, 2016 키를 가진 엘리먼트는 그저 위치만 이동했다는 걸 알게 된다.
  2. 따라서 React는 다른 자식 엘리먼트는 변경하지 않고, 추가된 엘리먼트만 변경한다.

Key의 특징

  • key 속성에는 보통 데이터 베이스 상의 유니크한 값(e.g. id)을 부여한다.
  • key 는 전역적으로 유일할 필요는 없고, 형제 엘리먼트 사이에서만 유일하면 된다.
profile
개발 공부 기록 블로그

0개의 댓글