Kimsora✨
article thumbnail
Published 2022. 11. 28. 17:01
Virtual DOM/ React Diffing Algorithm React
320x100
반응형

Virtual DOM

상대적으로 무거운 DOM에 비하여 React의 가상 DOM 객체는 실제 DOM 객체와 동일한 속성을 가지고 있음에도

“훨씬 가벼운 사본”이라고 표현할 수 있다

React는 새로운 요소가 UI에 추가되면 트리 구조로 표현이 되는 가상의 DOM이 만들어지고

요소의 상태가 변경이 되면 다시 새로운 가상의 DOM 트리가 만들어진다

이전의 가상의 DOM과 이후의 가상의 DOM의 차이를 비교 한 뒤

가상의 DOM은 실제 DOM에 변경을 수행할 수 있는 최상의 방법을 계산한다 

실제 DOM은 최소한의 작업만 수행해도 렌더링을 할 수 있게되어 

실제 DOM의 업데이트 비용을 줄일 수 있고 더 빠른 렌더링이 가능해진다

const vDom = {
	tagName: "html",
	children: [
		{ tagName: "head" },
		{ tagName: "body",
			children: [
				tagName: "ul",
				attributes: { "class": "list"},
				children: [
					{
						tagName: "li",
						attributes: { "class": "list_item" },
						textContent: "List item"
					}
				]
			]
		}
	]
}

 

React Diffing Algorithm

기존의 DOM 트리를 새로운 트리로 변환하기 위하여 최소한의 연산을 하기위해서 최신 알고리즘을 사용하여도 n개의 노드가 있을때 O(n^3)의 복잡도를 가지기 때문에 React는 두 가지의 가정을 가지고 시간 복잡도 O(n)의 새로운 휴리스틱 알고리즘(Heuristic Algorithm)을 구현해낸다 

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


다른 타입의 DOM 엘리먼트인 경우

 

<div>
	<Counter />
</div>

//부모 태그가 div에서 span으로 바뀝니다.
<span>
	<Counter />
</span>

 DOM 트리는 각 HTML 태그마다 각각의 규칙이 있어 그 아래 들어가는 자식 태그가 한정적이라는 특징이 있다.

(태그 밑에는태그만 와야 한다던가,태그 안에태그를 또 쓰지 못하는 것.)

자식 태그의 부모 태그 또한 정해져 있다는 특징이 있기 때문에, 부모 태그가 달라진다면 React는 이전 트리를 버리고
새로운 트리를 구축해버린다

이렇게 부모 태그가 바뀌어버리면, React는 기존의 트리를 버리고 새로운 트리를 구축하기 때문에 이전의 DOM 노드들은
전부 파괴 되어지고  부모 노드였던 <div>가 <span>으로 바뀌어버리면 자식 노드인 <Counter />는 완전히 해제된다.

이전 <div> 태그 속 <Counter />는 파괴되고 <span> 태그 속 새로운 <Counter />가 다시 실행되어지므로  새로운 컴포넌트가 실행되면서 기존의 컴포넌트는 완전히 해제(Unmount)되어버리기 때문에 <Counter />가 갖고 있던 기존의 state 또한 파괴된다

 

같은 타입의 DOM 엘리먼트인 경우

 

바뀌지 않는다면 React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이한다

가능한 이유는 앞서 React가 실제 DOM이 아닌 가상 DOM을 사용해 조작하기 때문인제  업데이트 할 내용이 생기면 virtual DOM 내부의 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면 그때 단 한번 실제 DOM으로의 렌더링을 시도한다

<div className="before" title="stuff" />

//기존의 엘리먼트가 태그는 바뀌지 않은 채 className만 바뀌었습니다.
<div className="after" title="stuff" />

 

React는 두 요소를 비교했을 때 className만 수정되고 있다 className before와 after는 각자 이런 스타일을 갖고 있다

//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>을 비교할 때 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐 점을 찾는다

첫 번째 자식 노드들과 두 번째 자식 노드들이 일치하는 걸 확인한 뒤 세 번째 자식 노드를 추가한다

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

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

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

위에 예시 처럼 구현하게 되면 React는 우리의 기대대로 최소한으로 동작하지 못하게 되고  원래의 동작하던 방식대로
처음의 노드들을 비교하게 된다

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

 이 문제를 해결하기 위해 key라는 속성을 지원하는데 효율적으로 가상 DOM을 조작할 수 있도록 한다

개발할 당시 key라는 속성을 사용하지 않으면 React 에서 key값을 달라고 경고문을 띄우는 것도 이 때문이다

=>key값이 없는 노드는 비효율적으로 동작할 수 있기 때문

키(key)

만약 자식 노드들이 이 key를 갖고 있다면, React는 그 key를 이용해 기존 트리의 자식과 새로운 트리의 자식이 일치하는지 아닌지 확인할 수 있다


<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

//key가 2014인 자식 엘리먼트를 처음에 추가합니다.
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

React는 key 속성을 통해 ‘2014’라는 자식 엘리먼트가 새롭게 생겼고, ‘2015’, ‘2016’ 키를 가진 엘리먼트는 그저 위치만 이동했다는 걸 알게 되고 기존의 동작 방식대로 다른 자식 엘리먼트는 변경하지 않고 추가된 엘리먼트만 변경다

 key 속성에는 보통 데이터 베이스 상의 유니크한 값(ex. Id)을 부여해주면 된다

키는 전역적으로 유일할 필요는 없고, 형제 엘리먼트 사이에서만 유일하면 된다

만약 이런 유니크한 값이 없다면 최후의 수단으로 배열의 인덱스를 key로 사용할 수 있다.

=>다만 배열이 다르게 정렬될 경우가 생긴다면 배열의 인덱스를 key로 선택했을 경우는 비효율적으로 동작한다  (배열이 다르게 정렬되어도 인덱스는 그대로 유지되기 때문)

인덱스는 그대로지만 그 요소가 바뀌어버린다면 React는 배열의 전체가 바뀌었다고 받아들일 것이고, 기존의 DOM 트리를 버리고 새로운 DOM 트리를 구축해버리기 때문에 비효율적으로 동작한다

728x90
반응형

'React' 카테고리의 다른 글

Fragment/Portal  (0) 2022.12.04
Custom Hooks와 코드 분할/React.lazy()와 Suspense  (0) 2022.11.29
useMemo/useCallback  (0) 2022.11.28
Redux  (0) 2022.11.04
React Props와State  (0) 2022.11.03
profile

Kimsora✨

@sorarar

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그

WH