3분

React가 컴포넌트로 프론트엔드 아키텍처를 재구성한 방법

React는 컴포넌트 기반 UI, 선언적 렌더링, 상태 중심 뷰를 대중화해 페이지 중심 코드에서 재사용 가능한 시스템과 패턴으로 팀들의 작업 방식을 바꿨습니다.

React가 컴포넌트로 프론트엔드 아키텍처를 재구성한 방법

React가 도착했을 때 무엇이 바뀌었나

React는 단순히 새로운 라이브러리를 소개한 것이 아니라, 팀들이 “프론트엔드 아키텍처”라 부르는 것의 의미를 바꿨습니다. 실무적으로 프론트엔드 아키텍처는 UI 코드베이스를 대규모로 이해가능하게 유지하는 일련의 결정입니다: UI를 어떻게 나누는지, 데이터가 어떻게 흐르는지, 상태는 어디에 저장되는지, 사이드 이펙트(데이터 패칭 등)를 어떻게 처리하는지, 그리고 결과를 어떻게 테스트 가능하고 팀 전체에 일관되게 유지하는지 같은 것들입니다.

한 문장으로 정리한 컴포넌트 사고

컴포넌트 사고는 UI의 각 조각을 렌더링을 소유하고 다른 단위들과 조합해 전체 페이지를 구성할 수 있는 작고 재사용 가능한 단위로 다루는 것입니다.

React가 촉진한 변화들

React가 널리 퍼지기 전에는 많은 프로젝트가 페이지와 DOM 조작 중심으로 조직되어 있었습니다: “이 요소를 찾아 텍스트를 바꿔라, 이 클래스를 토글해라” 같은 식이었죠. React는 팀들을 다른 기본값으로 밀어넣었습니다:

  • 상태 중심 UI: 상태를 업데이트하면 UI가 그 결과로 갱신됩니다.
  • 연결보다는 컴포지션: 서로 관련 없는 파일을 이어붙이는 대신 컴포넌트를 중첩하고 결합해서 화면을 구성합니다.
  • 재사용을 1순위 목표로: 공유되는 UI 패턴은 복사된 마크업이 아니라 컴포넌트가 됩니다.

이 아이디어들은 일상의 작업을 바꿨습니다. 코드 리뷰는 "이 상태는 어디에 있어야 하나?"를 묻기 시작했고, 디자이너와 엔지니어는 공통의 컴포넌트 어휘로 정렬할 수 있게 되었으며 팀은 전체 페이지를 다시 작성하지 않고도 UI 빌딩 블록 라이브러리를 성장시킬 수 있었습니다.

React를 넘는 변화

팀이 나중에 다른 프레임워크로 옮기더라도 React가 만든 습관들은 남습니다: 컴포넌트 기반 아키텍처, 선언적 렌더링, 예측 가능한 데이터 흐름, 그리고 일회성 페이지 코드보다 재사용 가능한 디자인 시스템 컴포넌트를 선호하는 태도 등입니다. React는 이러한 패턴을 자연스럽게 만들었고, 그 영향은 넓은 프론트엔드 생태계에 퍼졌습니다.

React 이전: 페이지 중심 UI와 DOM-우선 코드

React 이전에는 많은 팀이 페이지 중심으로 인터페이스를 구축했습니다. 서버에서 렌더링되는 템플릿(PHP, Rails, Django, JSP 등)이 HTML을 생성하고, 그 위에 jQuery로 상호작용을 추가하는 방식이 일반적이었습니다.

전형적인 스택: 템플릿 + jQuery + 플러그인

페이지를 렌더링한 뒤 스크립트로 활성화(activate)했습니다: 날짜 선택기, 모달 플러그인, 폼 검증기, 캐러셀 등—각각 자신의 마크업 기대치와 이벤트 훅을 가졌습니다.

코드는 종종 DOM 노드를 찾아 핸들러를 붙이고 DOM을 변경하는 식이었고, 아무것도 망가지지 않기를 바라는 식이었습니다. UI가 커지면 진실의 원천(source of truth)은 조용히 DOM 자체가 되곤 했습니다.

동작이 계층에 걸쳐 흩어짐

