Jaeilit

리액트 재조정과 가상돔 본문

TIL

리액트 재조정과 가상돔

Jaeilit 2023. 6. 8. 16:04
728x90

재조정 공식문서

 

재조정 (Reconciliation) – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org

 

이전글에서 브라우저 렌더링 과정을 살펴 봤습니다.

 

브라우저가 HTML 파싱하여 렌더트리를 구성하고 레이아웃을 계산하여 페인팅을 하는데

DOM 요소의 추가 또는 width height 같이 위치값 변경이 일어나면 Reflow가 일어나서 렌더트리를 재구성하게 됩니다.

 

SPA 의 스택기반의 페이지들은 dom 요소의 변화가 빈번하기 때문에 Reflow 는 많은 연산을 요구함으로 비용이 많이 드는 작업입니다.

이는 성능과도 연결이 되므로 리플로우를 최소화하는 것이 성능을 향상시키는 방법이 됩니다. 

 

재조정과 가상돔

 

처음에는 가상돔과 재조정을 다른 개념으로 이해하려고 하니 어려웠는데 하나의 개념으로 보니 마음이 가벼워졌습니다.

spa 에서 dom 요소가 빈번하게 일어날 때 매번 reflow 과정을 거치게 되면 연산 비용이 많이 든다고 이야기했습니다. 리액트에서는 하나의 트리로는 O(n^3) 의 복잡도가 소요되고 1000개의 엘리먼트에 대해서도 10억번의 비교연산을 수행해야한다고 설명했습니다. 이 비용 문제를 해결하기 위해 2가지의 가정을 했는데요

 

1. 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.

2. 개발자가 key, props 를 통해 여러 렌더링 사이에 어떤 자식이 엘리먼트가 변경되지 않았는지 표시해줄수있다.

 

이 2가지의 가정으로 O(n^3) 을 O(n) 복잡도의 휴리스틱 알고리즘을 구현했다고 합니다.

 

 

어떻게 재조정하는가?

리액트에서는 재조정에 diffing 알고리즘을 사용합니다.

2개의 트리를 비교할때, React 의 두 엘리먼트는 root 엘리먼트부터 비교를 합니다.

 

여기서 2개의 트리란, 가상돔 2개를 의미하는데요

실제돔을 복사한 기존의 가상돔과, 변경사항을 반영한 가상돔을 이야기합니다.

 

1. 엘리먼트의 타입이 다른 경우

 

두 엘리먼트의 타입이 다르면 React는 이전 트리를 버리고 완전히 새로운 트리를 구축합니다.

트리를 버릴때 DOM 노드들은 모두 파괴되고 새로운 트리가 만들어질때 새로운 DOM 노드들이 DOM에 삽입됩니다.

그에 따라 루트 아래에 컴포넌트들이 전부 언마운트가 되고 state 를 잃게 됩니다.

 

예) div -> span 으로 변경이 되면 트리 전체를 재구축합니다. 이전의 Count 컴포넌트는 사라지고 새로운 Count가 마운트 됩니다.

 

2. 엘리먼트 타입이 같은 경우

같은 엘리먼트일 경우에는 속성들만 갱신합니다. 아래 예제와 같은 경우에는 className 만 수정합니다.

3. 자식에 대한 재귀적 처리

DOM 노드의 자식들을 재귀적으로 처리할때, 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성합니다.

 

3-1 자식의 끝에 엘리먼트를 추가할때

아래 예제에서 두개의 트리가 있습니다. 하나는 변경전, 하나는 변경 후의 트리입니다.

두 트리를 비교했을 때  li 태그의 first, second 가 같으므로 마지막에 third를 추가합니다.

3-2 자식의 앞에 엘리먼트를 추가하는 경우,

위와 같이 두개의 트리가 있습니다. 하나는 변경 전, 하나는 변경 후의 트리입니다.

두 트리를 비교 했을 때 Duke villanova 가 같지만 순서가 맞지않습니다.

 

이런 경우에는 종속트리를 그대로 유지하는 대신 모든 자식을 변경하기 때문에 비효율적이고 성능에 좋지 않습니다.

 

keys

하지만 이 문제를 해결하기 위해서 리액트에서 key 가 얼마나 중요한지 또 설명해주고 있습니다.

 

리액트에서는 key 속성으로 자식들이 기존의 트리 key과 이후의 트리 key과 일치하는지 비교하게 됩니다.

위와 같이 맨 앞에 엘리먼트가 추가되었다하더라도 고유한 key 값이 있다면 Duke, Villanova 는 그저 이동만 하면 된다고 표현되네요

 

key 값

보통 key 값을 줄 때 고유한 id 값을 줘야하는데 그런 상황이 안될 경우 배열의 index 나 고유한 값을 주려고 Math.randum 이나 uuid 라이브러리를 설치해서 암호화 코드를 넣는 경우가 있습니다.

결론적으로 uuid 같은 암호화 코드들은 렌더링 할때마다 바뀌기 때문에 리액트는 트리를 잃어버릴 수 있어서 오히려 더 좋지않습니다.

 

변하지 않는 리스트라면 배열의 index 를 넣어도 되지만 변할 수 있는 배열이라면 고유한 id 값을 넣는 것이 좋습니다.

 

의문점,

 

저는 여기서 가상 돔이 실제 돔을 복사해서 메모리에 저장한다는 말은 메모리를 사용한다는 의미인데, 이렇게까지 가상돔을 구성해서 실제 돔을 반영하는게 과연 옳은 일인가? 라는 의문점이 들었습니다.

 

여기서 메모리는 RAM 을 이야기하는데 데이터 저장의 의미로 말씀드린것 뿐만 아니라 CPU 연산을 하기 위해 메모리를 들락날락 하는 그 메모리의 사용량도 포함하여 이야기하는 것입니다.

 

의문점을 해결하기 위해 구글링해본 결과 SPA 에서 처럼 빈번하게 dom 의 변화가 일어나는 경우에는 가상 돔을 메모리에 저장해두고 사용하는 것이 매번 Reflow 연산비용을 쓰는 것 보다 낫다 라는 결론이였습니다.

 

 

 

 

 

728x90