DOM(Document Object Model)
정의
- HTML이나 XML 문서들을 자바스크립트같은 프로그래밍 언어로 조작 할 수 있도록 되어 있는 모델
- HTML이란 코드로 설계된 웹페이지가 브라우저 안에서 화면에 나타나고 이벤트에 반응하고 값을 입력받는 등
- 기능들을 수행할 객체들로 실체화 된 형태
- HTML 돔은 노드를 탐색하거나 수정할 수 있는 API를 제공한다. 돔은 getElementById 혹은 removeChild와 같은 메소드를 포함.
- 우리는 돔과 관련된 작업을 하기 위해 보통 자바스크립트를 사용해서 제어할 수있다.
기존 렌더링 방식
- 브라우저는 서버가 보내준 HTML 파일 을 해석(Parsing)하여 DOM 트리 를 만든다.
- 브라우저는 서버가 보내준 CSS 파일 을 해석(Parsing)하여 CSSOM 트리 도 만든다.
- DOM 트리 + CSSOM 트리 를 결합해 렌더트리 를 만든다.
- 렌더트리로 각 노드의 위치와 크기를 계산한 레이아웃 을 만든다. 뷰포트(Viewport) 내에서 각 노드들의 위치와 크기를 계산한다
- Layout 계산이 완료되면 이제 요소들을 실제 화면을 그리는 페인트(Paint) 를 한다.
HTML 돔은 HTML document의 구조와 같은 트리 구조로 이루어져 있다. 트리 구조는 탐색하기 쉬우므로 사용성이 좋다.
하지만 DOM은 새로운 요청이나 변경사항이 있을 때마다 매번 리렌더링한다. 오늘날의 돔 트리는 매우 거대하다.
왜냐하면, 우리는 더욱더 크고 다양한 웹 어플리케이션(싱글 페이지 어플리케이션 - SPA)을 개발하고 있다. 그러다 보니
우리는 돔 트리를 수정하는 일이 점점 잦아졌다. 만약 돔 안에 천 개 단위의 div이 있다고 가정하자.
우리는 모던 웹 개발자이기 때문에 이 앱은 SPA로 구성되어 있다. 거기에는 수많은 이벤트 핸들러 메소드가 있다.
문제점
첫 번째로 다루기가 힘들다. 이벤트 핸들러를 다뤄야 하는 상황을 가정해보자. 만약 문맥을 제대로 찾지 못한다면, 코드가 어떻게 흘러가는지 깊숙하게 파고들어야 한다. 시간 소모가 크고 버그가 발생할 수도 있다.
두 번째로 불편하다. 탐색하는 걸 일일이 이런 식으로 해야 할까? 아마 우리가 더 똑똑해져서 어떤 노드가 업데이트 되어야 한다는 걸 구분할 수 있다면 어떨까?
다시 한 번 말하지만, 리액트는 이런 부분을 도와준다. 첫 번째 문제점을 해결해줄 방법은 '정의'이다. DOM 트리를 일일이 탐색하는 낮은 수준의 방법 대신에 컴포넌트가 어떻게 구성되어야 할지 간단하게 정의 해주기만 하면 된다. 낮은 수준의 작업은 리액트가 대신해줄 것이다. HTML 돔 API를 호출하는 작업에 대해서는 리액트가 해주기 때문에 걱정할 필요가 없다. 결국, 컴포넌트는 어떤 일을 해야 할지에 대해 정의를 해주면 된다.
Virtual DOM(가상 돔)
정의
- 버츄얼돔은 우리가 가상적으로 UI를 저장해두었다가 Real DOM과 연동(sync)시켜주는 프로그래밍 컨셉이다.
공식문서에서 “reconciliation (재조정)”이라는 과정을 통해서 한다.
reconciliation 과정 : 리액트는 Real DOM과 Virtual DOM을 참고한다.그리고 “diffing” 알고리즘을 이용해 변화가 일어난 DOM 요소만 새로 렌더링을 한다. - DOM의 구조를 간결히 흉내낸 자바스크립트 객체
- 가상 돔은 HTML 돔의 추상화 개념이다. 이것은 가볍고, 브라우저 스펙의 구현체와는 분리되어있다.
사실, 돔은 이미 추상화 개념이기 때문에 가상 돔은 추상화를 또 추상화한 개념이다
비교 알고리즘 (Diffing Algorithm)
Elements Of Different Types
상위 root element가 바뀌면 하위 element도 함께 바뀐다.
<div>
<Counter />
</div>
<span>
<Counter />
</span>
위와 같이 상위태그가 div에서 span으로 바뀌게 되면 하위 Counter 컴포넌트 자체는 바뀌지 않았더라도 두 루트 엘리먼트의 타입이 다르면, React는 이전 트리를 버리고 완전히 새로운 트리를 구축합니다.
DOM Elements Of The Same Type
같은 타입의 두 React DOM 엘리먼트를 비교할 때 eact는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신합니다. 그렇다면 요소(element)는 같지만 속성(attribute)만 바뀌면 어떨까?
<div className="before" title="stuff" />
<div className="after" title="stuff" />
이런 경우엔 리액트는 아까처럼 DOM은 유지시키고 className만 수정. 새로 DOM을 생성하지 않고 효율적으로 렌더링 할 수 있다.
Recursing On Children
DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성합니다.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li> //여기만 추가.
</ul>
이렇게 세번째 리스트만 추가가 되어진 경우엔 어떨까? 리액트 diffing 알고리즘은 첫번째, 두번째 리스트가 같은 것을 확인했다. 그리고 세번째가 다른걸 확인하였기 때문에 세번째 element만 추가한다.
리액트 덕분에 효율적으로 DOM을 렌더링 할 수 있다. 기존 DOM 방식이었다면 갈아엎고 새로 생성 되었을 것 이다.
하지만 위와 같이 단순하게 구현하면, 리스트의 맨 앞에 엘리먼트를 추가하는 경우 성능이 좋지 않습니다.
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li> //Conneticut만 추가되었지만 순서가 첫 번째로 들어감.
<li>Duke</li>
<li>Villanova</li>
</ul>
우리의 바램은 리액트가 “선생님, 제가 Conneticut을 생긴것을 찾아냈으니 이것만 추가로 생성하겠습니다.” 이렇게 해주었으면 좋겠지만, 아니다. 첫 번째 리스트가 Duke가 Conneticut으로 바뀐 것을 알아채고 리액트는 li의 모든 요소를 새로 렌더링한다.
즉, React는 <li>Duke</li>와 <li>Villanova</li> 종속 트리를 그대로 유지하는 대신 모든 자식을 변경합니다. 이러한 비효율은 문제가 될 수 있습니다.
Keys
이러한 문제를 해결하기 위해, React는 key 속성을 지원합니다. 자식들이 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>
이렇게 각 요소에 key를 지정해주면 순서가 다르더라도 처음부터 렌더링을 하지 않고 변화가 생긴 부분만 렌더링을 해준다.
<li key={item.id}>{item.name}</li>
위와같이 id를 사용하면 효율적으로 좋은 코드를 쓸 수 있다. 그리고 key를 사용하지 않는 경우에 리액트에서 warning 메세지를 보내므로 key를 꼭 사용하자.
ex)
- 어떤 ul안에 li들만 변경할거라 치면 다른것들은 건들지 않고 딱 필요한 그것들만 조작 하는것.
- 실제 가구들의 미니어쳐들을 출력해서 그것들을 미리 배치 해보면서 어떤 가구들이 실제로 위치가 바뀌는지 먼저 파악한 후 딱 그 실제 가구들만 움직이는 과정.
📌 DOM vs VirtualDOM
DOM | Virtual DOM | |
업데이트 | 느리다 | 빠르다 |
메모리 | 메모리 낭비가 심함 | 메모리 낭비가 덜함 |
새로운 element 업데이트 방식 | 새로운 element가 업데이트된 경우 새로운 DOM 생성 | 새로운 element가 업데이트 된 경우 새로운 가상 DOM 생성 후 이전 DOM과 비교 후 차이점 DOM만 업데이트 |
✅ 가상 돔 때문에 리액트가 빠르다고 하는것은 정확한 표현이 아니다.
💥 Svelte는 가상돔을 쓰지 않기때문에 더욱 빠르다고 주장!
ex) 미니어쳐를 출력해서 시뮬레이션하는 과정을 빼면 더 빨라질 수 있다
➡️ 그것들 없이도 효율적으로 DOM조작이 가능하다면!
‼️ WHY : React는 Svelte같은 방식을 사용하지 않는 것일까??
React = Library
Svelte = Compiler
리엑트로 만든 사이트들은 자바스크립트 파일들을 사용해서 동작하고,
사용자가 작성한 React전용 코드를 브라우저가 가상돔으로 해석해서 메모리에서 먼저 구현한 다음 최종적으로 실제 DOM에 적용합니다. 이때의 DOM 변경을 가장빠르게는 아니지만 사용자가 불편을 느끼지 않을만큼 충분히 빠르게 설계한 결과물이 가상 돔 방식.
리액트 등의 거치는 과정은 사이트가 실행되는 시점 즉 런타임에 서 발생. 즉 브라우저가 자바스크립트 파일 받아서 하는 작업
가상 돔은 이런 런타임이란 제약 조건 하에서 브라우저에 로드된 라이브러리로서, DOM에 가해지는 변화들을 최소한의 변경으로 구현할 방법을 찾는 방식입니다.
Svelte는 그과정을 사이트가 배포되기 전에 미리 다 해두도록 합니다.
스벨트 형식에 맞게 코드를 짜고 이걸 빌드하면 사이트에서 딱 실제로 일어날 일등들즉 이 이벤트가 들어오면 이 가구를 어디로 옮기고 이것들만 컴팩트한 자바스크립트 파일로 컴파일 해서 내놓는것 ➡️ 브라우저에서 딱히 뭘 돌릴 필요가 없어짐
리엑트나 뷰는 사용자가 사이트에 접속했을 떄 자신들의 라이브러리를 로드해가지고다가 브라우저에서 가상 DOM을 써서 하는 최적화 작업을 스벨트는 코딩하고 나서 빌드할때 미리 해둬가지고 딱 필요한 부분만 자바스크립트로 내놓는다는것.
따라서 스벨트로 만든 사이트들은 용량면에서도 더 가볍고 돔의 조작도 더 빠름
그러나 스벨트는 아직 만들어진지 오래 되지않아서 커뮤니티가 덜 쌓였고 코드를 미리 컴파일 해서 내보내야 한다는것도 특정형태의 사이트에서는 한계가 될 수도 있습니다.
이에 비해 리엑트는 강력한 기능 도 많고 안정성도 많이 다져진 상태.
참고사이트
공식문서 재조정 (Reconciliation) : https://ko.reactjs.org/docs/reconciliation.html#gatsby-focus-wrapper
'React' 카테고리의 다른 글
리액트 라우터 outlet 사용하여 중첩된 레이아웃 구성 (0) | 2023.03.09 |
---|---|
Custom Hook 만들기 (0) | 2023.03.01 |
useState & useEffect 비교 (0) | 2023.03.01 |
React Portal이란? (0) | 2023.03.01 |
composition의 개념 ('children prop') (0) | 2022.11.22 |