jh.nrtv

[React] - Virtual Dom, React Diffing Algorithm 본문

React

[React] - Virtual Dom, React Diffing Algorithm

wlgus3 2023. 1. 25. 12:59

✅ Virtual Dom

리액트에 존재하는 실제 DOM의 사본과 같은 개념 

실제로 DOM 객체에 접근해서 조작하는 대신 가상DOM을 이용해 변화 전, 후를 비교해서 변화하는 부분만 빠르게 파악 후 적용

 

Real DOM (DOM)?  Document Object Model의 약자로, 뜻을 그대로 풀자면 문서 객체 모델을 의미합니다. 여기서 문서 객체란 브라우저가 JavaScript와 같은 스크립팅 언어가 <html>, <head>, <body>와 같은 태그들에 접근하고 조작할 수 있도록 태그들을 트리 구조로 객체화 시킨 것을 의미합니다. 다시 말하자면 DOM은 브라우저가 트리 구조로 만든 객체 모델입니다.

트리 구조로 DOM 객체가 이뤄져 있기 때문에 JavaScript는 쉽게 DOM 객체에 접근할 수 있고, DOM 객체를 조작할 수 있게 됩니다. 이렇게 프로그래밍 언어로 조작하는 DOM은 애플리케이션의 UI 상태가 변경될 때마다 해당 변경 사항을 나타내기 위해 업데이트가 됩니다. 만약 이런 DOM을 조작하는 정도가 잦다면 성능에 영향을 미치게 될 것이고, DOM의 렌더링은 브라우저의 파워, 즉 브라우저의 구동 능력에 의존하기 때문에 DOM의 조작 속도는 느려지게 됩니다.

 

DOM이 변경되고 업데이트가 된다는 것은 결국 브라우저의 렌더링 엔진 또한 리플로우(Reflow)한다는 것을 의미합니다. 즉 업데이트 된 요소와 그에 해당하는 자식 요소들에 의해 DOM 트리를 재구축함으로써 재랜더링 과정을 거쳐 UI를 업데이트 해야 합니다. 브라우저의 리플로우와 리페인트 과정은 다시금 레이아웃 및 페인트에 해당하는 재연산을 해야 하기 때문에 속도가 그만큼 느려지게 됩니다.

 

결국 “바뀐 부분만 비교해서 그 부분만 렌더링을 할 수는 없을까?“ 라는 아이디어를 기반으로 React는 Virtual DOM을 세상에 내놓게 된 것입니다.

 

Virtual DOM( 가상 돔 )? 그렇기 때문에 React에는 모든 DOM 객체에 대응하는 가상의 DOM 객체가 있습니다. 상대적으로 무거운 DOM에 비하여 React의 가상 DOM 객체는 실제 DOM 객체와 동일한 속성을 가지고 있음에도 “훨씬 가벼운 사본”이라고 표현할 수 있습니다.

 

[그림] 가상 DOM이 실제 DOM에 적용되는 과정을 약식으로 표현하자면 이렇습니다.

노란색 원들은 업데이트된 노드, 즉 UI 요소를 의미합니다. React에서는 “상태가 변경된” UI 요소로 볼 수 있습니다. 그런 다음 가상의 DOM 트리의 이전 버전과 현재 가상 DOM 트리 간의 차이가 계산됩니다. 그런 다음 트리가 업데이트된 UI를 제공하기 위해 부분적으로 리렌더링 됩니다. 이렇게 업데이트된 트리는 실제 DOM으로 한꺼번에 업데이트가 됩니다.

 

Virtual DOM의 형태? 추상화된 자바스크립트 객체 형태 가지고 있음 

 


✅ React Diffing Algorithm

React가 기존 가상 DOM과 변경된 새로운 가상 DOM을 비교할 때, React 입장에서는 변경된 새로운 가상 DOM 트리에 부합하도록 기존의 UI를 효율적으로 갱신하는 방법을 알아낼 필요가 있었습니다. 즉 하나의 트리를 다른 트리로 변형을 시키는 가장 작은 조작 방식을 알아내야만 했는데, 알아낸 조작 방식 알고리즘은 O(n^3)의 복잡도를 가지고 있었습니다.

만약 이 알고리즘을 그대로 React에 적용한다면 1000개의 엘리먼트를 실제 화면에 표시하기 위해 10억(1000^3)번의 비교 연산을 해야만 합니다. 사실 이것은 너무 비싼 연산이기 때문에 React는 두 가지의 가정을 가지고 시간 복잡도 O(n)의 새로운 휴리스틱 알고리즘(Heuristic Algorithm)을 구현해냅니다.

 

[알고리즘] Time Complexity (시간 복잡도) - 하나몬

