리액트 핵심 요소 살펴보기

Jseok·2026년 5월 7일
post-thumbnail

리액트를 사용하다 보면 JSX, Virtual DOM 등 자연스럽게 익숙해지는 개념들이 있습니다. 우리는 리액트를 활용하여 개발을 진행하지만, 리액트의 세부적인 개념들을 명확히 이해하고 사용하지는 못한 것 같습니다.

이번 글에서는 리액트의 핵심이 되는 주요 개념들을 하나씩 정리하며, 리액트의 동작 방식에 대해 조금이나마 이해해보려고 합니다.


JSX와 트랜스파일

JSX를 처음 배웠을 때, 단순히 자바스크립트 안에서 HTML을 작성할 수 있는 문법 정도로만 생각했습니다. JSX가 무엇인지 정확하게 배우기 전에, 그저 리액트로 개발하려면 써야하는 것 정도로만 이해했던 것 같습니다.

실제로 JSX는 HTML과 매우 유사하게 생겼고, 브라우저 화면에 UI를 그린다는 점에서도 비슷합니다. 하지만 JSX는 HTML이 아닙니다. 조금 더 깊게 들여다보면, JSX는 ECMAScript 표준 문법이 아니기 때문에 브라우저가 직접적으로 이해할 수 있는 문법이 아닙니다.

그럼에도 우리가 리액트에서 JSX를 사용할 수 있는 이유는 트랜스파일 과정이 존재하기 때문입니다. 리액트에서는 Babel과 같은 트랜스파일러가 JSX 문법을 브라우저가 이해할 수 있는 일반 자바스크립트 코드로 변환합니다.

// 이러한 JSX 문법은
<div className="container">
  <h1>Hello</h1>
  <p>LET'S SOPT React Study</p>
</div>

// 아래와 같은 형식으로 변환됩니다.React.createElement("div",
  { className: "container" },
  React.createElement("h1", null, "Hello"),
  React.createElement("p", null, "LET'S SOPT React Study")
);

React.createElement 는 화면에 그릴 UI 정보를 담은 React Element 객체를 생성합니다.

변환된 후 구조를 살펴보면, JSX는 결국 중첩된 함수 호출 구조로 변환되고 이를 통해 리액트는 UI를 트리 형태의 자바스크립트 객체 구조로 표현하고 있다는 점을 알 수 있습니다.

const element = <h1>SOPT</h1>;

console.log(element);

// console.log 결과
{
  type: "h1",
  props: {
    children: "SOPT"
  }
}

즉, JSX는 단순히 HTML을 그리는 문법이 아니라 UI를 자바스크립트 객체 트리로 표현하기 위한 선언형 문법에 가깝습니다.


Virtual DOM

리액트의 가장 대표적인 개념 중 하나가 Virtual DOM, 가상돔입니다. 가상돔은 실제 DOM을 추상화하여, 메모리 내에 가상으로 존재하는 DOM을 의미합니다.

그리고 우리는 일반적으로 가상돔이 실제 DOM보다 빠르다고 알고 있습니다. 완전히 틀린 설명이라고 보기는 어렵지만, 핵심을 정확히 담고 있지도 않습니다.

실제로 브라우저의 DOM API 자체가 항상 느린 것은 아닙니다. 문제는 상태가 복잡해질수록, UI를 조작하는 개발자가 직접 관리하는 비용이 급격히 커진다는 것입니다.

전통적인 DOM 조작

2주차 과제를 생각해보면, querySelector 등을 활용해 DOM 요소를 직접 찾고, innerText 를 수정하는 방식으로 화면 변경 과정을 모두 관리해야 했습니다.

그나마 2주차 과제는 간단한 편이라 괜찮았지만 규모가 조금만 더 커져도, 우리는 어떤 요소를 수정해야 하는지 매번 찾아야 하고, 상태와 화면을 동기화해야하고, 여러 UI 변경 순서도 제어해야 하는 등 작업이 더 복잡해집니다.

리액트는 조금 다른 방식으로 접근하여, 개발자가 모든 화면 변경 과정을 직접 작성하지 않습니다.

대신 현재 상태라면 어떤 UI가 되어야 하는지를 선언합니다.

return isLoggedIn
  ? <Dashboard />
  : <LoginPage />;

그리고 이전 UI와 새로운 UI를 비교하여 실제 DOM 변경 사항을 계산합니다. 여기서 필요한 것이 바로 가상돔입니다.

위에 JSX 섹션에서 언급한 것처럼, JSX는 내부적으로 객체 형태로 변환되고 컴포넌트 구조 또한 중첩된 트리 형태로 표현됩니다.

리액트는 상태가 변경될 때마다 새로운 UI 트리를 생성하고, 이전 트리와 비교합니다. 어떤 컴포넌트가 달라졌는지, 어떤 부분이 추가 또는 삭제되었는지를 가상돔에서 계산한 뒤 실제 DOM에는 필요한 변경만 반영합니다. 이 과정이 바로 Reconciliation 입니다.

