
위에서 설명한 바와 같이, 리액트가 선언적 API를 제공하기 때문에 리액트의 사용자는 렌더링 작업을 함에 있어서 매번 무엇이 바뀌었는지를 걱정할 필요가 없을 것이다. 하지만, 내부에서는 기존의 Virtual DOM과 변경사항이 생긴 Virtual DOM 의 비교작업이 이루어지는데,이 과정을 Reconciliation(비교 조정, 재조정)이라고 한다.
컴포넌트에서 prop이나 state가 변경될 때, 직전에 렌더링된 요소(element)와 새로 반환된 요소를 비교하여 두 element가 일치하지 않으면 리액트는 새로운 요소로 DOM을 업데이트 하는데, 이러한 프로세스를 Reconciliation(비교 조정, 재조정) 이라고 한다.
실제로는 모든 DOM 트리를 순회하면서 탐색 및 변경하는 과정을 거쳐야 하는데, 지금까지 알려진 알고리즘은 O(n^3)의 시간복잡도를 가지므로 1000개의 요소를 표시하려면 무려 10억번의 비교가 필요하다. 따라서, 리액트에서는 이 대신 아래 두 가지 가정에 따른 휴리스틱 알고리즘을 채택하였다.
key prop을 이용해 다른 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 안정적인 자식 요소에 대한 힌트를 얻을 수 있다. // before
<div className="before" title="stuff" />
// after
<div className="after" title="stuff" /> 두 앨리먼트를 비교했을 떄, className만 변경되었으므로 동일한 속성은 유지하고 className만 변경해준다.// before
<div>
<Counter />
</div>
// after
<span>
<Counter />
</span> 바뀐 이후의 트리의 루트 엘리먼트는 div 에서 span태그로 바뀐 이 경우 새로운 트리를 구축하게 되는 것이다. 트리를 버릴 때 이전 DOM 노드들은 모두 삭제되며, 새로운 트리가 만들어 질 때 새로운 DOM 노드들이 DOM에 삽입된다. (이전의 state들은 모두 사라진다.)자식에 대한 재귀적인 처리 (key prop)
DOM 노드의 자식들을 재귀적으로 처리할 때, 리액트는 기본적으로 동시에 두 리스트를 순회하 고 차이점이 있으면 변경한다.
// before
<ul>
<li>first</li>
<li>second</li>
</ul>
// after
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
만약 위와 같은 트리 구조의 DOM이 바뀐다고 할 떄, first와 second를 비교하여 일치하는 것을 확인하고 마지막 요소에 third를 추가하는 경우 간단하게 구현할 수 있겠지만 만약, 첫번째 요소에 엘리먼트를 추가 할 경우 성능이 좋지 않을 것 이다.
첫번째 요소부터 일치하지 않으므로 리액트는 다른 타입으로 판단해 모든 자식을 변경한다. 사실은 한 요소만 추가된 것인데 전체 자식을 변경하게 되므로 성능적인 측면에서 심각한 낭비를 초래할 것이다.
위와 같은 상황을 해결하기 위해 리액트에서는 key 속성을 지원하여 다음과 같이 구현한다.
// before
<ul>
<li key="first">first</li>
<li key="second">second</li>
</ul>
// after
<ul>
<li key="zero">zero</li>
<li key="first">first</li>
<li key="second">second</li>
</ul>
자식들이 key를 가지고 있다면, 리액트는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.
first와 second라는 key를 가진 li요소는 이미 있으므로 변경하지 않고, zero라는 key를 가진 li 요소만 맨 위에 추가하는 것으로 해결할 수 있다!
컴포넌트 인스턴스는
key를 기반으로 갱신되고 재사용되므로 요소를 인덱스 대신 유일하게 식별 가능한 요소로 두는 것이 바람직하다. key 값으로 인덱스를 사용했을 경우 항목의 순서가 바뀌었을 때 재배열되어 컴포넌트의 순서가 엉망이 될 수 있기 때문이다. (key는 형제 내에서만 유일하면 된다.)