웹 개발의 변화와 React의 등장
문서 전달 방식과 UI 상호작용을 구분하고, 상태 동기화 문제에서 React의 컴포넌트·렌더링 모델이 나온 배경을 이해합니다.
처음 웹 개발을 시작하면 화면만 잘 나오면 된 것 아닌가 하는 생각이 자연스럽게 듭니다. 작은 페이지라면 HTML과 CSS에 약간의 자바스크립트를 더하는 것으로 충분할 수 있습니다. 하지만 같은 데이터가 목록과 배지, 버튼처럼 여러 화면 조각에 동시에 나타나고 사용자의 동작에 따라 계속 바뀌면 문제가 달라집니다. 개발자는 데이터뿐 아니라 어떤 DOM을 어떤 순서로 바꿨는지까지 함께 추적해야 하고, 버튼 하나를 바꿨는데 다른 영역은 이전 값을 보여 주거나 데이터는 갱신됐는데 화면 일부가 그대로 남는 버그가 이때 생깁니다.
React는 이런 UI 구조의 문제를 다루는 라이브러리입니다. 화면을 컴포넌트(component)로 나누고, props와 state를 입력으로 받아 지금 보여 줄 UI를 계산하도록 돕습니다. state는 화면이 기억해야 하는 데이터이고, props는 부모가 컴포넌트에 넘겨주는 입력입니다. 두 값의 실제 문법은 다음 글의 작은 예제에서 다룹니다.
이 장에서는 HTML의 요소와 속성, CSS의 선택자, 자바스크립트의 함수와 객체, 이벤트를 알고 있다고 가정합니다. 아직 낯설다면 웹 입문 교재의 HTML 구조와 문법과 자바스크립트 기초를 먼저 읽어 보세요.
React · UI와 실행의 관계
같은 상태가 여러 UI를 바꾸면 동기화가 필요합니다
장바구니 수량이 바뀌면 목록·합계·배지·버튼이 함께 바뀝니다. 현재 props와 state에서 각 컴포넌트의 UI를 계산하는 모델을 비교합니다.
정적 사이트·서버 렌더링·SPA는 서로 다른 전달 구조입니다. 이 그림은 상태 동기화 부담의 흐름만 설명하며, 모든 페이지에 React가 필요하다는 뜻이 아닙니다.
위 그림은 문서 중심의 화면에서 React의 UI 모델에 이르는 흐름을 네 단계로 보여 줍니다. 정적 파일이든 서버가 만든 HTML이든 HTML과 CSS로 문서를 보여 주는 화면에서 시작해, 이벤트와 요청으로 DOM 일부를 바꾸는 부분적인 상호작용이 더해집니다. 그러다 하나의 데이터가 여러 UI에 영향을 주면 그 모두를 맞추는 동기화 부담이 생기고, React는 컴포넌트와 state, props로 UI를 계산하는 모델로 이 부담을 줄입니다.
그림의 설명처럼 이 흐름은 상태 동기화의 부담이 어떻게 커지는지를 보여 줄 뿐, 모든 페이지에 React가 필요해졌다는 시간표가 아닙니다. 또 정적 사이트와 서버 렌더링, SPA를 같은 기준으로 나눈 단계로 읽지도 않습니다. 이 차이는 바로 다음 절에서 살펴봅니다.
문서 전달 방식과 화면 상호작용은 다른 축입니다
초기의 많은 웹사이트가 문서 열람 중심이었고, 페이지를 옮길 때마다 전체 문서를 다시 받았던 것은 사실입니다. 다만 정적 웹, 서버 렌더링, 동적 UI, SPA는 같은 기준으로 나뉘는 말이 아닙니다.
정적 사이트는 미리 만들어 둔 HTML 같은 파일로 요청에 응답하며, 그 문서에도 자바스크립트로 상호작용을 넣을 수 있습니다. 서버 렌더링 사이트는 요청과 데이터에 따라 서버가 HTML을 만들 수 있어서, 페이지를 옮길 때 전체 문서를 다시 받더라도 내용은 동적으로 만들어질 수 있습니다. SPA(Single Page Application)는 보통 첫 문서를 받은 뒤 클라이언트의 자바스크립트가 라우팅과 화면 일부의 갱신을 맡는 구조를 가리킵니다.
현대의 웹 애플리케이션은 이 방식들을 섞어 쓰기도 합니다. 서버에서 처음 HTML을 만들고 브라우저에서 상호작용을 이어 갈 수 있으며, React도 SPA만을 위한 도구는 아닙니다. 실제로 일어난 큰 변화는, 브라우저가 문서를 보여 주는 데서 그치지 않고 입력과 요청, 필터, 편집처럼 오래 이어지는 UI 상태를 다루게 되었다는 점입니다.
직접 DOM을 고칠 때 생기는 동기화 부담
작은 메뉴나 독립된 탭처럼 상태가 단순한 기능은 순수 자바스크립트로도 명확하게 구현할 수 있으며, DOM을 직접 조작하는 방식 자체가 잘못된 것은 아닙니다. 어려움은 하나의 상태가 여러 UI에 영향을 줄 때 커집니다. 예를 들어 장바구니의 수량이 바뀌면 목록의 수량과 합계, 헤더의 배지, 결제 버튼의 상태를 모두 같은 값에 맞춰야 합니다. 이벤트 처리기마다 필요한 요소를 찾아 따로 고치면, 갱신 경로가 늘어날수록 빠뜨린 DOM 변경을 찾기 어려워집니다.
명령형 DOM 갱신과 React 렌더링 비교 — 상태 추적
장바구니 수량 하나의 변경이 목록/합계/배지/버튼에 반영되는 직접 DOM 경로와 state/render/commit 경로를 독립 비교한다. 현재 값/요청/DOM의 차이를 추적할 가치가 있다.
React · UI와 실행의 관계
수동 DOM 갱신과 상태 기반 렌더링은 책임을 다르게 나눕니다
같은 장바구니 수량을 읽는 화면 영역을 비교합니다. 직접 갱신에서는 각각의 DOM 수정 경로를 맞추며 React에서는 setter 요청 뒤 render와 commit을 구분합니다.
두 경로는 비교 대상이며 위에서 아래로 모두 실행하는 순서가 아닙니다. React의 render는 컴포넌트 호출로 UI를 계산하며 commit은 필요한 DOM 변경을 반영합니다. 상태 소유권과 입력 검증은 개발자가 설계합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 본문의 단가 1,200원·수량 2→3 예제를 선택합니다. 직접 DOM 갱신과 React 경로는 같은 입력과 결과의 독립 비교이며, 주문 가능 여부와 서버 가격 확정은 이 화면 관찰 밖입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 독립적인 두 갱신 경로는 각각 노드 4개와 간선 3개로 구성된다(총 노드 8개·간선 6개). 왼쪽은 데이터 뒤 관련 DOM을 직접 갱신하고 일관성을 확인한다. 오른쪽은 setter 요청→render의 UI 계산→commit의 DOM 반영이다. 두 경로 사이에 간선을 그리지 않으며 모바일 위아래 비교도 순차 실행이 아니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 수량 2의 화면 | qty: 2 / 합계·배지·버튼: 2,400원 · 2 · 활성 | 수량 2에서 합계는 2,400원이고 배지는 2, 결제 버튼은 활성이다 | 합계 1,200×2와 버튼 조건 qty>0은 같은 수량에서 계산한다 |
| 수량 3으로 변경 요청 | 요청 qty: 3 / React commit 전 DOM: 수량 2 · 합계 2,400원 | 직접 경로는 관련 DOM을 수정하고 React 경로는 수량 3을 요청해 다음 UI를 계산한다 | setter 요청과 render 계산은 commit 전 DOM을 곧바로 바꾸지 않는다. 직접 DOM 경로와 독립 비교다 |
| 수량 3의 화면 | qty·배지: 3 · 3 / 합계·버튼: 3,600원 · 활성 | 직접 DOM 갱신 또는 React commit 뒤 수량과 배지는 3, 합계는 3,600원이다 | React commit 또는 직접 DOM 갱신이 관련 표시를 같은 qty 3에 맞춘다 |
명령형 DOM 갱신과 React 렌더링 비교 — 상태 추적
장바구니 수량 하나의 변경이 목록/합계/배지/버튼에 반영되는 직접 DOM 경로와 state/render/commit 경로를 독립 비교한다. 현재 값/요청/DOM의 차이를 추적할 가치가 있다.
React · UI와 실행의 관계
수동 DOM 갱신과 상태 기반 렌더링은 책임을 다르게 나눕니다
같은 장바구니 수량을 읽는 화면 영역을 비교합니다. 직접 갱신에서는 각각의 DOM 수정 경로를 맞추며 React에서는 setter 요청 뒤 render와 commit을 구분합니다.
두 경로는 비교 대상이며 위에서 아래로 모두 실행하는 순서가 아닙니다. React의 render는 컴포넌트 호출로 UI를 계산하며 commit은 필요한 DOM 변경을 반영합니다. 상태 소유권과 입력 검증은 개발자가 설계합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 본문의 단가 1,200원·수량 2→3 예제를 선택합니다. 직접 DOM 갱신과 React 경로는 같은 입력과 결과의 독립 비교이며, 주문 가능 여부와 서버 가격 확정은 이 화면 관찰 밖입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 독립적인 두 갱신 경로는 각각 노드 4개와 간선 3개로 구성된다(총 노드 8개·간선 6개). 왼쪽은 데이터 뒤 관련 DOM을 직접 갱신하고 일관성을 확인한다. 오른쪽은 setter 요청→render의 UI 계산→commit의 DOM 반영이다. 두 경로 사이에 간선을 그리지 않으며 모바일 위아래 비교도 순차 실행이 아니다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 수량 2의 화면 | qty: 2 / 합계·배지·버튼: 2,400원 · 2 · 활성 | 수량 2에서 합계는 2,400원이고 배지는 2, 결제 버튼은 활성이다 | 합계 1,200×2와 버튼 조건 qty>0은 같은 수량에서 계산한다 |
| 수량 3으로 변경 요청 | 요청 qty: 3 / React commit 전 DOM: 수량 2 · 합계 2,400원 | 직접 경로는 관련 DOM을 수정하고 React 경로는 수량 3을 요청해 다음 UI를 계산한다 | setter 요청과 render 계산은 commit 전 DOM을 곧바로 바꾸지 않는다. 직접 DOM 경로와 독립 비교다 |
| 수량 3의 화면 | qty·배지: 3 · 3 / 합계·버튼: 3,600원 · 활성 | 직접 DOM 갱신 또는 React commit 뒤 수량과 배지는 3, 합계는 3,600원이다 | React commit 또는 직접 DOM 갱신이 관련 표시를 같은 qty 3에 맞춘다 |
위 그림은 같은 장바구니 수량을 읽는 화면을 두 방식으로 갱신하는 경로를 나란히 비교합니다. 두 경로는 서로 이어지지 않으며, 위에서 아래로 모두 실행하는 순서도 아닙니다. 왼쪽의 직접 DOM 갱신에서는 클릭이나 입력, 요청 같은 사용자 동작이 앱의 값을 바꾸면, 목록과 합계, 배지와 버튼의 DOM을 각각 고친 뒤 빠진 갱신이 없는지 일관성을 직접 확인해야 합니다.
오른쪽의 React 방식에서는 이벤트 함수가 setter를 호출해 state의 갱신을 요청하면 렌더링이 예약됩니다. React는 render 단계에서 컴포넌트를 호출해 다음 UI를 계산하고, commit 단계에서 이전 결과와 달라진 부분만 DOM에 반영합니다(React의 render와 commit 설명). 그래서 render가 DOM을 전부 다시 만드는 작업이라는 뜻은 아닙니다.
그림의 설명처럼, 이 모델에서도 상태를 어디에 둘지와 입력을 어떻게 검증할지는 개발자가 설계합니다. 어떤 값을 state로 저장하고 컴포넌트를 어디서 나눌지도 마찬가지입니다. React는 구조를 제공하지만 올바른 상태 설계를 대신해 주지는 않습니다.
한 종류의 상품 가격을 1,200원으로 고정하고 장바구니 수량을 2에서 3으로 바꾼 경우를 생각해 봅니다. 수량 표시와 헤더 배지는 둘 다 3, 합계는 1,200 × 3 = 3,600원이어야 하고, 결제 버튼은 수량이 0보다 크므로 활성 상태여야 합니다. 이때 합계를 따로 저장해 계속 고치기보다 현재 수량에서 계산하면, 서로 다른 합계가 저장될 여지를 줄일 수 있습니다. 실제로 주문할 수 있는지와 가격의 확정은 서버가 판단할 별도의 조건이며, 이 사례는 화면 안의 동기화만 다룹니다.
UI 라이브러리가 유리해지는 조건
UI 라이브러리를 도입하면 컴포넌트 단위의 구조와 선언적 렌더링, 생태계의 도구를 얻는 대신 학습과 빌드, 의존성의 비용이 생깁니다. 그래서 기능의 개수보다, 화면이 어떻게 바뀌고 팀이 그 변화를 어떻게 관리해야 하는지를 먼저 봐야 합니다.
ADOPTION CONDITIONS
React 도입은 상태의 연결 범위와 변경 비용으로 판단합니다
고정 콘텐츠·작은 상호작용부터 상태 공유·반복 UI·팀 변경까지 비교합니다. 이점과 학습·빌드·의존성 비용을 함께 보며, 서버 렌더링과 함께 사용할 수도 있습니다.
핵심 신호: 한 값이 여러 화면 영역에 영향
장바구니 수량 하나가 목록·합계·배지·버튼을 바꾼다면 상태 소유권과 갱신 경로를 먼저 정합니다.
| 화면 조건 | 관찰할 신호 | 단순한 시작점 | React의 이점 또는 비용 |
|---|---|---|---|
| 고정 콘텐츠 중심 | 읽기 흐름이 중심이고 변하는 로컬 상태가 거의 없다. | 의미 있는 HTML, CSS, 필요한 작은 스크립트 | 컴포넌트 런타임과 빌드 구성이 오히려 추가 비용일 수 있다. |
| 독립된 작은 상호작용 | 메뉴나 탭처럼 한 영역 안에서 상태가 끝난다. | 점진적 향상 또는 작은 JavaScript 모듈 | 동작이 반복·확장될 때 컴포넌트화의 가치가 생긴다. |
| 여러 영역이 상태 공유 | 하나의 값이 목록, 합계, 배지, 버튼에 함께 영향을 준다. | 상태의 단일 출처와 갱신 경로부터 설계 | state를 기준으로 다음 UI를 계산해 수동 DOM 동기화를 줄인다. |
| 반복되는 대화형 UI | 카드, 폼, 모달, 목록을 데이터로 반복하고 조합한다. | 재사용 경계와 입력·출력 계약 정의 | 컴포넌트와 props로 구조를 드러내고 테스트 단위를 나눌 수 있다. |
| 변경이 잦은 팀 제품 | 여러 사람이 화면 책임을 나누고 기능을 계속 확장한다. | 팀 규칙, 품질 기준, 유지 주체 합의 | 공통 모델과 생태계를 얻지만 학습·의존성·업그레이드 비용도 맡는다. |
고정 콘텐츠 중심
관찰 읽기 중심이고 변하는 로컬 상태가 거의 없습니다.
시작 의미 있는 HTML·CSS와 필요한 작은 스크립트
판단 React 런타임과 빌드 구성이 추가 비용일 수 있습니다.
독립된 작은 상호작용
관찰 메뉴·탭처럼 한 영역 안에서 상태가 끝납니다.
시작 점진적 향상 또는 작은 JavaScript 모듈
판단 동작이 반복·확장되면 컴포넌트화의 가치가 생깁니다.
여러 영역이 상태 공유
관찰 한 값이 목록·합계·배지·버튼에 함께 영향을 줍니다.
시작 상태의 단일 출처와 갱신 경로부터 설계
판단 state에서 다음 UI를 계산해 수동 DOM 동기화를 줄입니다.
반복되는 대화형 UI
관찰 카드·폼·모달·목록을 데이터로 반복하고 조합합니다.
시작 재사용 경계와 입력·출력 계약 정의
판단 컴포넌트와 props로 구조·테스트 단위를 나눕니다.
변경이 잦은 팀 제품
관찰 여러 사람이 화면 책임을 나누고 기능을 계속 확장합니다.
시작 팀 규칙·품질 기준·유지 주체 합의
판단 공통 모델과 생태계를 얻고 학습·의존성·업그레이드 비용을 맡습니다.
위 그림은 화면의 조건마다 살펴볼 신호와 단순한 시작점, React를 쓸 때의 이점과 비용을 표로 비교합니다. 읽기 흐름이 중심이고 바뀌는 상태가 거의 없는 고정 콘텐츠라면 의미 있는 HTML과 CSS, 필요한 작은 스크립트로 시작하며, 이때는 컴포넌트 런타임과 빌드 구성이 오히려 추가 비용일 수 있습니다. 메뉴나 탭처럼 한 영역 안에서 상태가 끝나는 작은 상호작용은 점진적 향상이나 작은 자바스크립트 모듈로 충분하고, 그 동작이 반복되고 커질 때 컴포넌트로 나누는 가치가 생깁니다.
하나의 값이 목록과 합계, 배지, 버튼에 함께 영향을 준다면 상태를 한 곳에 두고 갱신 경로를 먼저 설계하며, 이때 React는 state에서 다음 UI를 계산해 수동 DOM 동기화를 줄여 줍니다. 그림이 핵심 신호로 꼽는 것도 이 경우입니다. 카드와 폼, 모달, 목록을 데이터로 반복하고 조합하는 화면은 컴포넌트의 재사용 단위와 입력·출력을 정해 두면, 컴포넌트와 props로 구조를 드러내고 테스트 단위를 나눌 수 있습니다. 여러 사람이 화면을 나눠 맡고 기능을 계속 늘리는 제품이라면 공통 모델과 생태계를 얻는 대신, 팀 규칙과 품질 기준, 누가 유지할지를 정하고 학습과 업그레이드의 비용도 감당해야 합니다.
정리하면, 고정 콘텐츠가 중심이고 상호작용이 작을 때는 HTML과 CSS, 작은 스크립트가 더 단순할 수 있습니다. 반대로 같은 상태를 여러 컴포넌트가 읽고, 반복되는 UI가 많으며, 화면의 변경을 여러 사람이 나눠 맡는다면 React의 구조적인 이점이 커집니다. 도구의 유행보다 상태가 얼마나 넓게 연결되는지와 변경 비용을 먼저 보세요.
Facebook에서 공개된 React
Facebook은 데이터가 계속 바뀌는 큰 UI를 컴포넌트로 조합하고 일관되게 갱신하기 위해 React를 만들었고, 2013년에 오픈 소스로 공개했습니다. 당시의 공식 소개에서도 React는 MVC 프레임워크가 아니라 조합할 수 있는 사용자 인터페이스를 만드는 라이브러리로 설명되었습니다(React 블로그의 2013년 글). 핵심 제안은 개발자가 DOM 변경 명령을 일일이 나열하는 대신, 데이터에 맞는 UI가 무엇인지를 컴포넌트로 표현하게 하는 것이었습니다.
React가 모든 웹 문제를 해결하지는 않습니다. 하지만 컴포넌트와 state, render와 commit이라는 모델은 상호작용이 많은 UI를 설명하고 변경 범위를 나누는 데 쓸모 있는 공통 언어가 됩니다.
정리
- 정적 사이트, 서버 렌더링, SPA는 문서를 전달하는 방식의 차이이며, 화면에 상호작용이 얼마나 있는지와는 다른 기준입니다.
- 하나의 상태가 여러 화면 영역에 영향을 주면, DOM을 직접 고치는 방식에서는 빠진 갱신을 찾기 어려워집니다.
- React는 setter로 state의 갱신을 요청하면 render에서 다음 UI를 계산하고, commit에서 달라진 부분만 DOM에 반영합니다.
- 합계처럼 다른 값에서 계산할 수 있는 값은 따로 저장하기보다 계산하면 서로 어긋날 여지가 줄어듭니다.
- React의 도입은 상태가 얼마나 넓게 연결되는지와 변경 비용으로 판단하며, 학습과 빌드, 의존성의 비용도 함께 따집니다.
- 상태를 어디에 두고 입력을 어떻게 검증할지는 React가 아니라 개발자가 설계합니다.
연습 문제
1. 수량을 0으로 바꾼 장바구니
가격이 1,200원인 상품 하나가 담긴 장바구니에서 수량을 3에서 0으로 바꾸었습니다. 그런데 목록만 0이 되고, 배지는 3, 합계는 3,600원, 결제 버튼은 활성 상태로 남았습니다. 지금 바뀌어야 할 세 결과와 그 계산 과정을 적어 보세요.
해설 보기
배지는 0, 합계는 1,200 × 0 = 0원이어야 하고, 결제 버튼은 수량이 0이므로 비활성 상태여야 합니다. 세 화면 영역이 같은 수량을 읽는데 그중 일부의 갱신이 빠진 문제입니다.
컴포넌트가 하나의 state에서 화면을 계산하면 이런 변경 경로를 읽기 쉬워집니다. 다만 그 state를 어느 컴포넌트가 가질지와 수량에 어떤 값을 허용할지는 직접 정해야 합니다.
2. 메뉴 하나뿐인 안내 페이지
고정된 안내문에 펼치고 접는 메뉴 하나만 있는 페이지가 있습니다. 1번의 장바구니 문제를 들어 이 페이지에도 React가 꼭 필요하다고 결론 내릴 수 있을까요?
해설 보기
그렇게 결론 내릴 수 없습니다. 메뉴의 열림 상태는 한 영역 안에서 끝나므로 작은 스크립트로도 충분히 관리할 수 있습니다.
장바구니의 동기화 부담은 하나의 값이 여러 영역에 영향을 줄 때 생기는 문제라서, 이를 모든 페이지에 React를 도입할 이유로 일반화하지 않습니다. 이 페이지에 React를 들이면 얻는 이점보다 런타임과 빌드 구성의 비용이 더 클 수 있습니다.
다음 글 React의 핵심 개념과 장점에서는 이 배경 위에서 선언적 방식과 컴포넌트, 가상 DOM, 단방향 데이터 흐름 같은 React의 핵심 개념이 실제 코드에서 어떤 역할을 하는지 살펴봅니다.