UI 동작은 한 곳에 모여있지 않았습니다. 보통 다음으로 나뉘었습니다:

  • 서버 뷰(조건부 렌더링, 부분 템플릿, 기능 플래그)
  • HTML(data-* 속성, 인라인 핸들러, 숨겨진 필드)
  • JavaScript(jQuery 셀렉터, 전역 상태, 플러그인 초기화)

예를 들어 결제 요약 위젯 하나가 서버에서 부분적으로 만들어지고, AJAX로 업데이트되고, 플러그인으로 일부를 제어하는 식으로 나뉠 수 있었습니다.

흔한 고통 포인트

이 접근법은 작은 개선에는 잘 작동했지만 반복적인 문제를 낳았습니다:

  • UI 중복: 같은 “컴포넌트”가 여러 템플릿과 페이지에서 다시 구현됨
  • 상태 불일치: 로딩 스피너, 비활성화된 버튼, 오류 메시지 등이 동기화되지 않음
  • 취약한 DOM 코드: 사소한 마크업 변경으로 셀렉터와 이벤트 연결이 깨짐

초창기 MV* 프레임워크

Backbone, AngularJS, Ember 같은 프레임워크는 모델·뷰·라우팅으로 구조를 가져오려 했습니다—대부분 큰 개선이었지만, 많은 팀은 여전히 패턴을 섞어 쓰며 반복 가능한 단위로 UI를 구성할 더 단순한 방법에 대한 틈을 남겼습니다.

큰 아이디어: 상태의 함수로서의 UI

React의 가장 중요한 전환은 간단히 말하면 강력합니다: UI는 상태의 함수다. DOM을 "진실의 원천"으로 취급하고 수동으로 동기화하는 대신, 데이터를 진실의 원천으로 두고 UI를 그 결과로 취급합니다.

일상 앱에서 ‘상태’가 의미하는 것

상태는 화면이 의존하는 현재 데이터입니다: 메뉴가 열려 있는지, 폼에 입력된 텍스트, 리스트의 항목들, 선택된 필터 등입니다.

상태가 바뀌면 페이지를 뒤져 여러 DOM 노드를 업데이트할 필요가 없습니다. 상태를 업데이트하면 UI가 그에 맞춰 다시 렌더됩니다.

왜 수동 DOM 작업이 줄어드는가

전통적 DOM-우선 코드는 보통 흩어진 업데이트 로직을 만들었습니다:

  • 체크박스 변경 → 라벨 업데이트
  • 항목 추가 → 리스트와 빈 상태 메시지 업데이트
  • 폼 제출 → 버튼 비활성화, 스피너 표시, 오류 처리

React 모델에서는 이런 업데이트들이 렌더 출력의 조건이 됩니다. 화면은 주어진 상태에서 무엇이 보여져야 하는지에 대한 읽기 쉬운 설명이 됩니다.

작은 예시

function ShoppingList() {
  const [items, setItems] = useState([]);
  const [text, setText] = useState("");

  const add = () => setItems([...items, text.trim()]).then(() => setText(""));

  return (
    <section>
      <form onSubmit={(e) => { e.preventDefault(); add(); }}>
        <input value={text} onChange={(e) => setText(e.target.value)} />
        <button disabled={!text.trim()}>Add</button>
      </form>

      {items.length === 0 ? <p>No items yet.</p> : (
        <ul>{items.map((x, i) => <li key={i}>{x}</li>)}</ul>
      )}
    </section>
  );
}

빈 메시지, 버튼의 disabled 상태, 리스트 내용이 모두 itemstext에서 파생된다는 점을 주목하세요. 이것이 아키텍처적 이득입니다: 데이터 구조와 UI 구조가 정렬되어 화면을 이해하고 테스트하며 확장하기 쉬워집니다.

컴포넌트: 새로운 빌딩 블록

실전 배포 수준으로 만드세요
사용자에게 공개할 준비가 되면 프로젝트를 커스텀 도메인에 올리세요.

React는 “컴포넌트”를 기본 UI 작업 단위로 만들었습니다: 명확한 인터페이스 뒤에 마크업, 동작, 스타일링 훅을 묶은 작고 재사용 가능한 조각입니다.