그렇다면 왜 리액트는 가상돔을 사용하는지 근본적인 의문이 들 수 있습니다. 실제 DOM은 브라우저가 관리하는 복잡한 객체이고, 직접 조작 비용도 존재합니다. 하지만 가상 돔은 단순한 자바스크립트 객체이므로 비교가 쉽고, 메모리 상에서 계산 가능하기 때문에 변경 사항을 추론하기 유리합니다.

다시 처음으로 돌아가서 가상돔이 일반 DOM에 비해 빠르다는 것은, 항상 그렇지는 않습니다. 오히려 DOM 변경 하나만 놓고 보면 직접 DOM을 조작하는게 더 빠를 수도 있습니다. 하지만 가상 돔은 UI를 계산 가능한 데이터 구조로 만들어서, 복잡한 UI 변경 과정을 추상화할 수 있도록 한다는 것에 더 큰 의미가 있습니다.

React Fiber

초기 리액트 렌더링 과정은 동기적으로 한 번에 처리했습니다. 모든 작업이 하나의 작업처럼 메인 스레드에서 실행되었는데, 프로젝트 규모가 커지고 UI 트리가 커질수록 이 작업이 점점 무거워졌습니다.

대규모 리스트 렌더링이나 복잡한 상태 업데이트, 애니메이션과 동시에 발생하는 렌더링 등에서 브라우저가 버벅이기 시작했습니다.

이러한 문제를 해결하기 위해 React Fiber라는 개념이 등장했습니다. React Fiber는 렌더링 작업을 작은 단위로 분리하여, 필요하다면 중간에 멈추고 다시 이어서 작업하도록 합니다. 즉 렌더링 작업의 우선순위를 조절합니다.

점진적 렌더링 (Incremental Rendering)

리액트의 대표적인 특징 중 하나는 점진적 렌더링입니다.

리액트는 모든 작업의 중요도를 동일하게 생각하지 않습니다. 사용자 입력, 클릭 이벤트와 같은 작업은 즉각적으로 반응해야 하기 때문에 높은 우선순위를 주고 화면 밖 컴포넌트 렌더링이나 데이터 프리패칭은 상대적으로 낮은 우선순위를 가집니다.

Fiber는 이러한 작업들을 우선순위와 브라우저 상황에 맞게 작업을 분배합니다.

더블 버퍼링 (Double Buffering)

두 번째 특징은 더블 버퍼링 구조입니다.

리액트는 계산 중인 UI를 보여주지 않기 위해, 현재 화면을 바로 수정하지 않습니다. 보이지 않는 곳에서 UI 트리를 먼저 생성하고 모든 계산이 끝난 뒤 완성된 트리를 한 번에 교체합니다. 즉, 우리는 항상 완성된 UI만 볼 수 있습니다.

이러한 구조 덕분에 렌더링 도중 작업을 중단하거나 다시 이어서 수행 가능하고, 이후 다룰 Suspense 같은 기능들도 구현할 수 있습니다.


Render Phase와 Commit Phase

리액트는 상태가 변경되었다고 해서 바로 실제 DOM을 수정하지 않습니다.

내부적으로 렌더링 과정을 크게 Render Phase와 Commit Phase로 나누어 처리합니다.

Render Phase

Render Phase는 UI를 계산하는 단계입니다. state 나 props 가 변경되면 리액트는 컴포넌트 함수를 다시 실행합니다. 위에 가상돔 섹션에서 계속 언급했지만 이때 이전 트리와 새로운 트리를 비교하여 변경 사항을 계산합니다.

간단하게 요약하면 새로운 UI가 어떤 모습이어야 하는지 계산하는 단계입니다.

Commit Phase

Render Phase에서 모든 계산이 끝난 후 Commit Phase가 실행됩니다.

Commit Phase에서는 계산된 변경 사항을 실제 DOM에 반영합니다. Render Phase는 Fiber 아키텍처 이후에 렌더링 작업을 작은 단위로 나누어 처리할 수 있게 되면서 필요하다면 중간에 멈추고 다시 수행하는게 가능하지만, Commit Phase는 중단할 수 없습니다. 실제 화면 변경 도중 작업을 멈추면 일관성 없는 UI가 보일 가능성을 차단하기 위함입니다.

우리는 컴포넌트 함수가 다시 실행되면 무조건 DOM에 변경이 있다고 생각합니다. 하지만 이렇게 Render Phase와 Commit Phase로 구분되어 렌더링 과정이 진행되기 때문에, 변경 사항이 없다면 Commit Phase로 넘어가지 않아 DOM 변경이 없을 가능성도 있습니다.

0개의 댓글