[React] virtual DOM & Reconciliation

young·2022년 7월 27일
0

7/21~8/18 Section 4 TIL

목록 보기
4/22

Before Learn...

DOM: Document Object Model = 문서 객체 모델
브라우저가 트리 구조로 만든 객체 모델

DOM은 트리구조이며, 트리는 데이터 저장의 의미보다 저장된 데이터를 빠르게 탐색할 수 있는 장점이 있다.

DOM 객체가 트리구조로 되어있기 때문에 JS는 쉽게 DOM 객체에 접근 및 탐색할 수 있고, DOM 객체를 조작할 수 있게 된다.

프로그래밍 언어로 조작하는 DOM은 UI 상태가 변경될 때마다 업데이트 된다.
잦은 DOM 조작은 성능에 영향을 미치게 된다.
DOM의 렌더링은 브라우저의 구동 능력에 의존하기 때문에 DOM 조작 속도가 느려진다.


✅ TIL

  • React Virtual DOM
  • React Reconciliation (Diffing Algorithm)

1️⃣ Virtual DOM

Virtual DOM은 Real DOM의 "가벼운" 사본과 같다.

리액트는 실제 DOM 객체에 접근하여 조작하는 대신,
가상의 DOM 객체에 접근하여 변화 전과 변화 후를 비교하고 바뀐 부분만 적용한다.

Virtual DOM의 등장 배경

DOM이 변경되고 업데이트 된다는 것은 브라우저의 렌더링 엔진이 Reflow한다는 것을 의미한다.
DOM 트리를 재구축함으로써 리랜더링 과정을 거쳐 UI를 업데이트 한다.
브라우저의 Reflow와 Repaint 과정은 레이아웃 및 페인트에 해당하는 재연산을 해야하기 때문에 속도가 그만큼 느려진다.

따라서 JS로 조작하는 DOM의 요소가 많을수록 모든 DOM 업데이트에 대하여 리플로우를 해야 하므로 DOM의 업데이트에 대한 비용이 많이 들게 된다.

대부분의 JS 프레임워크는 조작하는 DOM 요소 1개만을 업데이트하지 않고, 전체 DOM 요소를 업데이트 시킨다.
-> 비효율적인 업데이트를 반복한다.
-> 바뀐 부분만 비교해서 그 부분만 렌더링할 수 있는 Virtual DOM이 탄생했다.

Virtual DOM이란

React에는 모든 DOM 객체에 대응하는 가상의 DOM 객체가 있다.
가상 DOM 객체는 실제 DOM 객체와 동일한 속성을 가지고 있음에도 훨씬 가볍다.

가상 DOM은 가상의 UI요소를 메모리에 유지시키고,
유지시킨 가상의 UI 요소를 ReactDOM과 같은 라이브러리를 통해 실제 DOM과 동기화시킨다.

실제 DOM을 조작하는 것은 실제로 브라우저 화면에 그리기 때문에 느리지만,
가상 DOM을 조작하는 것은 실제 DOM처럼 브라우저 화면에 그리는 것이 아니기 때문에 훨씬 속도가 빠르다.

  1. React는 새로운 요소가 UI에 추가되면 가상의 DOM이 만들어진다.
  2. 상태가 변경되면 다시 새로운 DOM 트리가 만들어진다.
  3. 이전의 가상의 DOM과 이후의 가상의 DOM의 차이를 비교한다.
  4. 트리가 업데이트된 UI를 제공하기 위해 부분적으로 리렌더링된다.
  5. 업데이트된 트리는 실제 DOM으로 한꺼번에 업데이트 된다.
    실제 DOM이 최소한의 작업으로 렌더링을 할 수 있게 된다.

2️⃣ 재조정 (Reconciliation)

"컴포넌트의 state나 props가 변경되면 React는 새로 반환된 컴포넌트를 이전에 렌더링된 컴포넌트와 비교하여 실제 DOM을 업데이트 해야하는지 결정합니다. 두 컴포넌트가 동일하지 않다면, React는 DOM을 업데이트 합니다. 이 과정을 재조정(Reconciliation)이라고 합니다."

React가 기존의 가상 DOM과 변경된 가상 DOM을 비교할 때,
새로운 가상 DOM 트리에 부합하도록 기존의 UI를 효율적으로 갱신하는 방식이 필요했다.

일반적인 하나의 트리를 다른 트리로 변형시키기 위해 최소한의 연산 수를 구하는 알고리즘을 그대로 적용한다면 너무 비싼 연산을 수행하게 된다.

따라서 React는 2가지 가정을 기반하여 시간 복잡도 O(n)의 새로운 휴리스틱 알고리즘을 구현해냈다.

  1. 서로 다른 타입의 두 요소는 서로 다른 트리를 만들어낸다.
  2. 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

📌 비교 알고리즘 (Diffing Algorithm)

기존의 가상 DOM과 변경된 가상 DOM 트리를 비교할 때, 트리의 레벨 순서대로 순회하는 방식으로 탐색한다. (같은 레벨끼리 비교하는 BFS의 일종)

즉, 두 개의 트리를 비교할 때, 두 엘리먼트의 root 엘리먼트부터 비교한다.

업데이트 할 내용이 생기면 virtual DOM 내부의 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면 그때 단 한 번 실제 DOM으로의 렌더링을 시도한다.

1️⃣ 비교 중에 다른 타입의 DOM 엘리먼트를 찾을 경우

React는 이전 트리를 버리고 완전히 새로운 트리를 구축한다.

즉, 부모 태그가 바뀌면 새로운 트리를 구축하기 때문에 이전의 DOM 노드들은 전부 파괴된다.

자식 노드(컴포넌트)가 완전히 해제(Unmount)되면서 이전 트리와 연관된 모든 state 또한 파괴된다.


2️⃣ 반대로 DOM 엘리먼트의 타입이 바뀌지 않을 경우

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

하나의 DOM 노드를 처리한 뒤, 해당 노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때마다 변경한다.

이를 재귀적으로 처리한다고 표현한다.

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

React는 자식 노드를 위에서 아래로 순차적으로 비교한다.

따라서 이것을 고려하지 않고 첫번째 자식 노드에 변경 사항을 적용한다면, 트리 변환 성능이 좋지 않게 된다.

React는 자식 노드 전체가 바뀌었다고 받아들이기 때문에 변경되지 않은 노드마저 전부 버리고 새롭게 렌더링한다. ➞ 굉장히 비효율적인 방식

React는 이 문제를 해결하기 위해 key 속성을 지원한다.
key 값이 없는 노드는 비효율적으로 동작할 수 있다.

Key

자식 노드들이 key를 가지고 있다면, key를 이용해 기존 트리의 자식과 새로운 트리의 자식이 일치하는지 비교할 수 있다.

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

위와 같은 상황에서 React가 자식 노드들의 key를 비교한다면,
기존의 엘리먼트가 위치만 이동했다는 걸 알게 되어 이것을 버리지 않고 추가된 엘리먼트만 변경할 수 있다.

최후의 수단으로 배열의 인덱스를 key값으로 설정한다면,
배열의 요소가 다르게 정렬되어도 인덱스는 그대로 유지되기 때문에 key의 존재를 활용하지 못하는 상황이 된다.
일부가 아닌 배열 전체가 바뀌었다고 받아들여 새로운 DOM 트리를 구축해버리기 때문이다.


공식문서를 공부한 글입니다.

https://ko.reactjs.org/docs/reconciliation.html

profile
즐겁게 공부하고 꾸준히 기록하는 나의 프론트엔드 공부일지

0개의 댓글