React의 Virtual DOM 작동 방식과 주의할 점

te-ing·2025년 2월 18일
0

Virtual DOM의 비교(diffing) 알고리즘

state나 props가 갱신되어 render()가 실행되면서 리액트가 렌더링되면, 리액트는 실제 DOM 구조를 가벼운 자바스크립트 객체로 표현한 가상 DOM(리액트 엘리먼트 트리)을 생성합니다. 그리고 diffing 알고리즘을 통해 변경사항을 O(n)의 복잡도로 파악할 수 있습니다.

알고리즘의 동작원리는 다음과 같습니다.

  • 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
  • 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

두 트리를 비교할 때, 루트부터 비교하며 어떤 변경사항이 있는지를 확인합니다. 이때 만약 루트 노드가 다르면 해당 하위 트리를 모두 새로 렌더링합니다.

노드를 비교할 때에는 타입(이름)과 속성, key를 비교합니다. 엘리먼트의 타입(이름)이 다르다면 완전히 새로운 트리를 구축합니다. <a>에서 <img>로, 혹은<Article>에서 <Comment>로 바뀌는 것 모두 트리 전체를 재구축하는 경우입니다.

두 노드의 타입이 같으면 속성과 자식 요소를 비교합니다. 이때 동일한 내역은 유지하고 변경된 속성들만 갱신합니다. 노드의 비교과정이 끝나면 해당 노드의 자식들을 재귀적으로 탐색하며 처리합니다.


리액트에서 배열의 변경사항을 감지하고 렌더링하는 방식

리액트에서 배열을 사용해서 컴포넌트를 렌더링한다면 리액트는 배열을 순회하면서 요소를 생성하고 반환합니다.

이때 배열에 변경사항이 생겼을 때 리액트는 Virtual DOM과 현재의 배열을 비교하며 변경사항을 확인합니다.
예를 들어 [1,2,3,4,5] 라는 배열이 있을 때, 3을 삭제한다면 리액트는 [1,2,4,5]배열을 순회하면서 비교하게 되는데, [1,2]까지는 기존의 첫번째 두번째 요소와 같지만 [4,5]는 네번째 [3,4]와 다르기 때문에 재조정 과정을 진행합니다.

만약 첫번째 값이 삭제되었다면 배열의 모든 요소를 리렌더링 해야 하는데, 배열 내 모든 요소를 리렌더링하는 것은 효율적이지 않으며, 배열 내부의 값을 비교하기 위해 배열의 모든 값을 확인하면서 변경사항을 체크하는 것도 효율적이지 않습니다.

때문에 리액트는 key를 통해서 배열 내에 어떤 요소가 변경되었는지를 판단하는데, key값이 존재하지 않으면 자동으로 배열의 index를 key값으로 설정합니다.


index를 key값으로 사용할 때의 문제점

이때 배열의 index를 key값으로 사용할 때의 문제점이 발생하는데, 배열의 순서가 바뀌거나 값이 추가/삭제되는 경우 index가 변경될 수 있습니다.
예를 들어 [1,2,3,4,5] 라는 배열이 있을 때, 3을 삭제한다면 리액트는 index가 [0,1,3,4]가 아닌 [0,1,2,3]으로 변경되어 5가 변경되었다고 판단하게 됩니다.


Math.random()을 key값으로 사용할 때의 문제점

또한 key값을 Math.random() 과 같이 매번 변경되는 것을 사용하면 안되는 이유도 이와 같은데, 키를 확인하는 과정에서 key가 매번 변경되기 때문에 배열의 모든 요소가 변경되었다고 판단하여 모든 요소를 재생성하는 효율적이지 않는 동작을 하게 됩니다.



https://ko.react.dev/learn/rendering-lists#why-does-react-need-keys
https://ko.legacy.reactjs.org/docs/reconciliation.html#the-diffing-algorithm

profile
프론트엔드 개발자

0개의 댓글

관련 채용 정보