## [[3. Virtual DOM|Virtual DOM]]란?
![[Pasted image 20231107164649.png]][^0]
가상 DOM. DOM의 상태를 메모리에 저장하고 이후 수정되면 DOM과 동기화하는 패턴[^1]. 효율적으로 DOM을 수정하는 방법으로 React.js에서 제시되었다.
## 문제 상황
### `innerHTML`수정의 장점과 단점
#### 장점
자바 스크립트에서 동적으로 DOM을 수정하는 가장 편한 방법은 `innerHTML` 값을 수정하는 것이다. `appendChild` 등을 이용한 방법은 복잡한 구조를 가진 객체를 수정하기 힘들기 때문이다. 예로 `<p><p><p><p>Hello</p>W</p>o</p>rld!</p>`를 `appendChild`로 추가하려면 아래와 같은 긴 코드가 필요하다.
```javascript
let p1 = document.createElement("p"),
p2 = document.createElement("p"),
p3 = document.createElement("p"),
p4 = document.createElement("p");
p4.textContent = "Hello";
p3.appendChild(p4);
p3.appendChild(document.createTextNode("W"));
p2.appendChild(p3);
p2.appendChild(document.createTextNode("o"));
p1.appendChild(p2);
p2.appendChild(document.createTextNode("rld!"));
```
```javascript
// 위와 결과가 같은데 줄은 훨씬 적다.
let p1 = document.createElement("p");
p1.innerHTML = "<p><p><p>Hello</p>W</p>o</p>rld!"
```
#### 단점
문제는 이런 방식이 본질적으로 DOM이 아니라 그 전 단계인 문자열을 수정하는 방식이라는 것이다. 예를 들어 다음과 같이 DOM을 수정했다고 하자.
```HTML
어떤DOM객체.innerHTML += "<p>안녕!</p>"`
```
우리 입장에서는 `어떤DOM객체`에 `<p>` 하나 추가한 것이지만, 브라우저 입장에서는 이전과 다른 새로운 문자열이 된 것이다. 따라서 브라우저는 다시 [[1. Parsing|Parsing]]을 통해 DOM 트리를 갱신하고 렌더링을 수행한다. 우리는 `<p>` 하나 추가하여 렌더링하고 싶었지만, 브라우저는 `어떤DOM객체`와 모든 후손 노드를 다시 렌더링하는 것이다. 이를 막는 방법은 DOM을 수정하는 `appendChild`와 같은 함수를 이용하는 방법이지만, 이 경우 도돌이표처럼 복잡한 구조를 가진 객체를 수정하기 힘들다는 문제점이 존재한다.
## 해결책
### 나는 `innerHTMl`, 너는 `appendChild`
문제를 정리해보면 우리한테 편한 `innerHTML`을 이용한 방법이 비효율적인 렌더링을 일으키고, 비효율적인 렌더링을 막으려하니 우리가 불편하다는 점이다. 이런 경우 좋은 해결책은 입력은 `innerHTML`로 받고 내부적으로는 `appendChild`와 같이 DOM 수정을 하는 "대리자"를 하나 만드는 것이다. 이 "대리자"는 우리에게 마치 DOM 인 것처럼 행세하며 우리에게 `innerHTML`로 입력을 받은 뒤, 내부적으로는 실제 DOM을 수정하여 비효율적인 렌더링을 막는다. 이 "대리자"의 이름이 바로 `Virtual DOM`이다.
## `Virtual DOM`의 작동 방식
아래의 내용은 이해를 돕기 위해 실제 가상 DOM을 단순화한 내용으로 출처는 [이 글](https://codingmedic.wordpress.com/2020/11/10/the-virtual-dom/)이다.
### 1. DOM 복사
```HTML
<ul class = "list">
<li class = "list__item">List item</li>
</ul>
```
위 HTML 문서의 원본 DOM이 아래와 같다고 할 때
```javascript
const list = {
tagName: "ul",
attributes: { "class": "list" },
children: [
{
tagName: "li",
attributes: { "class": "list__item" },
textContent: "List item"
}
]
};
```
다음과 같이 DOM을 복사한 `copy`를 생성한다.
``` javascript
const copy = {
tagName: "ul",
attributes: { "class": "list" },
children: [
{
tagName: "li",
attributes: { "class": "list__item" },
textContent: "List item"
}
]
};
```
### 2. 차이점 확인
```javascript
document.querySelector(".list").innerHTML = `
<ul class = "list">
<li class = "list__item">List item one</li>
<li class = "list__item">List item two</li>
</ul>
`;
```
위와 같은 명령으로 `copy`를 수정했다고 하자. 그럼 `copy`는 아래와 같이 수정된다.
```javascript
const copy = {
tagName: "ul",
attributes: { "class": "list" },
children: [
{
tagName: "li",
attributes: { "class": "list__item" },
textContent: "List item one"
},
{
tagName: "li",
attributes: { "class": "list__item" },
textContent: "List item two"
}
]
};
```
그럼 React 같이 가상 DOM을 사용하는 프레임워크는 원본 DOM과 수정된 복사본 사이의 차이를 확인하고 아래와 같은 `diffs`를 생성한다. 이 작업을 **diffing**이라고 한다.
```javascript
const diffs = [
{
newNode : {/* new version of list item one */},
oldNode : {/* original version of list item one */},
index : {/* index of element in parent's list of child nodes */}
},
{
newNode : {/* list item two */},
index : {/* */}
}
]
```
### 3. Virtual DOM, DOM 동기화
`diffs`를 통해 수정된 노드들의 목록을 알게 되었으니 이를 토대로 원본 DOM과 Virtual DOM을 동기화한다.
우리는 분명 `innerHTML` 처럼 HTML을 수정하였지만, 내부적으로는 DOM을 수정하게 되는 것이다.
## Reference
[^0]:https://codingmedic.wordpress.com/2020/11/10/the-virtual-dom/
[^1]:https://ko.vuejs.org/guide/extras/rendering-mechanism.html