React Diffing Algorithm

e-pong:)·2022년 11월 25일
0

React Diffing Algorithm

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

React는 많은 연산을 대신하여 시간복잡도 O(n)의 새로운 휴리스틱 알고리즘(Heuristic Algorithm)을 구현해냅니다.

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

여기서 React는 비교 알고리즘(Diffing Algorithm)을 사용한다.

React가 DOM 트리를 탐색하는 방법

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

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

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

DOM 트리는 HTML 태그마다 각각의 규칙이 있어서 아래로 들어가는 자식 태그가 한정적이라는 특징을 가지고 있다.
( 예를들어 <ul> 태그 밑에는 <li>태그가 들어오거나, <p>태그안에 <p>태그를 또 쓰지 못한다는 것 )
자식 태그의 부모 태그 또한 정해져있다는 특정 때문에 부모태그가 달라지면 React는 이전 트리를 버리고 새로운 트리를 구축한다.

  <div>
      <Counter />
  </div>

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

이렇게 부모 태그가 바뀌어버리면, React는 기존의 트리를 버리고 새로운 트리를 구축하기 때문에 이전의 DOM 노드들은 전부 파괴됩니다.

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

반대로 타입이 바뀌지 않는다면 React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이트한다.
이것이 가능한 이유는 React가 실제 DOM이 아닌 가상 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>Duke</li>
  <li>Villanova</li>
</ul>

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

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

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

개발할 당시 key라는 속성을 사용하지 않으면 React 에서 key값을 달라고 경고문을 띄우는 것도 이 때문이다. key 값이 없는 노드는 비효율적으로 동작할 수 있기 때문이다.

key

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

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

배열의 인덱스로 key 값을 사용하면 안되는 이유
배열이 다르게 정렬될 경우 비효율적으로 동작한다.
왜냐하면 배열이 다르게 정렬되어도 인덱스는 그대로 유지되기 때문에 인덱스는 그대로지만 그 요소가 바뀌게 된다면 React는 배열의 전체가 바뀌었다고 받아들일 것이고, 기존의 DOM 트리를 버리고 새로운 DOM 트리를 구축해버리기 대문에 비효율적으로 동작한다고 할 수 있다.

profile
말에 힘이 있는 사람이 되기 위해 하루하루, 성장합니다.

0개의 댓글