브라우저가 서버로부터 HTML 응답을 받아 화면을 그리기 위해 실행하는 과정을 Critical Rendering Path
라고 한다.
critical Rendering Path
는 다음과 같은 단계를 가진다.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
해당 과정에서 %, vh, vw와 같이 상대적인 위치, 크기 속성
은 모두 실제 화면에 그려지는 px단위로 변환된다.
해당 과정을 거치고 나면 비로소 화면에 나타나게 된다.
위의 과정을 하나의 그림으로 표현하면 아래와 같다.
하지만 여기서 우리가 자바스크립트를 통해 DOM을 조작
하면 보통의 경우 DOM tree를 수정하게 된다. 그렇게되면 render tree - Layout - Painiting의 단계를 또 다시 겪게 되는데 이는 많은 비용이 들며 속도 저하의 주요 원인
이 된다.
HTML 파일에서 스타일을 적용하는 방법에는 여러가지가 있다.
그 중에서 브라우저는 <style />
또는 inline style 블럭을 발견했을 때는 CSS 구문을 분석 후 CSS 규칙을 이용해서 CSSOM 트리를 업데이트 후 HTML 파싱을 재진행
그러나 외부 stylesheet는 다른 방식으로 동작된다. 외부 stylesheet는 Parser blocking요소가 아니기 때문에 메인 스레드가 아닌 백그라운드에서 다운로드하기 때문에 파싱을 멈추지 않는다.
css는 render blocking ( render tree를 만들 때 CSSOM이 필요하기 때문에
{cascading 때문에 css는 점진적으로 CSSOM 트리를 만들 수 없고 CSS를 모두 해석해야 구성할 수 있다.})
javascript는 Parser blocking ( async나 defer 옵션을 준다면 실행되는 순서를 늦출 수 있다.
async: 다운은 병렬적으로하고 다운이 완료되면 dom parsing을 blocking
defer: 다운은 병렬적으로하고 다운이 완료되더라도 dom parsing을 blocking하지 않고 끝날 때까지 기다림)
자바스크립트에 의해 DOM 트리, CSSOM 트리가 변경될 때 Render Tree가 다시 재구성되며 Reflow와 Repaint가 일어나게 된다. (위에서 언급된 Layout의 과정부터 다시 시작)
기하적인 영향을 주지 않는 CSS 속성값을 변경하면 Layout 과정은 건너뛴다.
Layout이 일어나면 전체 픽셀을 다시 계산해야 하므로 부하가 크다. 반면, Repaint는 이미 계산된 픽셀값을 이용해 화면을 그리기 때문에 Layout에 비해 부하가 적다.
다음과 같은 코드가 있다고 가정해보자.
<div id="sample" style="background:red;width:150px;height:50px;">
Sample
</div>
요소에 기하적인 영향을 주는 CSS 속성값에는 height, width, left, top, font-size, line-height 등이 존재하며 해당 속성을 이용해서 CSSOM 트리를 변경하게 되면 다음과 같이 Layout이 발생한 것을 볼 수 있다.
const example = document.getElementById('example');
example.style.width = '400px';
Layout 발생
요소에 기하적인 영향을 주지 않는 CSS 속성값에는 background-color, color, visibility, text-decoration등이 존재하며 해당 속성을 이용해서 CSSOM 트리를 변경하게 되면 다음과 같이 Layout은 건너뛰고 Paint만 발생하는 것을 볼 수 있다.
const sample = document.getElementById('example');
example.style.backgroundColor = 'blue';
리페인트 발생
React는 SPA이기 때문에 DOM을 조작
하는 경우가 많아 위와 같은 이슈를 겪게 된다. 그래서 해당 이슈를 해결하고자 Virtual DOM을 사용한다.
React의 공식문서에 따르면 Virtual DOM
은 이상적인 또는 "가상" UI 표현이 메모리에 유지되고 ReactDOM과 같은 라이브러리에 의해 "실제" DOM과 동기화되는 프로그래밍 개념이라고 한다.
Virtual DOM
을 사용하면 다음과 같은 장점이 존재한다.
화면에 변경사항들이 발생했을 때
Virtual DOM에서 Batch단위로 처리
하여 나온 최종적인 결과를 실제 DOM에게 전달하기 때문에 불필요한 연산의 횟수를 줄일 수 있다.
즉, 한번의 렌더링만으로도 되는 과정에서 여러 곳이 바뀌면서 여러번 렌더링 할 때 효율적으로 한번의 업데이트를 하는 방법을 제시해준다. (DOM이 빈번히 업데이트하는 것을 좀 더 효율적인 방법으로 업데이트)
하지만 이러한 과정은 Virtual DOM 없이도 변화가 있을 때마다 그 변화들을 묶어 DOM fragment에 적용
하여 실제 DOM에 전달하여도 불필요한 연산의 횟수를 줄일 수 있으며 Virtual DOM은 DOM보다 빠르다고도 할 수 없다.
그럼에도 Virtual DOM을 사용하는 이유는 매번 어떤 DOM 노드가 변경되어야하는지 파악해야하고 조작 할 DOM이 많아지게 되면 실수가 발생할 가능성도 커지는데 Virtual DOM은 이러한 복잡한 과정들을 자동화 및 추상화 해주기 때문에 사용한다고 할 수 있다.