⚡️ Time Complexity (시간 복잡도) Time Complexity (시간 복잡도)를 고려한 효율적인 알고리즘 구현 방법에 대한 고민과 Big-O 표기법을 이용해 시간 복잡도를 나타내는 방법에 대해 알아봅시다. ❗️효

hanamon.kr

 

두 가지 가정은 이것입니다.

  1. 각기 서로 다른 두 요소는 다른 트리를 구축할 것이다.
  2. 개발자가 제공하는 key 프로퍼티를 가지고, 여러 번 렌더링을 거쳐도 변경되지 말아야 하는 자식 요소가 무엇인지 알아낼 수 있을 것이다.

 

React가 DOM 트리 탐색하는 방법

 React는 기존의 가상 DOM 트리와 새롭게 변경된 가상 DOM 트리를 비교할 때, 트리의 레벨 순서대로 순회하는 방식으로 탐색합니다. 즉 같은 레벨(위치)끼리 비교한다는 뜻입니다. 이는 너비 우선 탐색(BFS)의 일종이라고 볼 수 있습니다.

React는 이런 식으로 동일 선상에 있는 노드를 파악한 뒤 다음 자식 세대의 노드를 순차적으로 파악해 나갑니다.

 

다른 타입의 DOM 엘리먼트인 경우? 이전 트리 버리고 새로운 트리 구축 , 이전 돔 노드 전부 파괴 (unmount)

같은 타입의 DOM 엘리먼트인 경우?  최소한 변경 사항만 업데이트함 ,업데이트 할 내용이 생기면 virtual DOM 내부의 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면 그때 단 한번 실제 DOM으로의 렌더링을 시도합니다.

//className이 before인 컴포넌트
<div style={{color: 'red', fontWeight: 'bold"}} title="stuff" />

//className이 after인 컴포넌트
<div style={{color: 'green', fontWeight: 'bold"}} title="stuff" />

두 엘리먼트를 비교했을 때 React는 정확히 color 스타일만 바뀌고 있음을 눈치챕니다. 따라서 React는 color 스타일만 수정하고 fontWeight 및 다른 요소는 수정하지 않습니다. 이렇게 하나의 DOM 노드를 처리한 뒤 React는 뒤이어서 해당 노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때마다 변경합니다. 이를 재귀적으로 처리한다고 표현합니다.

 

자식 엘리먼트의 재귀적 처리

예를 들면 이렇게 자식 엘리먼트가 변경이 된다고 가정하겠습니다.


<ul>
  <li>first</li>
  <li>second</li>
</ul>

//자식 엘리먼트의 끝에 새로운 자식 엘리먼트를 추가했습니다.
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React는 기존 <ul>과 새로운 <ul>을 비교할 때 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐 점을 찾습니다. 그렇기 때문에 예상대로 React는 첫 번째 자식 노드들과 두 번째 자식 노드들이 일치하는 걸 확인한 뒤 세 번째 자식 노드를 추가합니다.

이렇게 React는 위에서 아래로 순차적으로 비교하기 때문에, 이 동작 방식에 대해 고민하지 않고 리스트의 처음에 엘리먼트를 삽입하게 되면 이전의 코드에 비해 훨씬 나쁜 성능을 내게 됩니다.

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

//자식 엘리먼트를 처음에 추가합니다.
<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

이렇게 구현하게 되면 React는 우리의 기대대로 최소한으로 동작하지 못하게 됩니다. React는 원래의 동작하던 방식대로 처음의 노드들을 비교하게 됩니다.

 

처음의 자식 노드를 비교할 때, <li>Duke</li> 와 <li>Connecticut</li>로 자식 노드가 서로 다르다고 인지하게 된 React는 리스트 전체가 바뀌었다고 받아들입니다. 즉 <li>Duke</li>와 <li>Villanova</li>는 그대로기 때문에 두 자식 노드는 유지시켜도 된다는 것을 깨닫지 못하고 전부 버리고 새롭게 렌더링 해버립니다. 이는 굉장히 비효율적인 동작 방식입니다.

그래서 React는 이 문제를 해결하기 위해 key라는 속성을 지원합니다. 이는 효율적으로 가상 DOM을 조작할 수 있도록 합니다. 만일 개발할 당시 key라는 속성을 사용하지 않으면 React 에서 key값을 달라고 경고문을 띄우는 것도 이 때문인 것입니다. key값이 없는 노드는 비효율적으로 동작할 수 있기 때문입니다.

 

 

'React' 카테고리의 다른 글

[React] Vanilla JS로 React의 useState hook 구현하기  (1) 2023.12.20
React - 번들링과 웹팩  (0) 2023.01.18
Redux -React 상태관리  (0) 2022.12.27
React- Custom Component- CDD, CSS in JS  (0) 2022.12.22