## [[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