코드 훔쳐보는 변태 코더
춤 좋아하는 백엔드 개발자(였으면 좋겠다)
Virtual DOM 가장 쉽게 이해하기

선언형 프로그래밍

버추얼돔에 대해서 공부하기 이전에 우리는 선언형 프로그래밍에과 명령형 프로그래밍에 대해 알고 갈 필요가 있다.

선언형 프로그래밍이란 ?

한 정의에 따르면, 프로그램이 어떤 방법으로 해야 하는지를 나타내기보다 무엇과 같은지를 설명하는 경우에 "선언형"이라고 한다.

선언형 프로그래밍은 무엇(What)을 원하는지를 명시하고, 시스템이 어떻게(How) 처리해야 하는지는 추상화된 레벨에서 결정하는 방식이다.

쉽게 말해 리액트에서 UI를 선언적으로 작성하면 개발자는 UI의 구조와 렌더링 결과물을 설명하고, 리액트가 이를 실제 DOM 조작으로 변환하고 최적화한다.

그럼 명령형 프로그래밍이란 ?

프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종이다.

어떻게(How) 작업을 수행해야 하는지 단계별로 명시하고, 변경 가능한 상태를 직접 조작하여 프로그램의 흐름을 제어하는 방식이다.

개발자의 의도대로 하나부터 열까지 동작해야 하는 상황에서는 좋을 수 있지만, 그게 아니라면 오히려 복잡한 작업순서와 예상치못한 결과를 초래할 수 있다.

DOM (Document Object Model)

DOM은 Document Object Model의 약자로 HTML 문서 의 모든 노드를 구조적으로 표현한 것입니다.

쉽게 말해 DOM은 응용 프로그램의 UI를 나타낸다고 할 수 있다.

만약 당신이 1부터 5까지의 값이 담긴 배열에서 중간의 3을 4로 바꾸고 싶다면 어떻게 할것인가?

const arr = [1,2,3,4,5] // 변경 전
const arr = [1,2,4,4,5] // 변경 후
 

작은 규모라면, 단순히 하나의 값을 변경한 새 배열을 선언해 대체할 수 있지만, 만약 규모가 작지 않다면 해당 방법은 비효율적일 수 있다.

이렇게 실제 DOM은 하나의 변경점이 발생해도 모든 노드를 업데이트 하게 된다. 변경점이 발생하지 않았던 노드도 리렌더링을 하게 된다면 얼마나 큰 비용이 소모되는지 알 수 있다.

Virtual DOM

버추얼 돔은 실제 돔의 추상화된 모델이라 볼 수 있다. 그러기 떄문에 추상화의 추상화이다. 경량 복사본이라는 점을 제외하면 실제 DOM 개체와 동일하다.

버추얼돔은 돔의 동작방식을 보완하여 속성이 변경되면 전체 트리가 아닌 해당 노드만 업데이트된다.

리액트에서 버추얼돔은 어떻게 업데이트에 관여할까?

  1. ReactDOM.render() 는 실제 돔과 버추얼 돔트리를 생성하여 첫 번째 로드시 화면의 요소를 렌더링한다.
  2. 요소에 대한 변경 사항 혹은 상태 변경에 대한 알림을 버추얼 노드로 전송한다. 마찬가지로 노드의 속성이 변경되면 자체적으로 업데이트 된다.
  3. 리액트는 업데이트된 버추얼돔을 실제 돔과 비교한다. 그리고 실제 돔을 업데이트 하며 이 과정을 재조정이라고 한다. 재조정은 Diffing Algorithm 으로 알려진 휴리스틱 알고리즘을 사용하여 수행된다.

그렇게 버추얼돔은 변경값이 존재하는 속성만 업데이트하여 렌더링함과 동시에

모든 변경점을 하나하나 리렌더링을 트리거하며 업데이트하지 않고, 필요한 부분만 업데이트하여 한번만 리렌더링을 트리거한다.

div.innerHTML='change text'
div.style.cssText='width:100px;height:100px'
 

