재조정 (Reconciliation)
리액트 Virtual DOM은 수정사항이 먼저 적용 되고 실제 DOM과 차이점을 비교해서 수정사항을 실제 DOM에 적용한다.
DOM은 트리구조이기 때문에 변경된 부분을 감지하기 위해서는 트리 구조를 비교해야 한다. 일반적인 트리 구조의 비교는 O(n^3)의 시간요소되는 것으로 알려져 있어서 비효율적이지만, React는 이를 Heuristic한 방식을 사용해서 O(n)의 시간에 해결하고 있다.
‘비교는 필요한 부분만 비교하자’
- 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어 낸다.
- 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.
비교알고리즘 (Diffing Algorithm)
두 개의 트리를 비교할 때, React는 두 엘리먼트의 루트(root) 엘리먼트부터 비교한다. 이후의 동작은 루트 엘리먼트의 타입에 따라 달라진다.
엘리먼트의 타입이 다를 경우
두 루트 엘리먼트의 타입이 다르면, React는 이전 트리를 버리고 완전히 새로운 트리를 구축한다. 모두 트리 전체를 재구축한다.
트리를 버릴 때 이전 DOM노드들은 모두 파괴된다. 컴포넌트 인스턴스는 componentWillUnmount()가 실행된다. 새로운 트리가 만들어질 때, 새로운 DOM노드들이 DOM에 삽입된다.
루트 엘리먼트 아래의 모든 컴포넌트도 언마운트되고, 그 state도 사라진다.
DOM 엘리먼트의 타입이 같은 경우
같은 타입의 두 React DOM엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신한다.
<div className="before" title="stuff" />
<div className="after" title="stuff" />
이 두 엘리먼트를 비교하면, React는 현재 DOM 노드 상에 className만 수정한다.
DOM노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리한다.
같은 타입의 컴포넌트 엘리먼트
컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지된다. React는 새로운 엘리먼트의 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 props를 갱신한다.
자식에 대한 재귀적 처리
DOM노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성한다.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React는 두 트리에서 변경은 잘 되겠지만 리스트의 맨 앞에 엘리먼트를 추가하는 경우 성능이 좋지 않을 수 있다. 예를 들어, 아래의 두 트리 변환은 안좋게 작동한다.
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
종속 트리를 그대로 유지하는 대신 모든 자식을 변경한다.
keys
React는 key 속성을 지원한다. 자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.
배열 map 함수를 적용할 때, 각 요소마다 고유 key값을 할당하는 이유는
- React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다.
- 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.
즉 key는 그 값이 변하지 않는 유일한 식별자의 역할을 가진다. 고유성 부여를 위해 key 값은 배열의 각 항목 간 서로를 식별할 수 있게 하는 문자열을 사용하는 것이 좋다.
key는 엘리먼트의 변화를 감지한다.
key가 없다면 리스트를 순차적으로 비교, key가 존재할 때는 어떤 요소만 변화가 있는지 빠르게 찾아 낼 수 있다. key를 사용하면 기존의 요소는 리렌더링하지 않고 변화가 감지된 요소만 리렌더링하여 효율적인 DOM 사용 가능
key는 변하지 않고, 예상가능하며, 유일해야한다. Math.random을 사용시 많은 컴포넌트 인스턴스와 DOM노드를 불필요하게 재생성하여 성능이 나빠지거나 자식 컴포넌트의 state 가 유실 될 수 있다.
그렇다면 index를 넣으면 안되는 이유는?
index 사용시 재배열되기 때문에 비효율적으로 동작할 것이다. 인덱스를 key로 사용 중 배열이 재배열되면 컴포넌트의 state와 관련된 문제가 발생할 수 있다. 컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용된다. 인덱스를 key로 사용하면, 항목의 순서가 바뀌었을때 key또한 바뀔 것이다. 그 결과, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수도 있다.
참조사이트