Virtual DOM
리액트에 있어서 reconciliation 의 개념을 이해하려면, 버츄얼 돔의 이해가 필요합니다. Virtual DOM에 대해서는 간단하게만 설명하고 넘어가겠습니다. 이 동영상 은 VDOM을 직관적으로 이해하는데 많은 도움을 주고, React 공식문서 VDOM 을 참고하셔도 좋습니다. 사실 VDOM은 워낙 유명하고 중요한 개념이기 때문에 검색을 통해 쉽게 레퍼런스를 찾으실 수 있을 겁니다. 😀
DOM 트리는 무겁습니다. DOM 트리를 전부 다시 그리는 것은, 브라우저 렌더링에 있어서 매우 큰 코스트를 잡아먹죠. 그래서 리액트는 메모리에 VDOM 이라는 가상의 돔 트리를 존재시키면서, 실제 DOM 트리를 렌더링 하는데에 있어서는, VDOM을 바탕으로 변경시킬 일부 부분만을 리렌더링하게끔 하는 방식이 가능하게 되죠.
이러한 방식을 통해 리액트는 선언적 API 가 가능하게 됩니다. 즉, 우리가 리액트에게 원하는 UI의 상태를 알려주면(선언적), 그 상태를 기반으로 DOM은 자동적으로 업데이트가 되는 것입니다. 예를들어 우리가 JSX 문법을 기반으로 컴퍼넌트를 작성만 하면, 렌더링은 React에서 알아서 다 처리해주는 것 처럼요.
Reconciliation(재조정)
앞서서 말했듯이, 리액트가 선언적 API를 제공하기 때문에 리액트의 사용자는 렌더링 작업을 함에 있어서 매번 무엇이 바뀌었는지를 걱정할 필요가 없습니다. 하지만 내부에서는 기존의 VDOM과 변경사항이 생긴 VDOM 의 비교작업이 이루어지겠죠? 이 과정을 Reconciliation(재조정) 이라고 말합니다.
이 과정을 거치려면, 실제로는 모든 DOM 트리를 순회하면서 탐색 및 변경하는 과정을 거쳐야합니다. 하지만 이런 모든 과정을 거친다면 최첨단의 알고리즘을 이용해도 O(n^3) 의 시간복잡도를 가진다고 하더군요. 따라서 리액트는 조금은 다른 휴리스틱한 알고리즘을 이용해서, 시간복잡도를 O(n)까지 줄이는 방식을 택합니다. 참고로, v16이상 부터는 React Fiber 라는 새로운 알고리즘을 적용해 기존의 Reconciliation 알고리즘에서 발생하는 애니메이팅에 대한 부분을 개선했다고 합니다.
이런 알고리즘을 깊게 파고들면 매우 복잡하게 구현이 되어있을 거라 생각합니다. 리액트 공식문서에서는 이에 대해 대표적인 몇가지 방식을 소개하고 있고, 저 또한 간단하게만 요약해서 적어보았습니다.
DOM 엘리먼트의 타입이 다른 경우
<div>
<Counter />
</div>
<span>
<Counter />
</span>
- 이전 트리를 버리고 완전히 새로운 트리를 구축 (비효율적)
DOM 엘리먼트의 타입이 같은 경우
<div className="before" title="stuff" />
<div className="after" title="stuff" />
- 변경된 속성들에 대해서만 갱신
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
- 스타일 또한, 변경 사항이 있는 속성만 업데이트 (color 속성만 업데이트)
리액트 Key?
( 예시는 리액트 공식문서에서 그대로 가져왔습니다. )
성능이 좋은 경우
<!-- 전 -->
<ul>
<li>first</li>
<li>second</li>
</ul>
<!-- 후 -->
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
성능이 좋지 않은 경우
<!-- 전 -->
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<!-- 후 -->
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
DOM 노드의 자식들을 재귀적으로 처리할 때, React는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성합니다.
아래 예시와 같이 리스트의 맨 앞에 엘리먼트를 추가하는 경우, 성능이 좋지 않다고 합니다. 아마 내부 알고리즘이 순차적으로 트리를 순회하기 때문이겠죠? 아무튼 이러한 문제를 해결하기 위해 key라는 속성이 필요하게 됩니다.
Warning: Each child in a list should have a unique "key" prop.
리액트 개발하면서 위와 같은 경고를 보신적 있지 않으신가요?
에러를 일으키는 경우는 아니지만, Reconciliation 을 하는 데에 있어서 최적화에 문제가 있다고 경고를 주고 있는 겁니다.
<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 props를 주게 되면, 트리 변환 작업이 효율적으로 수행되게 됩니다.
내부적으로 저 key props 를 엘리먼트 식별자로 사용하면서 reconciliation을 하는 데에 도움이 되는 것이죠.
마지막으로 key props를 선정할 때의 유의사항을 나열하면서 마무리하겠습니다. 읽어주셔서 감사합니다. 🙏
유의사항
- 두 컴퍼넌트가 교체하는 상황에서는 그 둘을 같은 타입으로 만드는 것이 더 효율적이다.
- key값을 선정할 때는, 형제 사이에서만 유일하면 되고, 전역에서 유일한 value 일 필요는 없다.
- 배열의 인덱스는 key로 사용하지 않는 것이 좋다. 항목들이 재배열되면, key값이 변경될 수 있기 때문이다.
- key는 변하지 않고, 예상 가능하면서, 유일해야 한다. (Math.random() 같은 걸 key로 쓰면 안됨)
'Web > React' 카테고리의 다른 글
[React] 리액트 버전 18은 무엇이 달라졌을까? / React v18 (0) | 2021.09.08 |
---|---|
[React] 리액트 Concurrent 모드란 (0) | 2021.06.06 |
[React] Next JS 이미지 최적화, 이미지 컴퍼넌트 / Image Component / Image Optimization (0) | 2021.03.08 |
[React] Next JS Pre-rendering / Static Generation(getStaticProps ) / SSR(getServerSideProps) (0) | 2021.03.08 |
[React] Next JS 동적 라우팅 / Dynamic Routing (0) | 2021.03.08 |
댓글