예를 들어 이러한 변경점이 발생할 때 , 실제 돔은 변경점 하나하나마다 리렌더링을 한다고 하면, 버추얼 돔은 이 변경점을 모두 반영하여 한번의 리렌더링을 발생시킨다.

→ 실제 돔은 이렇게 개발자가 원하는 방향을 하나하나 일일히 명령하여 렌더링과 성능최적화를 직접 신경써야 한다면

→ 버추얼 돔은 개발자가 원하는 방향과 처리방법을 선언하면 리액트가 디핑 알고리즘을 통해 변경점을 캐치 후 렌더링한다.

이것이 선언형 / 명령형 프로그래밍 방식의 차이라고도 볼 수 있다.

그렇다면 버추얼돔과 실제돔 사이 비교는 어떻게 이뤄질까?

한 트리를 다른 트리로 변환하기 위하여 최소한의 연산을 제공하는 알고리즘은 O(n3)의 시간 복잡도를 가진다고 한다. → 1000개의 요소를 표시하는데 10억번의 비교가 필요하다는 소리이다.

리액트는 이러한 재조정 과정에서 Diffing 알고리즘 휴리스틱 알고리즘을 사용하여 성능을 최적화하며 시간 복잡도를 낮췄다.

휴리스틱 알고리즘

→ 휴리스틱이란 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나 체계적이면서 합리적 판단이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있도록 구성된 간편 추론 방법이다.

단순히 중요한 것들만 고려해서 최선의 값을 찾아내는 방법이라고 할 수 있다.

  • 다른 타입의 두 요소는 서로 다른 트리를 생성한다.
  • 개발자는 Key prop을 이용해 다른 렌더링 사이에서의 자식 요소에 대한 힌트를 얻을 수 있다.

리액트는 위 두가지 가정에 따른 휴리스틱 알고리즘을 채택하여 시간복잡도를 O(n)으로 낮췄다.

따라서 휴리스틱 알고리즘이 기반하고 있는 가정에 부합하지 않는 경우에 성능이 나빠질 수 있다.

재조정 과정

  • 기본적으로 두개의 트리를 비교할 때 ,리액트는 비교를 위한 새 버추얼돔을 생성 후 루트 엘리먼트부터 비교한다.
    • 돔 요소 타입이 다른경우 리액트는 이전 트리를 버리고 새로운 트리를 구축하며 새로운 돔 노드들이 돔에 삽입되며 연관된 모든 state 는 사라지며 새로 다시 마운트된다.
    • 같은 경우에는 변경된 속성들만 갱신한다.
  • 돔노드의 처리가 끝나면, 리액트는 해당 노드의 자식들을 재귀적으로 처리한다.
    • 더 이상 처리할 자식이 없을때까지 처리한다는 뜻
  • 이러한 상황에서의 Key 프로퍼티가 성능적인 측면에서 중요한 역할을 하게된다.
    • 자식들이 key를 갖고있다면, 리액트는 key를 통해 기존트리와 이후 트리의 자식들이 일치한지 확인하여 자식 요소들을 순회하지 않고 key 속성으로 쉽게 판단할 수 있게 된다.

→ key 값으로 인덱스를 사용하면 어떻게 될까?

  • 어떠한 상황에서 단순히 key 값으로 인덱스를 사용하는 경우가 존재한다.
  • 재배열되는 경우에는 항목의 순서가 바뀔 때 key 또한 바뀌기 때문에 식별자로서의 역할을 하지 못한다.

주의점

  • 매우 비슷한 결과물을 출력하는 두 컴포넌트를 교체하고 있다면, 그 둘을 같은 타입으로 만드는 것이 더 나을수도 있다.
  • 위와 마찬가지로 key 는 변하지 않고 예상이 가능하며 유일해야한다. 변하는 키를 사용하면 자식 컴포넌트의 state 가 유실되거나 성능이 나빠질 수 있다.

레퍼런스

ReactJS Reconciliation - GeeksforGeeks

Difference between Virtual DOM and Real DOM - GeeksforGeeks

What is the difference between virtual and real DOM (React)?

[ React ] 재조정(Reconciliation)과 Key 사용 이유

재조정 (Reconciliation) – React

  Comments,     Trackbacks