HTML 템플릿, 이벤트 리스너, CSS 셀렉터를 관련 없는 파일에 흩어놓는 대신, 컴포넌트는 이동하는 부분들을 가깝게 유지합니다. 모든 것을 한 파일에 넣어야 한다는 의미는 아니지만, 코드는 사용자가 보고 상호작용하는 것을 중심으로 조직됩니다.

컴포넌트가 실제로 의미하는 것

실무적 관점에서 컴포넌트는 보통 다음을 포함합니다:

  • 구조(무엇을 렌더링하는가)
  • 상호작용(핸들러, 상태, 이펙트)
  • 스타일링 훅(클래스 이름, 변형, 토큰)

중요한 전환은 “이 div를 업데이트하라”는 사고에서 “이 상태에서 버튼을 비활성화된 상태로 렌더하라”는 사고로 바뀐다는 점입니다.

캡슐화 = 유지보수 용이성과 명확한 소유권

컴포넌트가 적은 수의 props(입력)와 이벤트/콜백(출력)을 노출하면 내부를 바꿔도 앱의 다른 부분을 망가뜨리지 않고 개선할 수 있습니다. 팀은 특정 컴포넌트나 폴더(예: "checkout UI")를 소유하고 자신 있게 개선할 수 있습니다.

캡슐화는 우발적 결합을 줄입니다: 전역 셀렉터가 줄고, 크로스-파일 사이드 이펙트가 줄며, "왜 이 클릭 핸들러가 작동을 멈췄지?" 같은 놀람이 줄어듭니다.

컴포넌트는 제품 개념에 대응한다

컴포넌트가 주요 빌딩 블록이 되자 코드가 제품을 그대로 반영하기 시작했습니다:

  • Button(primary/secondary, loading, icon)
  • Modal(열기/닫기, 포커스 트랩, ESC 키)
  • CheckoutForm(검증, 제출, 오류 상태)

이 매핑 덕분에 디자이너, PM, 엔지니어가 같은 "사물"에 대해 이야기하기 쉬워졌습니다.

파일 구조에 미친 영향

컴포넌트 사고는 많은 코드베이스를 기능 또는 도메인 기반 조직(/checkout/components/CheckoutForm 등)과 공유 UI 라이브러리(/ui/Button) 쪽으로 밀었습니다. 기능이 확장될 때 페이지 전용 폴더보다 이 구조가 더 잘 확장되며, 나중에 디자인 시스템을 도입할 기반을 마련합니다.

선언적 렌더링과 JSX가 정착시킨 이유

React의 렌더링 스타일은 흔히 선언적이라고 불립니다. 즉, 주어진 상황에서 UI가 어떻게 보여야 하는지를 선언하면 React가 브라우저를 그 상태로 맞추는 방법을 알아서 처리한다는 뜻입니다.

“결과를 설명하고 단계는 쓰지 말라”

옛 DOM-우선 접근법에서는 단계별 지시를 작성했습니다:

  • 요소를 찾아라
  • 새 노드를 만들어라
  • 텍스트를 설정하라
  • 추가하라
  • 나중에 업데이트하거나 제거하라

선언적 렌더링에서는 대신 결과를 표현합니다:

사용자가 로그인되어 있으면 이름을 보여주고, 아니면 "Sign in" 버튼을 보여라.

이 전환은 "UI 장부 관리"의 양을 줄여줍니다. 어떤 요소가 존재하는지, 무엇을 업데이트해야 하는지 계속 추적할 필요가 없고, 앱이 가질 수 있는 상태에 집중할 수 있습니다.

JSX가 정착에 기여한 이유

JSX는 UI 구조를 그 구조를 제어하는 로직과 가까이 쓰기 편하게 했습니다. 템플릿 파일과 로직 파일을 분리해 이리저리 오갈 필요 없이 관련 조각을 함께 둘 수 있습니다: 마크업 같은 구조, 조건, 작은 포맷 결정, 이벤트 핸들러를 한곳에 두는 것이죠.

이 근접성(co-location)은 React의 컴포넌트 모델이 실용적으로 느껴진 큰 이유입니다. 컴포넌트는 단순한 HTML 덩어나 JavaScript 번들이 아니라 UI 동작의 단위입니다.

