Reconciliation
동기
- state나 props가 갱신되면
render()
함수가 새로운 React element 트리를 반환함
- 이전 트리를 새 트리로 변환하기 위해 필요한 최소한의 연산 수를 구하는 알고리즘이 필요함
- React는 아래의 두 가정을 기반으로 O(N) 복잡도틀 가지는 heuristic 알고리즘을 구현함
- 서로 다른 타입의 두 element는 서로 다른 트리를 만들어냄
key
prop을 통해서, 여러 렌더링 사이에 어떤 자식 element가 변경되지 않는지 알 수 있음
비교 알고리즘 (Diffing Algorithm)
- 두 개의 트리를 비교할 때, 두 element의 root element부터 비교함
- root element의 타입에 따라 이후 동작이 결정됨
- root element의 타입이 다를 경우, 이전 트리를 버리고 새로운 트리를 만듬
- root element의 타입이 같을 경우, 이전 트리에서 변경된 속성만 갱신함
- DOM 노드의 처리가 끝나면, React는 이어서 해당 노드의 자식들을 재귀적으로 처리함
DOM element의 타입이 다른 경우
-
DOM element의 타입이 다르면, React는 이전 트리를 버리고 새로운 트리를 구축함
- 예)
<a> -> <img>
, <article> -> <comment>
, <button> -> <div>
등 element의 타입이 바뀌면 트리 전체를 재구축함
-
트리를 버릴 때 이전 DOM 노드들은 모두 파괴됨
- 컴포넌트 인스턴스는
componentWillUnmout()
가 실행됨
-
새로운 트리가 만들어질 때, 새로운 DOM 노드들이 DOM에 삽입됨
- 컴포넌트 인스턴스는
UNSAFE_componentWillMount()
가 실행되고, componentDidMount()
가 이어서 실행됨
-
이전 트리와 연관된 모든 state는 사라짐
DOM element의 타입이 같은 경우
- 같은 타입의 두 React DOM element를 비교할 때, React는 두 element의 attributes를 확인하여 동일한 attribute은 유지하고 변경된 attribute만 갱신함
- 아래 코드에서, React는 현재 DOM 노드 상에 className만 수정함
<div className="before" title="stuff" />
<div className="after" title="stuff" />
- 또한
style
이 갱신될 때, React는 변경된 속성만 갱신함
- 아래 코드에서, React는 fontWeight 속성은 수정하지 않고 color 속성만 수정함
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
컴포넌트가 갱신될 경우
- 컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지됨
- React는 새로운 element의 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 prop을 갱신함
- 이 때 해당 인스턴스의
UNSAFE_componentWillReceiveProps()
, UNSAFE_componentWillUpdate()
, componentDidUpdate
를 호출함
- 다음에
render()
메서드가 호출되고 비교 알고리즘이 이전 결과와 새로운 결과를 비교하여 재귀적으로 처리함
자식에 대한 재귀적 처리
- DOM 노드의 자식들을 재귀적으로 처리할 때, React는 두 리스트를 동시에 순회하고 차이점이 있으면 변경을 생성함
- 자식 맨 끝에 element를 추가하면, 두 트리 사이의 변경은 마지막에만 하면 됨
- 자식 맨 앞에 element를 추가하면, 종속 트리는 유지되지만 모든 자식을 변경하므로 비효율적임
// 자식 맨 끝에 element를 추가하는 경우
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
// 자식 맨 앞에 element를 추가하는 경우
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
Keys
- 이러한 문제를 해결하기 위해, React는
key
attribute를 지원함
- 자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리를 의 자식들이 일치하는지 확인함
- 아래 코드에서 React는 '2014' key를 가진 element가 새로 추가되었고, '2015'와 '2016' key를 가진 element를 이동하기만 하면 되는 걸 알 수 있음
<ul>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
<ul>
<li key="2014">Connecticut</li>
<li key="2015">Duke</li>
<li key="2016">Villanova</li>
</ul>
- key는 보통 element가 가지고 있는 식별자나 id를 사용하거나 데이터의 일부에 해쉬를 적용하여 생성해서 사용함
- key는 형제 사이에서만 유일하면 되고, 전역에서 유일할 필요는 없음
- 최후의 수단으로 배열의 인덱스를 key로 사용할 수 있지만, 배열이 재배열되는 경우 비효율적으로 동작할 것임
- 인덱스를 key로 사용하면 항목의 순서가 바뀌었을 때 key도 바뀜
- 그 결과, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수 있음
고려 사항
- React는 heuristic 알고리즘에 의존하고 있기 때문에, heuristic 알고리즘이 기반하고 있는 가정에 부합하지 않는 경우 성능이 나빠질 수 있음
- 알고리즘은 다른 컴포넌트 타입을 갖고 있는 종속 트리들의 일치 관계를 확인하지 않음
- 매우 비슷한 결과물을 출력하는 두 컴포넌트를 교체하는 경우, 그 둘을 같은 타입으로 만드는 것이 효율적임
key
는 반드시 변하지 않고, 예상 가능하며, 유일해야 함
- 변하는
key
(예: Math.random())를 사용하는 경우
- 많은 컴포넌트 인스턴스와 DOM 노드를 불필요하게 재생성하여 성능이 나빠질 수 있음
- 자식 컴포넌트의 state가 유실될 수 있음
Fiber Reconciler
기존 Reconciler (React 15)의 약점
- 기존 reconciliation(stack reconciler)은 재귀적으로 일어나기 때문에, 중간에 함수 호출을 끊을 수 없음
- React element를 전부 순회할 때까지 자바스크립트의 엔진을 독차지함
- 이러한 상황에서 애니메이션이 비동기로 동작하면, 애니메이션이 재때 실행되지 못해서 frame이 누락될 수 있음
- 따라서 stack reconciler는 애니메이션에 약함
- 데이터에 의한 UI 변경보다 애니메이션에 의한 UI 변경을 우선시할 수 없으므로
React Fiber의 등장
- Fiber
- fiber는 한 가상 stack frame을 차지하는 작업의 단위임
- fiber는 재귀 대신 linked list를 사용함
- 한 fiber의 작업이 끝나면, 링크를 타고 가서 다음 fiber의 작업을 하면 됨
- React의 element와 React의 fiber node는 1:1로 대응됨
- 한 element를 렌더링하는 것을 한 작업 단위인 fiber로 매핑함
- fiber의 종류는 current fiber와 in progress fiber가 있음
- current fiber : 이미 렌더링된 것을 나타냄
- in progress fiber : 현재 stack frame을 차지하며 렌더링이 진행 중인 fiber를 나타냄
Fiber Reconciler (React 16)
- Fiber reconciler
- Stack reconciler의 완전히 하위 호환되는 버전임
- DOM 트리의 노드를 나타내는데 사용되는
fiber
로부터 이름이 유래됨
- 주요 목적은 증가하는 렌더링, UI 애니메이션의 부드러운 렌더링, user interaction과의 반응성임
- 일을 여러개의 덩어리로 나눠서 여러 개의 frame에 나눠서 렌더링할 수 있게 함
- 단위별 일에 우선순위를 정의하고, 일을 정지, 재사용, 중지할 수 있게 함
참고