React의 Virtual DOM이 자식 요소들을 중첩 배열이 아닌 1차원 배열로 "평탄화(flatten)"하는 이유는 단순히 구현상의 편의를 넘어서, 성능 최적화와 예측 가능한 렌더링을 달성하기 위한 핵심 전략 중 하나입니다. 이 문서에서는 그 이유를 세 가지 측면에서 단계적으로 정리합니다.
React는 상태(state)나 props가 변경되었을 때, 이전 Virtual DOM과 새로운 Virtual DOM을 비교(diff)하여 변경된 부분만 실제 DOM에 반영하는 재조정(Reconciliation) 과정을 거칩니다.
이 때 사용하는 알고리즘이 바로 Diffing Algorithm이며, 이는 다음 조건을 충족해야 성능이 극대화됩니다:
key를 통한 변경 감지 가능const children = [child1, [child2, child3], child4];
이런 구조라면 비교 알고리즘은 다음과 같이 복잡한 과정을 거쳐야 합니다:
children[0]은 단순 비교children[1]은 배열 → 다시 내부를 순회해야 함 (재귀 호출)const children = [child1, child2, child3, child4];
이렇게 만들면:
key를 기준으로 변경된 요소만 빠르게 탐지 가능✅ 요약: 중첩 배열 → 재귀로 느림, 평탄 배열 → 순차 비교로 빠름
JSX에서는 다음과 같은 유연한 표현이 자주 사용됩니다:
<ul>
{[<li>첫째</li>, items.map(item => <li>{item}</li>)]}
</ul>
이 경우 children은 다음처럼 중첩 배열이 됩니다:
[ <li>첫째</li>, [<li>둘째</li>, <li>셋째</li>] ]
하지만 실제 DOM 구조는 중첩 배열을 허용하지 않습니다. HTML의 부모-자식 관계는 항상 1차원 목록입니다. <ul> 태그 안에는 직속 자식 <li> 요소들이 평평하게 나열되어야 합니다.
React는 Virtual DOM이 실제 DOM 구조를 모방해야 하므로, 중첩 구조를 정규화(normalize)하여 평탄한 배열로 변환합니다.
✅ 요약: JSX는 중첩 배열을 쉽게 만들지만, 실제 DOM은 평탄 구조만 허용함. 따라서 Virtual DOM도 이를 따라야 함
Virtual DOM은 비교 외에도 여러 내부 처리를 수행합니다:
null, false, undefined 등의 필터링React.Fragment 등 특수 케이스 처리이 모든 로직에서 children이 중첩 배열이면 다음과 같은 복잡한 분기문이 필요합니다:
if (Array.isArray(child)) {
child.forEach(recurse);
} else {
process(child);
}
하지만 createElement 단계에서 이미 평탄화된 1차원 배열을 만들면:
for (let child of children) 같은 단순 루프로 처리 가능✅ 요약: 미리 평탄화하면 이후 모든 처리 과정이 단순, 안정, 예측 가능
React의 Virtual DOM에서 자식 목록을 평탄화하는 것은 다음과 같은 React의 철학과 맞닿아 있습니다:
이를 통해 개발자는 JSX의 자유도를 누리면서도, React 내부에서는 고도로 최적화된 렌더링 파이프라인이 작동하게 됩니다.