Reconcilation?
React를 사용할 때, `render() 함수는 React 엘리먼트 트리를 만드는 것이다.' 라고 생각할 수 있다.
state나 props가 갱신되면 render() 함수는 새로운 React 엘리먼트 트리를 반환한다.
이때 React가 가장 효과적으로 UI를 갱신해서 트리를 만드는지 알아보자!
여기서 사용된 것이 Reconciliation (재조정) 알고리즘이다.
하나의 트리를 가지고 다른 트리로 변환하기 위한 최소한의 연산 수를 구하는 알고리즘 문제를 풀기 위한
일반적인 해결책들이 있다. 하지만 이런 알고리즘도 n개의 엘리먼트가 있는 트리에 대해 O(n3)의 복잡도를 가진다.
1000개의 엘리먼트를 그리기 위해서 10억 번의 비교 연산을 수행해야 한다는 뜻이다.
그래서 React는 두 가지 가정을 기반으로 O(n) 복잡도의 휴리스틱 알고리즘을 구현했다.
1. 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
2. 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할 지 표시해 줄 수 있다.
비교 알고리즘 (Diffing Algorithm)
두 개의 트리를 비교할 때, React는 루트(root) 엘리먼트부터 비교한다. 그후 루트 엘리먼트의 타입에 따라 달라진다.
엘리먼트 타입이 다른 경우
루트 엘리먼트의 타입이 다르다면, React는 이전 트리를 버리고 완전히 새로운 트리를 구축한다.
트리를 버릴 때 이전 DOM 노드들은 모두 파괴된다. 컴포넌트 인스턴스는 componentWillUnmount()가 실행된다.
새로운 트리가 만들어질 때, 새로운 DOM 노드들이 DOM에 삽입된다.
그에 따라 컴포넌트 인스턴스는 UNSAFE_componentWillMount()가 실행되고 componentDidMount()가 이어 실행된다.
이전 트리와 관련된 모든 state도 사라진다.
<div>
<Counter />
</div>
<span>
<Counter />
</span>
<div> 태그가 <span> 태그로 바뀌었다면 <Counter>는 사라지고, 다시 마운트가 된다.
이처럼 루트 엘리먼트 아래의 모든 컴포넌트도 언마운트되고 그 state도 사라진다.
※ UNSAFE_componentWillMount ?
레거시이며, 새로 작성하는 코드에서는 componentWillMount를 사용하면 된다.
엘리먼트 타입이 같은 경우
루트 엘리먼트의 타입이 같다면, 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() 메서드가 호출되고 비교 알고리즘이 이전 결과와 새로운 결과를 재귀적으로 처리한다.
※ UNSAFE_componentWillReceiveProps(), UNSAFE_componentWillUpdate() ?
레거시이며, 새로 작성하는 코드에서는 componentWillReceiveProps(), componentWillUpdate()를 사용하면 된다.
자식에 대한 재귀적 처리
DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면
변경을 생성한다.
예를들어, 자식의 끝에 엘리먼트를 추가하면, 두 트리 사이의 변경은 잘 적용될 것이다.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React는 두 트리에서 <li>first</li>가 일치하는 것을 확인하고, <li>second</li>가 일치하는 것을 확인한다.
그리고 마지막으로 <li>third</li>를 트리에 추가한다.
하지만 단순하게 위와 같이만 구현하면, 리스트의 맨 앞에 엘리먼트를 추가하는 경우 성능이 좋지않다.
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
<li>Duke</li>와 <li>Villanova</li> 종속 트리를 그대로 유지하지 않고 모든 자식을 변경한다.
이런 비효율은 문제가 될 수 있다고 판단해서 Key 속성을 사용하였다.
Keys
자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.
<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를 추가하였는데, 이제 React는 '2014' key를 가진 엘리먼트를 새로 추가하고,
'2015'와 '2016' key를 가진 엘리먼트는 이동만 하면 된다.
'React > 패스트캠퍼스' 카테고리의 다른 글
[React - 기초] PropTypes (2) | 2022.03.19 |
---|---|
[React - 기초] Render Props (1) | 2022.03.18 |
[React - 기초] Portals (2) | 2022.03.17 |
[React - 기초] Memoization (2) | 2022.03.15 |
[React - 기초] Hooks (0) | 2022.03.10 |