React와 가상 DOM

우디·2023년 6월 7일
0

React

목록 보기
4/4
post-thumbnail

React란

React는 사용자 인터페이스(UI) 구축을 위한 javaScript 라이브러리로, 사용자 인터페이스를 구성하는 컴포넌트 기반 접근 방식을 제공한다

React는 다음과 같은 문제를 해결하고자 Facebook에서 개발되었다

  1. 복잡성 관리: 대규모 애플리케이션에서 UI를 구축하면 복잡성이 증가할 수 있다. React는 컴포넌트 기반 접근 방식을 통해 UI를 작은 단위로 나누고, 각 컴포넌트를 독립적으로 개발하고 유지하는 것으로 코드의 가독성과 유지 보수성을 향상시킨다.

  2. 성능: React는 가상 DOM(Virtual DOM) 개념을 도입하여 UI 업데이트의 효율성을 높인다. React는 실제 DOM을 직접 조작하는 대신 가상 DOM에 변경 사항을 적용하고, 최소한의 DOM 조작만 수행하여 성능을 향상시킨다. 이를 통해 효율적인 UI 렌더링이 가능해진다.

  3. 재사용성: React의 컴포넌트 기반 접근 방식은 재사용 가능한 UI 요소를 쉽게 작성하고 조합할 수 있도록 한다. 컴포넌트를 작은 부분으로 나누고, 각 부분을 독립적으로 개발하여 필요에 따라 조합할 수 있다. 이는 개발 생산성을 높이고 코드의 재사용성을 증가시킨다.

  4. 단방향 데이터 흐름: React는 단방향 데이터 흐름을 지향한다. 데이터는 부모 컴포넌트에서 자식 컴포넌트로만 흐르며, 자식 컴포넌트에서는 상위 컴포넌트의 상태를 직접 변경할 수 없다. 이는 애플리케이션의 상태 관리를 예측 가능하고 디버깅하기 쉽게 만든다.

이러한 문제들을 해결하기 위해 React는 간결하고 선언적인 구문을 사용하며, 컴포넌트 간의 계층 구조를 통해 UI를 구성한다. 이러한 특징들로 인해 React는 현대적인 웹 애플리케이션 개발에 많이 사용되고 있다.

가상 DOM(Virtual DOM)

가상 DOM은 실제 DOM(Document Object Modal)을 메모리에 가벼운 복제본으로 유지하고, UI 업데이트 시에 변경사항을 비교하여 필요한 부분만 실제 DOM에 적용하는 방식이다.

일반적으로 웹 애플리케이션에서 UI를 업데이트하려면 DOM을 조작해야 한다. DOM은 웹 페이지의 구조를 표현하는 객체 모덜로써, HTML 요소들의 트리 구조로 이루어져 있다. DOM을 직접 조작하면 UI 업데이트가 빈번하거나 복잡한 경우 성능 문제가 발생할 수 있다. 예를 들어, 수많은 DOM 요소를 추가, 제거, 변경하는 작업이 필요한 경우에는 많은 리소스와 시간이 소모된다

가상 DOM은 이러한 문제를 해결하기 위해 도입되었는데, 가상 DOM을 사용하여 React에서는 다음과 같은 단계로 UI 업데이트의 효율성을 높인다.

  1. 초기 렌더링: React는 가상 DOM을 생성하여 메모리에 유지한다. 이 가상 DOM은 실제 DOM과 유사한 구조를 가지지만, 실제로는 메모리 내에 존재하는 객체다.

  2. 업데이트 감지: 상태가 변경되면 React는 가상 DOM과 이전의 가상 DOM을 비교하여 변경된 부분을 찾는다. 이를 통해 변경된 컴포넌트나 요소를 식별한다.

  3. 가상 DOM 업데이트: 변경된 부분을 실제 DOM에 적용하기 전에, React는 가상 DOM을 업데이트한다. 이 과정에서 가상 DOM은 메모리 내에서 효율적으로 조작된다

  4. 실제 DOM 업데이트: 가상 DOM의 업데이트가 완료되면 React는 변경된 부분을 실제 DOM에 적용한다. 이 때, 실제 DOM을 직접 조작하는 것이 아니라, React가 변화를 감지하고 필요한 변경만을 실제 DOM에 반영한다. 이는 불필요한 DOM 조작을 최소화하여 성능을 향상시킨다.

가상 DOM을 사용하면 실제 DOM 조작을 최소화하고, 변경된 부분만 업데이트하는 방식으로 UI를 관리할 수 있다. 이는 React의 성능을 향향시키고, 복잡한 UI 상태의 관리를 간소화하는 데 도움을 준다.

DOM을 직접 조작하는 것이 왜 성능 문제를 발생시킬까?

UI 업데이트가 빈번하지 않은 경우에는 성능 문제가 크게 나타나지 않을 수 있지만, UI 업데이트가 자주 발생하거나 복잡한 경우에는 DOM을 직접 조작하는 것이 성능 문제를 발생시킬 수 있다.

  1. DOM 조작 비용: DOM은 웹 페이지의 구조를 표현하는 객체 모델이다. 이러한 DOM 요소의 추가, 제거, 변경 등의 작업에는 큰 비용이 발생한다. DOM 조작이 발생할 때마다 브라우저는 다시 레이아웃을 계산하고, 화면에 다시 그려야 하기 때문이다. 이는 CPU와 메모리 등의 시스템 리소스를 많이 사용한다.

  2. 리플로우와 리페인트: DOM 조작은 브라우저의 리플로우(reflow)와 리페인트(repaint)를 유발할 수 있다. 리플로우는 레이아웃의 변경이 필요한 경우 발생하며, 리페인트는 스타일의 변경이 필요한 경우 발생한다. 리플로우와 리페인트는 브라우저가 UI를 다시 그리는 과정으로, 성능에 부담을 주는 작업니다.

  3. JavaScript와의 상호작용: DOM 조작은 JavaScript와의 상호작용을 필요로 한다. JavaScript는 단일 스레드로 동작하며, UI 업데이트가 빈번하게 발생하는 경우에는 JavaScript의 실행 시간이 증가하게 되어 렌더링과 사용자 인터렉션에 지연이 발생할 수 있다. 이는 사용자 경험을 저하시키고 반응성을 떨어뜨릴 수 있다.