“HTML과 JS를 섞는 것 아닌가요?”

JSX가 HTML과 JavaScript를 섞는다는 우려가 있습니다만, JSX는 실제로 HTML이 아니라 JavaScript 호출을 생성하는 문법입니다. 더 중요한 점은 React가 기술을 섞는 것이 아니라 함께 변하는 것들을 묶는 것입니다. 예: "검증에 실패할 때만 오류 메시지를 보여라" 같은 규칙이 논리와 UI 구조가 밀접하게 연결된 상황에서 한 곳에 있는 것이 여러 파일에 흩어져 있는 것보다 명확할 수 있습니다.

선언적 렌더링은 JSX에만 국한되지 않는다

JSX가 React를 접근하기 쉽게 했지만, 근본 개념은 JSX를 넘어 확장됩니다. JSX 없이 React를 쓸 수도 있고, 다른 프레임워크들도 다른 템플릿 문법으로 선언적 렌더링을 사용합니다. 지속적인 영향은 사고방식입니다: UI를 상태의 함수로 취급하고 프레임워크에 화면 동기화의 세부를 맡기세요.

리콘실리에이션과 가상 DOM(그리고 신화 없이)

앱이 성장하면 확장하세요
팀의 개발·배포 방식에 따라 Free, Pro, Business 또는 Enterprise 요금제를 선택하세요.

React 이전의 흔한 버그 원인은 단순했습니다: 데이터는 변경됐지만 UI는 변경되지 않았다. 개발자는 새 데이터를 가져온 뒤 적절한 DOM 노드를 찾아 텍스트를 업데이트하고, 클래스를 토글하고, 요소를 추가/제거하며 모든 것을 일관되게 유지하려 애썼습니다. 시간이 지나며 "업데이트 로직"이 실제 UI보다 더 복잡해지는 경우도 많았습니다.

React의 큰 작업 흐름 전환은 브라우저에 페이지를 어떻게 변경할지 지시하지 않는다는 점입니다. 주어진 상태에서 UI가 어떻게 보여야 하는지를 기술하면 React가 실제 DOM을 일치시키기 위한 최소 변경을 계산합니다.

리콘실리에이션의 실제 의미

리콘실리에이션은 React가 지난 렌더 출력과 이번 렌더 출력을 비교하고 실제 DOM에 가장 작은 변경 집합만 적용하는 과정입니다.

중요한 부분은 React가 "가상 DOM"을 성능의 마법으로 사용하는 것이 아니라 예측 가능한 모델을 제공한다는 점입니다:

  • 렌더 로직을 마치 UI를 처음부터 다시 그리는 것처럼 작성합니다.
  • React는 사용자에게 전체 재생성 비용을 지우지 않으면서 DOM을 증분적으로 업데이트합니다.

이 예측 가능성은 개발자 워크플로를 개선합니다: 수동 DOM 업데이트가 줄고, 비일관 상태가 줄며, UI 업데이트가 앱 전반에서 동일한 규칙을 따릅니다.

키(key): 실용적 요점

리스트 렌더링 시 React는 "이전 항목"과 "새 항목"을 매칭할 안정적인 방법이 필요합니다. 그게 key입니다.

{todos.map(todo => (
  <TodoItem key={todo.id} todo={todo} />
))}

ID 같은 안정적이고 고유한 키를 사용하세요. 항목이 재정렬되거나 삽입/삭제될 수 있다면 배열 인덱스는 피해야 합니다—아니면 React가 잘못된 컴포넌트 인스턴스를 재사용해 입력값이 엉키는 등 놀라운 동작이 발생할 수 있습니다.

자주 묻는 질문

React 맥락에서 “프론트엔드 아키텍처”는 무엇을 의미하나요?

React는 프론트엔드 아키텍처를 몇 가지 핵심 결정으로 재구성했습니다:

  • UI를 재사용 가능한 컴포넌트로 분할하기
  • UI를 상태 중심(state-driven) 으로 만들기(데이터를 진실의 원천으로 취급)
  • 컴포지션을 사용해 화면을 구성하기
  • 단방향 데이터 흐름을 채택해 소유권을 명확히 하기

실제 효과는 수작업 DOM 관리가 줄고 팀과 도구에 대한 경계가 더 명확해진다는 점입니다.

