React는 기존의 가상 DOM 트리와 새롭게 변경된 가상 DOM 트리를 비교할 때, 트리의 레벨 순서대로 순회하는 방식으로 탐색한다. 즉 같은 레벨(위치)끼리 비교한다는 뜻이며, 이는 너비 우선 탐색(BFS)의 일종이라고 볼 수 있다.
React는 이런 식으로 동일 선상에 있는 노드를 파악한 뒤 다음 자식 세대의 노드를 순차적으로 파악해 나간다.
DOM 트리는 각 HTML 태그마다 각각의 규칙이 있어 그 아래 들어가는 자식 태그가 한정적이라는 특징이 있다. (예를 들어 <ul>
태그 밑에는 <li>
태그만 와야 한다던가, <p>
태그 안에 <p>
태그를 또 쓰지 못하는 것이다.)
자식 태그의 부모 태그 또한 정해져 있다는 특징이 있기 때문에, 부모 태그가 달라진다면 React는 이전 트리를 버리고 새로운 트리를 구축한다.
<div>
<Counter />
</div>
//부모 태그가 div에서 span으로 바뀐다.
<span>
<Counter />
</span>
이렇게 부모 태그가 바뀌어버리면, React는 기존의 트리를 버리고 새로운 트리를 구축하기 때문에 이전의 DOM 노드들은 전부 파괴된다. 부모 노드였던 <div>
가 <span>
으로 바뀌어버리면 자식 노드인 <Counter />
는 완전히 해제된다.
즉 이전 <div>
태그 속 <Counter />
는 파괴되고 <span>
태그 속 새로운 <Counter />
가 다시 실행된다. 새로운 컴포넌트가 실행되면서 기존의 컴포넌트는 완전히 해제(Unmount)되어버리기 때문에 <Counter />
가 가지고 있던 기존의 state 또한 파괴된다.
반대로 타입이 바뀌지 않는다면 React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이트한다. 이것이 가능한 이유는 앞서 React가 실제 DOM이 아닌 가상 DOM을 사용해 조작하기 때문이다.
업데이트 할 내용이 생기면 virtual DOM 내부의 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면 그때 단 한번 실제 DOM으로의 렌더링을 시도한다.
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는 뒤이어서 해당 노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때마다 변경한다. 이를 재귀적으로 처리한다고 표현한다.
React는 기존 <ul>
과 새로운 <ul>
을 비교할 때 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐 점을 찾는다. 그렇기 때문에 예상대로 React는 첫 번째 자식 노드들과 두 번째 자식 노드들이 일치하는 걸 확인한 뒤 세 번째 자식 노드를 추가한다.
이렇게 React는 위에서 아래로 순차적으로 비교하기 때문에, 이 동작 방식에 대해 고민하지 않고 리스트의 처음에 엘리먼트를 삽입하게 되면 이전의 코드에 비해 훨씬 나쁜 성능을 내게 된다.
<!-- 나쁜 예 -->
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
//자식 엘리먼트를 처음에 추가한다.
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
<!-- 올바른 예 -->
<ul>
<li>first</li>
<li>second</li>
</ul>
//자식 엘리먼트의 끝에 새로운 자식 엘리먼트를 추가했다.
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
처음의 자식 노드를 비교할 때, <li>Duke</li>
와 <li>Connecticut</li>
로 자식 노드가 서로 다르다고 인지하게 된 React는 리스트 전체가 바뀌었다고 받아들인다. 즉 <li>Duke</li>
와 <li>Villanova</li>
는 그대로기 때문에 두 자식 노드는 유지시켜도 된다는 것을 깨닫지 못하고 전부 버리고 새롭게 렌더링 해버리기 때문에 이는 굉장히 비효율적인 동작 방식이다.
그래서 React는 이 문제를 해결하기 위해 key
라는 속성을 지원한다. 이는 효율적으로 가상 DOM을 조작할 수 있도록 하며, key
속성을 사용하지 않으면 React 에서 key값을 달라고 경고문을 띄우는 것도 이 때문이다. key값이 없는 노드는 비효율적으로 동작할 수 있기 때문이다.