React의 본질: 선언적 UI 모델
선언적 UI, JSX 의미, 컴포넌트 트리와 상태 흐름, DOM 조작을 배제하는 이유를 정리
React를 “자바스크립트 라이브러리”로만 보면 절반만 이해한 거다. React는 결국 UI를 어떤 방식으로 기술(describe)하고, 변경을 어떤 규칙으로 반영(reconcile)할지에 대한 모델이다. 핵심은 “DOM을 어떻게 조작하느냐”가 아니라 UI를 어떻게 선언하느냐다.
선언적 UI(Declarative UI)의 의미
개념
선언적 UI란 “어떻게(How)”가 아니라 “무엇을(What)” 적는 방식이다.
- 명령형(Imperative): “버튼을 만들고, 클릭 이벤트를 붙이고, 클릭하면 이 DOM을 지워라”
- 선언형(Declarative): “상태가 A면 버튼을 보여주고, 상태가 B면 다른 화면을 보여줘”
React는 UI를 다음 같은 형태로 취급한다.
UI = f(state, props)
즉, 현재 상태/입력값이 주어지면 UI가 결과로 계산된다.
왜 중요한가
UI를 선언적으로 만들면 다음이 가능해진다.
- 상태가 바뀌면 UI도 자동으로 바뀐다
- UI 변경 로직이 “DOM 조작 코드”가 아니라 “상태 전환”으로 수렴한다
- 조건, 리스트, 분기 같은 복잡도가 UI 코드에서 덜 흩어진다
언제 문제가 되는가
선언적 모델을 이해 못 하면 아래로 빠진다.
- “버튼 클릭하면
document.querySelector로 class 붙여야지” - “렌더 후에 요소 찾아서 값 넣어야지”
- “DOM을 직접 바꾸면 빠르지 않나?”
이 순간 React의 장점(예측 가능성, 유지보수성)을 스스로 버린다.
JSX가 HTML 템플릿이 아닌 이유
개념
JSX는 “HTML처럼 보이는 문법”일 뿐이고, 본질은 JavaScript 표현식으로 UI를 기술하는 방식이다.
- 템플릿(HTML)은 보통 “문자열 기반” 또는 “별도 DSL”
- JSX는 JS 안에서 UI를 구성하는 표현식
JSX는 결국 다음 같은 코드로 변환된다(개념적으로).
1
<div className="box">{title}</div>
→ “title이라는 값에 따라 UI 결과가 달라지는 함수의 반환값”
왜 중요한가
JSX가 템플릿이 아닌 순간부터 다음이 자연스러워진다.
- 조건부 렌더링이 “if로 DOM을 숨김”이 아니라 “조건에 따라 반환값이 바뀜”
- 리스트 렌더링이 “DOM append 반복”이 아니라 “배열 → 컴포넌트 배열로 매핑”
- UI 조합이 “문자열 결합”이 아니라 “컴포넌트 합성”
즉 JSX는 “HTML 대체”가 아니라 UI를 값으로 다루기 위한 문법이다.
언제 문제가 되는가
JSX를 템플릿으로 착각하면 이런 실수가 잦아진다.
- 렌더링 로직을 JSX 밖으로 빼지 못하고 지저분한 조건을 JSX 안에 늘어놓음
- “UI 조작”을 하려고 DOM API로 돌아감
- 상태 변화보다 DOM 변화를 먼저 생각함
컴포넌트 트리(Component Tree) 개념
개념
React 앱은 결국 컴포넌트 트리다.
- 루트(App)에서 시작
- 부모가 자식을 포함
- 각 컴포넌트는 자신의 props/state로 UI를 계산
- 결과적으로 트리 전체가 화면을 정의
트리는 “구조”인 동시에 “데이터 흐름”이기도 하다.
- 기본 흐름: 부모 → 자식(props)
- 이벤트 흐름: 자식 → 부모(callback props)
왜 중요한가
컴포넌트 트리를 이해하면 다음을 바로 정리할 수 있다.
- 상태는 어디에 두는가? (상태의 소유자 = 그 상태를 사용하는 컴포넌트들의 최소 공통 부모)
- 왜 Prop Drilling이 생기는가?
- 왜 Context/Redux 같은 도구가 필요한가?
트리 관점이 없으면 상태 관리가 “감”이 된다.
언제 문제가 되는가
컴포넌트 트리를 의식하지 않으면 흔히 이런 결과가 나온다.
- 상태가 여기저기 흩어져 동기화가 깨짐
- 공통 상태를 억지로 여러 컴포넌트에 복제(파생 state 남발)
- “어디서 바뀌는지” 추적 불가
React가 DOM을 직접 조작하지 않는 이유
개념
React는 기본적으로 DOM을 직접 조작하지 않는다. 대신 UI를 계산한 결과(가상 표현)를 기준으로 “필요한 변경만 반영”한다.
중요한 건 “Virtual DOM이 빠르다”가 아니라, React가 DOM 조작을 직접 맡으면 일관된 규칙으로 UI를 유지할 수 있다는 점이다.
왜 중요한가
DOM을 직접 만지면 문제가 생긴다.
- 상태와 DOM이 서로 다른 진실(source of truth)이 된다
- 렌더링 결과와 실제 DOM이 어긋날 수 있다
- 특정 시점에만 맞는 “임시 패치”가 누적된다
React의 원칙은 단순하다.
UI의 진실은 상태(state)다. DOM은 상태에서 계산된 결과다.
언제 문제가 되는가 (대표 케이스)
1) React가 관리하는 DOM을 외부에서 조작
1
document.querySelector(".tab").classList.add("active");
이런 코드는 다음 렌더에서 React가 다시 그려버리면 사라질 수 있다. “가끔 된다”가 더 위험하다.
2) 입력값/포커스/스크롤 같은 DOM 상태를 무시
DOM이 실제로 필요한 경우도 있다(포커스, 측정, 스크롤). 이때는 규칙이 있다.
- DOM 접근은
ref로 한다 - 접근 시점은
useEffect/이벤트 핸들러에서 한다 - DOM을 “진실”로 두지 말고 “보조 수단”으로만 쓴다
React 사고방식 vs 기존 DOM 조작 방식
| 구분 | 전통 DOM(명령형) | React(선언형) |
|---|---|---|
| 핵심 질문 | “DOM을 어떻게 바꾸지?” | “상태가 이러면 UI는 무엇이지?” |
| 변경 단위 | 요소/속성/클래스 | state/props |
| 복잡도 증가 시 | DOM 조작 코드가 흩어짐 | 상태 전환으로 수렴 |
| 유지보수 | 변경 경로 추적 어려움 | 데이터 흐름 기준 추적 가능 |
“컴포넌트는 함수다”의 진짜 의미
개념
컴포넌트는 “렌더링 시 호출되는 함수”다.
- 입력:
props - 내부 상태:
state - 출력: “현재 상태에서의 UI”
1
UI = Component(props, state)
왜 중요한가
함수로 이해하면 React가 왜 이렇게 동작하는지 설명이 된다.
- 렌더는 “다시 그린다”가 아니라 “다시 계산한다”
- 상태 업데이트는 “DOM 수정”이 아니라 “다시 계산하게 만드는 트리거”
- 동일 입력이면 동일 출력이라는 원칙(예측 가능성)이 생긴다
언제 문제가 되는가
컴포넌트를 함수로 이해 못 하면 이런 오해가 생긴다.
- “컴포넌트는 한 번 만들어지고 계속 살아있다”라는 착각
- 렌더링 중에 부작용(HTTP 요청, localStorage write 등)을 해버림
- 렌더를 “생성자/초기화”처럼 써서 버그 유발
정리: React는 UI 변경이 아니라 UI 계산 모델이다
- 선언적 UI: 무엇을 보여줄지 선언
- JSX: 템플릿이 아니라 UI를 값으로 만드는 표현식
- 컴포넌트 트리: UI 구조 + 데이터 흐름의 뼈대
- DOM 직접 조작 금지: 상태를 단일 진실로 유지하기 위해
이걸 잡으면 이후 훅, 상태 관리, 라우팅, 서버 컴포넌트까지 전부 같은 축으로 이해된다.
