React의 Virtual Dom은 트리를 어떻게 비교하는가?

IT공부중·2021년 3월 27일
1

React

목록 보기
9/10

React의 V-DOM은 이전 V-DOM과 현재 V-DOM을 비교해서 바뀐 부분만 변경함으로써 최적화가 이루어진다. 그렇다면 React는 어떠한 방식으로 V-DOM 이 바뀌었는지 아는걸까?

React는 Reconciliation 과정을 거친다. 일반적인 트리 비교 알고리즘으로는 O(n^3)의 시간복잡도를 가지는데에 비해 React는 두가지 가정을 기반하여 O(n) 복잡도의 휴리스틱 알고리즘을 구현했다.

비교 알고리즘. 두 엘리먼트의 루트 엘리먼트 부터 비교.

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

비교 알고리즘

두개의 트리를 비교할 때 React는 루트 엘리먼트부터 비교한다.

엘리먼트 타입이 다른 경우 → 모두 트리 전체를 재구축 한다.

a → img 로 Article 에서 Comment로, Button 에서 div로 등 모두 트리 전체를 재구축 하는 경우이다.

트리를 버릴 때 이전 DOM 노드들은 모두 파괴되고 componentWillUnmount가 실행된다.

새로운 트리가 만들어질 때, 새로운 Dom 노드들이 DOM에 삽입되고 ComponentDidMount가 실행된다.

DOM 엘리먼트의 타입이 같을 경우

같은 타입의 React DOM 엘리먼트를 비교할 떄, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신한다.

className이 변경되면 그것만 변경하고 style 안이 변경되도 그것만 변경한다.

DOM 노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리한다.

같은 타입의 컴포넌트 엘리먼트

컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지된다. React는 새로운 엘리먼트 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 props를 갱신. componentDidUpdate를 호출.

자식에 대한 재귀적 처리

DOM노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성.

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

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

이렇게 바뀌면 문제없이 잘 동작한다.

하지만 위와 같이 단순하게 구현하면, 리스트의 맨 앞에 엘리먼트를 추가하는 경우 성능이 좋지 않다. 예를 들어, 아래의 두 트리 변환은 형편없이 동작한다.

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

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

Duke 와 Villanova 의 순서가 바뀌면서 모든 list가 다 새로 생성될 것이다.

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가 2014인 엘리먼트가 추가되었고, 2015와 2016 key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다. 실제로, key를 사용할 값을 정하는 것은 어렵지 않다. 엘리먼트는 일반적으로 식발자를 가지고 있을 것이고, 그 데이터를 key로 사용할 수 있다.

만약 식별자를 가지고 있지 않다면 데이터 구조에 ID를 추가하거나 데이터 일부에 해시를 적용해서 key를 생성할 수 있다.(데이터 일부의 값이 겹치면 해시도 같아짐으로 주의.) 해당 key는 오로지 형제사이에서만 유일하면 된다.

최후의 수단으로 배열의 인덱스를 key로 사용할 수 있다. 만약 항목들이 재배열 되지 않는다면 이방법도 잘 동작할 것이지만, 재배열 되는 경우 비효율적으로 동작한다. 중간에 하나가 빠지거나, 추가가 되는 경우에 index가 바뀌면서 key가 바뀌게 될 것이고, key가 바뀌었기 때문에 React는 다른 component로 인식하고, 기존 component를 componentWillUnmount를 시키고 새롭게 componentDidMount를 할 것이다.

참고 내용
https://ko.reactjs.org/docs/reconciliation.html

profile
4년차 프론트엔드 개발자 문건우입니다.

0개의 댓글