DOM(Document Object Model)은 브라우저 렌더링 엔진의 HTML parser에 의해 생성된 트리 구조의 Node 객체 모델이다.
DOM의 목적은 JS를 사용하여 문서에 대한 추가, 삭제, 이벤트 처리 등을 처리하는 인터페이스 제공이다.
HTML을 파싱하여 DOM 객체로 이뤄진 DOM 트리를 생성한다.
CSS parser는 inline style과 CSS 코드를 파싱하여 CSSOM 트리를 생성한다.
DOM과 CSSOM의 정보를 바탕으로 실제 브라우저의 화면에 노출돼야 하는 노드들에 대한 정보인 렌더 트리를 생성한다.
실제 브라우저의 화면에 노출된다는 것은 display: none
인 보이지 않는 노드들은 렌더 트리를 생성할 때 제외한다는 것을 의미한다.
반면, 공간을 차지하는 visibility: hidden
속성은 렌더 트리에 포함된다.
렌더 트리의 각 노드들이 브라우저의 Viewport 내에서 어느 위치에 어떤 크기로 배치돼야 하는지에 대한 정보를 계산한다.
Layout 단계를 통해 %, vh, vw 등과 같은 상대적인 속성이 px 단위로 변환된다.
Viewport ? 그래픽이 표시되는 브라우저의 영역, 크기
디스플레이의 크기, 브라우저 창의 크기에 따라 달라진다.
렌더 트리의 각 노드들을 모니터에 실제 픽셀로 그리는 단계이다.
Reflow가 발생하면 Paint는 반드시 수행돼야 한다.
그러나 Reflow가 발생했을 경우에만 Paint가 발생하는 것은 아니다.
background-color, visibility 등과 같이 레이아웃에는 영향을 주지 않는 스타일 속성만 변경됐을 경우 Reflow를 수행할 필요가 없기 때문에 Paint 과정만 수행된다.
DOM을 수정할 때마다 렌더 트리 생성부터 Reflow, Repaint의 과정을 다시 수행해야 한다.
DOM 자체의 수정은 빠르지만, 브라우저가 수행해야 하는 이후의 과정이 느리다.
즉, 성능 저하의 주요 원인은 DOM을 수정할 때 발생하는 Reflow, Repaint 과정에 있다.
Reflow가 빈번하게 발생하는 경우 브라우저에서는 성능 저하가 발생하며, 웹 페이지의 DOM이 복잡하게 구성되어 있고 CSS가 많이 적용된 사이트일 수록 더욱 심해진다.
DOM 수정은 수반되는 비용이 크기 때문에 성능 저하를 최소화하기 위해 DOM을 최소한으로 수정해야 한다.
이 문제를 해결하기 위해 Virtual DOM이 등장했다.
Virtual DOM이란 실제 DOM의 구조와 비슷한 React 객체의 트리다.
React.createElement로 생성
이전 elements와 새로 생성된 elements 비교
필요한 경우 DOM update
컴포넌트 내에 state가 변경된 경우 React는 해당 컴포넌트를 dirty
하다고 표시하고 batch
에 추가한다.
Virtual DOM 엘리먼트와 실제 브라우저에 등록되어 있는 DOM 엘리먼트를 비교, 순회하며 dirty 체크된 엘리먼트들을 처리한다.
처리 과정에서 속성 값만 변경된 경우 속성 값만 업데이트하고, 엘리먼트의 태그 혹은 컴포넌트가 변경된 경우 해당 노드를 포함한 하위의 모든 노드를 언마운트(제거)한 뒤 새로운 virtual DOM으로 대체한다.
batch에 쌓인 모든 변경 혹은 업데이트가 모두 처리된 후 한 번 실제 DOM에 이 결과를 업데이트 한다.
트리를 비교할 때 동일한 level의 node들끼리만 비교한다.
ex) div 태그가 span 태그로 바뀐 경우
1. 기존 트리를 제거 후 새로운 트리를 만든다.
2. 기존 트리 제거 시 트리 내부의 엘리먼트, 컴포넌트들은 모두 제거한다.
3. 새로운 트리를 만들 때 내부 엘리먼트, 컴포넌트들도 모두 새로 만든다.
ex) class가 변경된 경우
1. 엘리먼트의 attributes를 비교한다.
2. 변경된 attributes만 업데이트 한다.
3. 자식 엘리먼트들에 diff 알고리즘을 재귀적으로 적용한다.
ex) <Item price=100 />
=> <Item price=200 />
1. 컴포넌트 인스턴스 자체는 변하지 않는다. (컴포넌트의 state 유지)
2. 컴포넌트 인스턴스의 업데이트 전 라이프 사이클 메서드들이 호출되며 props가 업데이트 된다.
3. render( )를 호출하고, 컴포넌트의 이전 엘리먼트 트리와 다음 엘리먼트 트리에 대해 diff 알고리즘을 재귀적으로 적용한다.
DOM 노드의 자식들을 재귀적으로 처리할 때 React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성한다.
이 때 비교하는 대상은 단순히 first-child to last-child로 비교한다.
// before
<ul>
<li>ItemA</li>
<li>ItemB</li>
</ul>
// after
<ul>
<li>ItemA</li> // ItemA 유지
<li>ItemB</li> // ItemB 유지
<li>ItemC</li> // ItemC 추가
</ul>
// before
<ul>
<li>ItemA</li>
<li>ItemB</li>
</ul>
// after
<ul>
<li>ItemC</li> // ItemA -> ItemC 변경
<li>ItemA</li> // ItemB -> ItemA 변경
<li>ItemB</li> // ItemB 추가
</ul>
// before
<ul>
<li key="A">ItemA</li>
<li key="B">ItemB</li>
</ul>
// after
<ul>
<li key="C">ItemC</li> // ItemC 추가
<li key="A">ItemA</li> // ItemA 유지
<li key="B">ItemB</li> // ItemB 유지
</ul>
key는 오로지 형제 사이에서만 유일하면 되고, 전역에서 유일할 필요는 없다.
리스트 배열이 재정렬되지 않거나 last-child에서만 추가, 변경, 제거가 일어난다면 인덱스를 key로 사용해도 되지만, 순서가 바뀌는 경우가 발생하면 key가 전부 바뀌기 때문에 key를 사용한 의미가 없다.
<참고 : https://velog.io/@1nthek/React-Virtual-DOM%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81
https://yujonglee.com/reactrendering.html
https://calendar.perfplanet.com/2013/diff/ >