즉, 느린 렌더링 속도, 높은 CPU 사용량, 메모리 누수 등의 문제가 발생할 수 있다

예를 들어보자

<div id="container">
  <div class="box">Box 1</div>
  <div class="box">Box 2</div>
</div>
.box {
  width: 100px;
  height: 100px;
  background-color: red;
  margin-bottom: 10px;
}
const container = document.getElementById("container");

// 새로운 박스 요소를 생성하여 컨테이너에 추가
const newBox = document.createElement("div");
newBox.classList.add("box");
newBox.innerText = "New Box";
container.appendChild(newBox);

// 박스 요소의 스타일 변경
newBox.style.backgroundColor = "blue";
newBox.style.width = "120px";

위의 코드에서는 container 요소에 새로운 box 요소를 추가하고, 추가된 box 요소의 스타일을 변경하고 있다

여기서 리플로우와 리페인트가 발생하는 상황은 다음과 같다.

  1. 새로운 box 요소 추가 시 리플로우: 새로운 box 요소를 container에 추가하는 경우, container의 레이아웃이 변경된다. 추가된 box 요소의 위치와 container의 높이가 변경되므로, 리플로우가 발생한다.

  2. box 요소의 스타일 변경 시 리페인트: newBox의 배경색과 너비를 변경하는 경우, 해당 요소의 스타일이 변경되므로 리페인트가 발생한다. 변경된 스타일에 따라 newBox의 배경색과 너비가 다시 그려지게 된다.

리액트에서는 어떨까?

import React, { useState } from 'react';

const Box = ({ color, width, text }) => (
  <div className="box" style={{ backgroundColor: color, width }}>
    {text}
  </div>
);

const Container = () => {
  const [boxes, setBoxes] = useState([
    { color: 'red', width: '120px', text: 'Box 1' },
    { color: 'red', width: '120px', text: 'Box 2' }
  ]);

  const handleAddBox = () => {
    const newBox = { color: 'blue', width: '120px', text: 'New Box' };
    setBoxes(prevBoxes => [...prevBoxes, newBox]);
  };

  return (
    <div id="container" style={{ height: '400px' }}>
      {boxes.map((boxStyle, index) => (
        <Box key={index} color={boxStyle.color} width={boxStyle.width} text={boxStyle.text} />
      ))}
      <button onClick={handleAddBox}>Add Box</button>
    </div>
  );
};

export default Container;

React는 가상 DOM을 사용해서, 컴포넌트의 변경이 발생할 때 변경된 부분에 대한 업데이트만 수행한다. 따라서 handleAddBox 함수에서 새로운 box 요소를 추가하거나 스타일을 변경해도, React는 변경된 부분만 업데이트하고 불필요한 리플로우와 리페인트를 최소화한다.

위의 코드를 실행하면 버튼을 클릭할 때마다 새로운 box 요소가 추가되고, 해당 요소의 스타일이 변경된다. 그러나 React는 가상 DOM을 통해 변경된 부분에 대한 업데이트를 수행하므로 Container 전체에 리플로우와 리페인트가 발생하지 않는다.

단, 항상 그러한 것은 아니다.

현재 #container의 height는 400px, Box의 height는 120px이다. box의 개수가 3개까지는 360px#container에 영향을 끼치지 않지만, 4개가 되면 height가 480px#container에 영향을 주게 된다. 이러한 경우 #container` 자체에도 리플로우가 발생할 수 있다.

이처럼 부모 DOM에 리플로우와 리페인팅이 발생하지 않는다는 것이 아니다. 최소화하는 것리하는 것에 주의하자.


Quiz!!

  • 리액트 컴포넌트가 리렌더링 된다는 것이 해당 DOM에 리플로우와 리페인팅을 발생시킨다는 것일까?

정답은 No다

React에서 컴포넌트의 리렌더링은 컴포넌트의 상태나 속성이 변경되었을 때, 해당 컴포넌트를 다시 그리는 과정을 말한다. React의 가상 DOM은 변경된 부분에 대한 최적화된 업데이트를 수행하여, 필요한 경우에만 DOM 조작을 수행하고 리플로우와 리페인트를 최소화한다. 즉, 컴포넌트의 리렌더링은 React 내부에서 가상 DOM을 사용하여 업데이트를 수행하는 과정이다.

반면에 DOM의 리플로우와 리페인트는 브라우저가 요소의 레이아웃이 변경되거나 스타일이 변경되어 다시 그리는 과정을 말한다. 이는 브라우저의 동작 방식에 따라 발생하며, DOM을 직접 조작하는 것이나 React를 사용하는 경우 모두 해당될 수 있다.

profile
계정 이전했습니다 https://velog.io/@rjw0907/posts

0개의 댓글