발표 스터디

리액트의 Virtual DOM 은 도대체 뭐가 좋다는걸까?

개발자R 2022. 4. 19. 18:04
반응형

리액트의 가장 큰 특징 중 하나는 가상 DOM을 사용한다는 것이다.

ReactDOM이 "실제 브라우저에 그려진 DOM"과 가상DOM을 비교하여 동기화 한다.

 

실제 DOM을 직접 다시 그리면 안좋은걸까?

DOM을 조작하는 것이 비효율적인 걸까?

 

여러 문서들을 본 결과, DOM을 직접 조작하는 것 자체가 비효율적인 건 아니다.

다만 DOM을 한번 수정하게 되면 지난 번에 공부한 것 처럼 ( https://life-of-erin.tistory.com/48?category=1035457 )

 

브라우저에서 화면이 그려지는 원리

브라우저를 구성하고 있는 요소 렌더링 엔진의 동작 원리 파싱 - HTML 파싱 HTML 파서의 특징 1. 오류에 너그럽다. HTML Tag가 정확하게 쓰여있지 않더라도 브라우저가 자체적으로 수정하여 에러를 제

life-of-erin.tistory.com

파싱 - 렌더크리 구축 - 레이아웃 - 페인트 

이 과정을 모두 거치게 된다.

 

변화가 생길 때마다 계속계속 렌더링을 한다면 어떻게 될까?

그게 바로 성능저하의 가능성이 생기는 지점이다.

 

그래서 ReactDOM은 DOM에서 바뀌어야 하는 부분을 미리 다 계산한 다음 한번에 변화를 시키도록 한다.

이 과정에서 메모리가 많이 차지하게 되고, 렌더링을 하는 규모는 커질 수 있다.

하지만 묶어서 한 번에 하기 때문에 효율적이게 되는 것이다.

그래서 React가 DOM보다 빠르다는 것은 잘못된 말이라고 한다.

 

한마디로 말해서 Virtual DOM은 DOM조작을 위한 버퍼라고 할 수 있다.

 

계산을 하는 과정이 또 들어가게 되는데 어떻게 더 효율적일 수가 있을까?

 

리액트는 비교 알고리즘을 최적화했다.

원래 n개의 엘리먼트를 포함하는 트리에 대해, 다른 트리로 변환하기 위한 최소한의 연산 수를 구하는 알고리즘은 O(n^3)의 복잡도를 가지고 있다고 한다.

하지만 React는 아래의 두 가정을 하여 O(n) 복잡도의 알고리즘을 사용한다.

1. 서로 다른 타입(태그)의 두 엘리먼트는 서로 다른 트리를 만든다. 

2. 개발자가 key prop을 이용하여 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시한다.

 

비교 알고리즘

root 엘리먼트부터 비교하며, 자식 엘리먼트를 재귀적으로 비교한다.

 

DOM 엘리먼트 타입이 다른 경우

1번 가정에 의해, 자식 컴포넌트들이 완전히 동일하더라도, 부모 컴포넌트의 타입이 바뀐다면 그 아래의 자손들은 모두 새로 만들어진다.

// before
<div>
  <Counter />
</div>

// after
<span>
  <Counter />
</span>

div태그가 span으로 바뀐다면 모든 state도 버려지고 다시 마운트가 된다.

 

DOM 엘리먼트 타입이 같은 경우

두 엘리먼트의 속성을 확인하여 변경된 속성만 갱신한다.

className, title, name, id 등 엘리먼트에서 변경된 속성만 찾아서 갱신한다.

style의 경우 style내에서 변경된 style속성만 갱신한다.

 

자식 엘리먼트 재귀처리

//마지막에 요소를 추가하는 경우
<ul>
  <li>first</li>
  <li>second</li>
</ul>

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

//첫번째에 요소를 추가하는 경우
<ul>
  <li>first</li>
  <li>second</li>
</ul>

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

마지막에 요소를 추가하는 경우를 먼저 보면,

react비교 알고리즘은 위에서부터 차례대로 요소를 비교한다.

따라서 first와 second까지는 변화가 없고, third가 추가되었다는 것을 발견했을 것이다.

 

그런데 첫번째에 요소를 추가한 경우, first와 zero가 다르니 처음부터 변화한 것이라 여긴다.

따라서 이런식으로 개발을 하면 아주 비효율적으로 렌더링 된다.

 

이를 해결하기 위해 리액트에서는 같은 타입의 형제 요소들을 쓸 때 key속성을 반드시 쓰라고 권장한다.

//key를 추가함
<ul>
  <li key="01">first</li>
  <li key="02">second</li>
</ul>

<ul>
  <li key="00">zero</li>
  <li key="01">first</li>
  <li key="02">second</li>
</ul>

이렇게 key를 쓰면 key를 기반으로 비교하기 때문에 zero만 추가되었다는걸 리액트가 알 수 있게 된다.

 

key는 형제끼리만 유일하면 된다.

key가 매번 렌더링 될 때마다 변화한다면 재생성되기 때문에 절대 불변하는 것이 좋다.

(index같은 걸로 key를 주면 변화할 수 있으므로 피해야한다.)

 

 

이를 기반으로 리액트 프로그래밍을 한다면 조금 더 효율적인 애플리케이션을 만들 수 있을 것이다.

반응형

'발표 스터디' 카테고리의 다른 글

내가 파이썬으로 코딩테스트를 보는 이유  (0) 2022.05.04
이벤트 – 버블링과 캡처링  (0) 2022.04.19
자바스크립트 Promise란  (0) 2022.04.19
JavaScript의 비동기  (0) 2022.04.18
자바스크립트의 함수  (0) 2022.04.04