한 문장으로 설명하는 ‘컴포넌트 사고’란 무엇인가요?

컴포넌트 사고(component thinking)는 각 UI 조각을 렌더링을 소유하고 더 큰 화면으로 조합 가능한 작은 재사용 단위로 취급하는 것입니다. 실무적으로 컴포넌트는 다음을 번들로 가집니다:

  • 구조(무엇을 렌더링하는가)
  • 상호작용(핸들러, 상태, 사이드 이펙트)
  • 스타일링 훅(클래스/변형/토큰)

이 접근은 “이 DOM 노드를 업데이트하라”는 사고에서 “이 상태에서 이 컴포넌트를 렌더링하라”는 사고로 전환시켜 줍니다.

왜 React는 수동 DOM 조작의 필요성을 줄였나요?

DOM-우선 코드에서는 DOM이 진실의 원천이 되어 여러 요소를 수동으로 동기화하곤 했습니다. React에서는 상태를 업데이트하면 그 상태를 바탕으로 렌더링이 일어나므로 로딩 스피너, 버튼의 disabled 상태, 빈 상태 메시지 등이 자연스럽게 일관성을 유지합니다.

간단한 기준: "요소를 찾아서 클래스를 토글하는" 동작을 많이 쓰고 있다면 프레임워크 모델과 싸우고 있는 것입니다. UI가 어긋난다면 대개는 상태 소유권 문제입니다.

React 이전의 DOM-우선 아키텍처에서 가장 큰 문제점은 무엇이었나요?

React 이전의 많은 앱은 페이지 중심 접근이었다: 서버 렌더링 템플릿 + jQuery + 플러그인. 동작 로직은 서버 뷰, HTML 속성, JS 초기화 코드에 흩어져 있었습니다.

흔한 문제는 다음과 같았습니다:

  • 템플릿/페이지 전반에 걸친 UI 중복
  • 마크업이 바뀌면 깨지는 취약한 셀렉터
  • 스피너/에러/버튼 상태 불일치

React는 팀을 재사용 가능한 컴포넌트와 예측 가능한 업데이트 패턴 쪽으로 밀어넣었습니다.

‘선언적 렌더링’이란 무엇이며 왜 중요한가요?

선언적 렌더링은 특정 상태에서 UI가 어떻게 보여야 하는지를 ‘설명’하는 것이고, DOM을 단계별로 조작하는 방식이 아닙니다.

전통적인 방법에서는 노드를 만들고 텍스트를 설정하고 붙이고 나중에 제거하는 식이었지만, 선언적 렌더링에서는 렌더 출력에 조건을 써서 “로그인 상태이면 이름을 보여주고 아니면 로그인 버튼을 보여라”처럼 표현합니다. React가 실제 DOM을 업데이트하는 일을 처리합니다.

왜 JSX가 React 모델의 확산을 도왔나요?

JSX는 UI 구조를 그와 관련된 로직(조건, 포맷, 핸들러)과 가깝게 함께 작성할 수 있게 해 주었기 때문에 React 모델이 빠르게 자리잡는 데 큰 도움이 됐습니다. JSX는 HTML이 아니라 JavaScript 호출을 생성하는 문법이라는 점을 기억하세요. 핵심 이득은 조직화에 있습니다: 함께 변하는 것들을 한 곳에 묶어두면 유지보수가 더 쉬워집니다.

React의 리콘실리에이션이란 무엇이며 `key`의 실제 목적은 무엇인가요?

리콘실리에이션(reconciliation)은 React가 이전 렌더와 이번 렌더를 비교하고 실제 DOM에 최소한의 변경만 적용하는 과정입니다.

핵심은 가상 DOM 자체가 마법 같은 성능 트릭이라기보다, 예측 가능한 모델을 제공한다는 점입니다: 렌더 로직을 마치 UI 전체를 새로 그리는 것처럼 쓰면 React가 증분적으로 업데이트를 수행합니다.

리스트를 렌더링할 때는 key가 중요합니다. 다음처럼 고유하고 안정적인 값을 사용하세요:

{todos.map(todo => (
  <TodoItem key={todo.id} todo={todo} />
))}

