재조정 (Reconciliation)
동기
React를 사용하다 보면, ’render()
함수는 React 엘리먼트 트리를 만드는 것이다.‘라고 생각이 드는 순간이 있을 것입니다. state
나 props
가 갱신되면 render()
함수는 새로운 React 엘리먼트 트리를 반환할 것입니다. 이때 React는 방금 만들어진 트리에 맞게 가장 효과적으로 UI를 갱신하는 방법을 알아낼 필요가 있습니다.
하나의 트리를 가지고 다른 트리로 변환하기 위한 최소한의 연산 수를 구하는 알고리즘 문제를 풀기 위한 일반적인 해결책들이 있습니다. 하지만 이러한 최첨단의 알고리즘도 n개의 엘리먼트가 있는 트리에 대해 O(n3)의 복잡도를 가집니다.
React에 이 알고 리즘을 적용한다면, 1000개의 엘리먼트를 그리기 위해 10억 번의 비교 연산을 수행해야 합니다. 너무나도 비싼 연산이죠. React는 대신, 두 가지 가정을 기반하여 O(n) 복잡도의 휴리스틱 알고리즘을 구현했습니다.
- 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
- 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.
비교 알고리즘 (Diffing Algorithm)
두 개의 트리를 비교할 때, React는 두 엘리먼트의 루트(root) 엘리먼트부터 비교합니다. 이후의 동작은 루트 엘리먼트의 타입에 따라 달라집니다.
엘리먼트의 타입이 다른 경우
두 루트 엘리먼트의 타입이 다르면, React는 이전 트리를 버리고 완전히 새로운 트리를 구축합니다.
<a>
에서 <img>
로, <Article>
에서 <Comment>
로, 혹은 <Button>
에서 <div>
로 바뀌는 것 모두 트리 전체를 재구축하는 경우입니다.
트리를 버릴 때 이전 DOM 노드들은 모두 파괴됩니다. 컴포넌트 인스턴스는 componentWillUnmount()
가 실행됩니다. 새로운 트리가 만들어질 때, 새로운 DOM 노드들이 DOM에 삽입됩니다. 그에 따라 컴포넌트 인스턴스는 UNSAFE_componentWillMount()
가 실행되고 componentDidMount()
가 이어서 실행됩니다. 이전 트리와 연관된 모든 state
는 사라집니다.
루트 엘리먼트 아래의 모든 컴포넌트도 언마운트되고 그 state
도 사라집니다. 예를 들어, 아래와 같은 비교가 일어나면,
<div>
<Counter />
</div>
<span>
<Counter />
</span>
이전 Counter는 사라지고, 새로 다시 마운트가 될 것입니다.
DOM 엘리먼트의 타입이 같은 경우
같은 타입의 두 React DOM 엘리먼트를 비교할 때, React는 두 엘리먼트의 속성을 확인하여, 동일한 내역은 유지하고 변경된 속성들만 갱신합니다. 예를 들어,
<div className="before" title="stuff" />
<div className="after" title="stuff" />
이 두 엘리먼트를 비교하면, React는 현재 DOM 노드 상에 className
만 수정합니다.
style
이 갱신될 때, React는 또한 변경된 속성만을 갱신합니다. 예를 들어,
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
위 두 엘리먼트 사이에서 변경될 때, React는 fontWeight
는 수정하지 않고 color
속성 만을 수정합니다.
DOM 노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리합니다.
같은 타입의 컴포넌트 엘리먼트
컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지됩니다. React는 새로운 엘리먼트의 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 props
를 갱신합니다. 이때 해당 인스턴스의 UNSAFE_componentWillReceiveProps()
, UNSAFE_componentWillUpdate()
, componentDidUpdate
를 호출합니다.
다음으로 render()
메서드가 호출되고 비교 알고리즘이 이전 결과와 새로운 결과를 재귀적으로 처리합니다.