Skip to content

React와 React-DOM의 아키텍처 - 뇌와 손은 어떻게 분리되었나?

Published: at 오후 08:54

우리는 React의 내부 메커니즘, 모범 사례, 디자인 패턴, 그리고 고급 개념들을 탐구합니다.

이 글들은 기본을 넘어 React가 내부적으로 어떻게 작동하는지 진정으로 이해하고자 하는 React 개발자들을 위해 작성되었습니다.

서론: 두 개의 패키지, 하나의 철학

React는 ‘react’와 ‘react-dom’이라는 두 개의 개별 패키지로 나뉘어 있습니다.

이 분리는 단순히 기술적인 선택이 아니라, React의 핵심적인 아키텍처 비전을 반영합니다.

마치 인간의 신체처럼, React는 ‘생각하는 뇌’와 ‘실행하는 손’으로 역할을 명확히 나누고 있습니다.

오늘은 이 두 패키지의 역할과 그 분리 뒤에 숨겨진 깊은 의미를 자세히 살펴보겠습니다.

React 패키지: 애플리케이션의 ‘뇌'

'react’ 패키지는 React 애플리케이션이 무엇인지를 정의하는 핵심 로직을 담고 있습니다.

마치 모든 것을 계획하고 결정하는 ‘뇌’와 같습니다.

컴포넌트, 엘리먼트, 상태를 생성하고 관리하는 모든 추상적인 작업이 바로 이곳에서 이루어집니다.

구체적으로 이 패키지는 다음을 관리합니다.

가장 중요한 점은 ‘react’ 패키지가 ‘플랫폼에 구애받지 않는다(platform-agnostic)‘는 것입니다.

이 안에는 웹 브라우저의 DOM이나 모바일의 네이티브 UI에 대한 코드가 단 한 줄도 없습니다.

오직 순수한 ‘무엇을(what)’ 렌더링할지에 대한 계획과 로직만 존재합니다.

이 덕분에 React는 웹, 모바일, 데스크톱, 심지어 VR 애플리케이션까지 넘나들 수 있는 것입니다.

React-DOM 패키지: 브라우저를 위한 ‘손'

'react-dom’ 패키지는 ‘웹 브라우저’라는 특정 플랫폼을 위해 만들어졌습니다.

이 패키지의 역할은 React라는 ‘뇌’가 만든 추상적인 세계(가상 DOM)와 실제 브라우저 DOM을 연결하는 ‘다리’이자, 그 계획을 실행하는 ‘손’입니다.

React의 지시를 실제 DOM 변경으로 바꾸는 모든 구체적인 작업이 이곳에서 이루어집니다.

주요 책임은 다음과 같습니다.

왜 두 개의 패키지로 분리했을까?

이 아키텍처 분리는 여러 가지 중요한 이점을 제공합니다.

1. 이식성 (Portability)

이것이 가장 큰 이유입니다.

React의 ‘뇌’는 그대로 둔 채, ‘손’의 역할만 하는 ‘렌더러(Renderer)‘를 교체하면 다른 환경에서도 React를 사용할 수 있습니다.

// 웹에서는 React-DOM 렌더러를 사용합니다.
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
root.render(<App />);

// 모바일에서는 React-Native 렌더러를 사용합니다.
import { AppRegistry } from "react-native";
AppRegistry.registerComponent("App", () => App);

// 테스트 환경에서는 React-Test-Renderer를 사용합니다.
import renderer from "react-test-renderer";
const tree = renderer.create(<App />).toJSON();



이 모든 환경에서 App 컴포넌트 자체는 동일하게 재사용될 수 있습니다.

이것이 바로 React의 ‘한 번 배워서, 어디서든 작성한다(Learn Once, Write Anywhere)‘는 철학의 핵심입니다.

2. 독립적인 유지보수 및 발전

각 패키지는 독립적으로 발전할 수 있습니다.

예를 들어, React 팀은 브라우저 DOM의 변화와는 상관없이 새로운 훅(useOptimistic 등)을 ‘react’ 패키지에 추가할 수 있습니다.

반대로, 브라우저에 새로운 API가 등장하면, ‘react-dom’ 팀은 React의 핵심 로직을 건드리지 않고도 렌더링 성능을 최적화할 수 있습니다.

3. 최적화 및 번들 크기

이 분리는 번들 크기를 최적화하는 데 도움이 됩니다.

예를 들어, 모바일 앱을 빌드할 때는 웹 브라우저의 DOM 조작과 관련된 react-dom의 방대한 코드를 포함할 필요가 전혀 없습니다.

JSX 없이 React 사용해보기

이 분리 구조를 실제로 체감할 수 있는 재미있는 방법이 있습니다.

바로 JSX 없이 React를 사용하는 것입니다.

사실 JSX는 React.createElement() 함수를 더 편하게 쓰기 위한 ‘문법적 설탕(Syntactic Sugar)‘일 뿐입니다.

// createElement와 useState는 'react' 패키지에서 옵니다.
const { createElement, useState } = React;

function Counter() {
  const [count, setCount] = useState(0);

  // 이 모든 것은 UI의 '설계도'를 만드는 과정입니다.
  return createElement("div", null, [
    createElement("p", null, `Counter: ${count}`),
    createElement(
      "button",
      { onClick: () => setCount(count + 1) },
      "Increment"
    ),
    createElement(
      "button",
      { onClick: () => setCount(count - 1) },
      "Decrement"
    ),
  ]);
}

// createRoot와 render는 'react-dom' 패키지에서 옵니다.
const root = ReactDOM.createRoot(document.getElementById("root"));
// '뇌'가 만든 설계도(엘리먼트)를 '손'에게 전달하여 화면에 그리도록 명령합니다.
root.render(createElement(Counter));



솔직히 말해, 보기에 아주 장황한 코드죠! 😅 이것이 바로 우리가 JSX의 편리함에 감사해야 하는 이유입니다.

결론

reactreact-dom의 분리 아키텍처는 훌륭한 설계가 어떻게 코드에 유연성과 유지보수성을 부여하는지 보여주는 완벽한 예시입니다.

이 구조는 React가 다양한 환경에서 작동하게 하면서도, 코드베이스를 일관되고 최적화된 상태로 유지할 수 있게 해줍니다.

핵심을 다시 정리해 보겠습니다.

이 아키텍처는 React 19와 그 이후에도 계속해서 발전하며, React가 성공할 수 있었던 이유인 단순함과 유연성을 유지하면서 개발자들에게 더 많은 가능성을 열어줄 것입니다.