항목이 재정렬되거나 삽입/삭제될 수 있다면 배열 인덱스를 피하세요. 잘못된 컴포넌트 인스턴스 재사용으로 입력값이 엉키는 등 이상한 동작이 발생할 수 있습니다.

단방향 데이터 흐름은 React 아키텍처를 어떻게 단순화하나요?

단방향 데이터 흐름은 데이터가 부모에서 자식으로 전달되고, 자식은 콜백을 통해 변경을 요청하는 방식입니다.

이 패턴은 경계를 명확하게 해 줍니다: “이 데이터를 누가 소유하고 있는가?”라는 질문에 대부분은 "가장 가까운 공통 부모"라고 답할 수 있습니다. 디버깅도 숨겨진 변경을 추적하는 대신 상태가 어디에 저장되었는지를 찾는 일이 됩니다.

실제 React 프로젝트에서 ‘상속보다 컴포지션’은 어떻게 적용되나요?

컴포지션은 여러 작은, 집중된 조각을 조합해서 화면을 만드는 방식입니다. 상속 기반 접근 대신 다음 같은 패턴을 자주 봅니다:

  • PageShell 같은 레이아웃 컴포넌트(헤더/사이드바/푸터)
  • Stack/Grid 같은 간격/정렬 컴포넌트
  • Card 같은 프레임 컴포넌트

이들은 보통 children을 받아 페이지가 내부 내용을 정하도록 합니다. 필요할 때는 title, footer, renderRow, emptyState 같은 슬롯 비슷한 prop을 통해 유연성을 제공합니다. 상속은 깊어지면 관리가 어렵지만 컴포지션은 더 예측 가능하고 유지보수하기 쉽습니다.

React 앱이 커질 때 팀은 상태 관리를 어떻게 접근해야 하나요?

성장하는 React 앱에서 상태 관리는 보통 이렇게 진행됩니다:

  1. 컴포넌트 전용 UI에는 로컬 상태 사용(입력값, 토글, 스피너)
  2. 형제 컴포넌트 간 동기화 필요 시 상태 끌어올리기(lift state up)
  3. 테마/로케일/현재 사용자 같은 횡단 관심사에는 Context 사용
  4. 멀리 떨어진 많은 컴포넌트가 같은 데이터에 의존하고 업데이트 규칙이 필요할 때는 외부 스토어(Redux 스타일) 고려

선택은 유행이 아니라 앱 복잡도와 팀 필요에 따라 해야 합니다.

테스트와 성능 측면에서 React 아키텍처의 주의점은 무엇인가요?

컴포넌트 경계가 명확하면 테스트와 성능 계획이 아키텍처 차원에서 이뤄질 수 있습니다.

테스트 관점:

  • 유닛 테스트: 특정 prop과 state에서 컴포넌트가 올바르게 렌더링되는지 검증
  • 통합 테스트: 폼과 검증 메시지, 리스트와 필터 같은 작은 트리를 렌더링해 사용자 동작이 올바른 UI 변화를 만드는지 확인

성능 관점에서는 구조적 결정이 중요합니다: 코드 스플리팅, 메모이제이션(필요할 때), 레이지 로딩 같은 전략을 통해 초기 로드를 작게 하고 자주 재렌더되는 부분의 비용을 줄입니다.

주의할 점은 과도한 분해(너무 많은 작은 컴포넌트), prop 드릴링, 그리고 명확하지 않은 상태 소유권입니다. 자동 생성 코드나 스캐폴딩을 쓸 때 이 문제는 더 빨리 드러납니다. 핵심은 상태 소유권을 명확히 하고 컴포넌트 API를 작게 유지하는 것입니다.

앞으로의 방향과 오래가는 교훈은 무엇인가요?

서버 컴포넌트, 메타 프레임워크, 더 나은 툴링이 React 앱 전달 방식을 계속 진화시킬 것입니다. 하지만 변하지 않는 교훈은 다음과 같습니다: 상태와 소유권, 조합 가능한 UI 블록을 중심으로 설계하고, 그 다음에 테스트와 성능을 자연스럽게 따라오게 하라는 점입니다.

더 깊은 구조적 결정은 /blog/state-management-react를 참조하세요.

Related posts