React에서의 자식 평탄화 이해하기: Babel, JSX, createElement, Virtual DOM 전환까지 전부 정리

MIlo·2025년 6월 10일

JSX에서 map()으로 만든 배열은 가상돔인가요? Babel은 중첩 구조를 어떻게 처리하나요? 중첩 배열을 그대로 두면 왜 안 되나요?


✅ 핵심 정리

  • Babel은 JSX를 중첩된 React.createElement 호출로 번역함 (계층 구조 유지)
  • 중첩 배열은 JSX에서 map()을 쓸 때 발생하며, 이 배열은 가상돔이 아님
  • React는 createElement() 내부에서 이를 평탄화하여 가상돔 트리에 정규화함
  • 평탄화를 안 하면 렌더링, 비교, 유지보수가 복잡해짐

1. Babel은 계층 구조를 어떻게 처리하는가?

JSX 구조:

<div>
  <p>
    <span>Hello</span>
  </p>
</div>

Babel 변환 결과:

React.createElement(
  'div',
  null,
  React.createElement(
    'p',
    null,
    React.createElement('span', null, 'Hello')
  )
);
  • 중첩된 구조 = 중첩된 함수 호출
  • 각 자식은 부모의 children 세 번째 인자로 들어감
  • 이 구조 자체는 평탄화 대상이 아님

2. 중첩 배열이 생기는 상황: JSX + map()

<ul>
  {[<li>1</li>, [<li>2</li>, <li>3</li>], <li>4</li>]}
</ul>

Babel 변환 시:

React.createElement(
  'ul',
  null,
  [
    React.createElement('li', null, '1'),
    [
      React.createElement('li', null, '2'),
      React.createElement('li', null, '3')
    ],
    React.createElement('li', null, '4')
  ]
)
  • 이 구조는 가상돔 객체들(VNode) 을 담고 있는 일반적인 JavaScript 배열임. 즉, 가상돔으로 구성된 JS 배열.
  • 👉 하지만 여기서 중요한 건 “중첩 배열 구조” 에 있다는 것임. 가상돔이 맞긴 하지만, React 내부의 처리 흐름에선 이걸 그대로 쓰기엔 번거롭다.
  • 렌더링 전까지는 배열로 남아 있음
  • createElement에서 이를 평탄화하여 1차원 배열로 정리함

3. 왜 중첩 배열을 그대로 두면 안 되는가?

📌 복잡한 렌더링 로직 유발

// 평탄화 안 하면 이렇게 복잡해짐
function render(children) {
  children.forEach(child => {
    if (Array.isArray(child)) {
      render(child); // 재귀적으로 호출
    } else {
      mount(child);
    }
  });
}

✅ 평탄화 후엔 이렇게 단순해짐

const flatChildren = children.flat(Infinity);
flatChildren.forEach(mount);
  • 배열 타입인지 확인 필요 없음
  • 재귀 X → 선형 반복으로 DOM 생성 가능

📌 비교 알고리즘 비효율성

  • 중첩 배열: 변경 추적 시 구조까지 비교해야 해서 느림
  • 평평한 배열: key 기반 선형 비교 가능 → 빠름

📌 실제 DOM 구조와 일치시키기

  • DOM은 직속 자식만 평평하게 가짐
  • 가상돔도 이를 모사해야 실제 렌더링과 정확히 일치함

4. createElement에서의 평탄화 처리 방식

function createElement(
  type: string | Function,
  props: HTMLAttributes | null,
  ...children: (Child | Child[])[]
): Readonly<VNode> {
  const { ref, key, ...rest } = props ?? {};
  const normalizedChildren: Children = Array.isArray(children)
    ? (children.flat(Infinity) as Child[])
    : null;
  • flat(Infinity)로 중첩 배열 모두 펼침
  • 결과: children이 항상 1차원 배열로 정리됨

✅ 결론: 평탄화는 '미리 정리하기'이다

중첩 배열은 가상돔이 아니며, createElement가 이를 가상돔으로 변환하는 과정에서 평탄화가 발생합니다. 이 과정을 통해 React는 렌더링, 비교, 업데이트 등 모든 작업을 단순한 규칙에 따라 빠르게 처리할 수 있게 됩니다.

  • JSX 구조는 중첩 함수 호출로 → 평탄화 필요 없음
  • map()으로 생긴 배열은 실제 배열이므로 → 반드시 평탄화 필요
  • React는 이를 createElement 안에서 처리하여 모든 자식을 1차원 가상돔 배열로 정규화
  • 그 결과, 렌더링과 diff 알고리즘이 단순화되고, DOM 구조와 일관성 유지 가능

이 원칙은 바닐라 JS로 리액트 렌더러를 직접 만들 때도 반드시 따라야 할 핵심입니다.

profile
앵맹!

0개의 댓글