조던 월크가 페이스북에서 만든 리액트가 재사용 가능한 컴포넌트, 선언적 뷰, 상태 기반 렌더링을 도입해 현대 프론트엔드 아키텍처를 어떻게 바꿨는지 살펴봅니다.

조던 월크는 페이스북에서 일하던 중 리액트를 만든 소프트웨어 엔지니어로 잘 알려져 있습니다. 리액트 이전의 프론트엔드는 종종 페이지와 템플릿을 중심으로 구성되었고, HTML, CSS, JavaScript를 동기화하려는 "글루 코드"가 점점 쌓이는 식이었습니다. 월크의 핵심 아이디어는 모델을 뒤집는 것이었습니다: UI를 시간이 지나며 패치하는 문서 집합으로 보지 말고, 작은 재사용 가능한 컴포넌트 트리로 보고 이를 더 큰 기능으로 조합하라는 것입니다.
이건 단순한 새로운 라이브러리가 아니라 UI 작업에 대한 새로운 사고방식이었습니다. 컴포넌트는 인터페이스의 일부를 그것이 필요로 하는 로직과 상태와 함께 묶고, 앱의 나머지 부분에는 깨끗한 인터페이스(props)를 노출합니다. 덕분에 UI는 하나의 깨지기 쉬운 페이지를 편집하는 것보다 레고 블록으로 조립하는 느낌에 더 가깝습니다.
리액트가 중요했던 이유는 팀들이 다음을 할 수 있게 했기 때문입니다:
실무적으로 리액트를 영향력 있게 만든 아이디어들을 살펴봅니다:
프레임워크 전문가일 필요는 없습니다. 목표는 정신 모델을 명확히 해서 좋은 리액트 패턴을 인식하고, 흔한 오해를 피하며, 심지어 리액트 밖에서도 같은 원칙을 적용할 수 있게 하는 것입니다.
리액트 이전에는 많은 팀이 템플릿, jQuery식 DOM 조작, 그리고 "X가 발생하면 Y를 업데이트" 같은 규칙들을 엮어 복잡한 인터페이스를 만들었습니다. 작동은 했지만 UI가 복잡해지면 한계에 부딪혔습니다.
흔한 패턴은: 데이터를 가져와 HTML을 렌더링한 뒤 이벤트 핸들러를 붙여 DOM을 직접 변형하는 것이었습니다. 상태가 바뀌는 순간(새 항목, 검증 오류, 토글 등) 의존하는 모든 곳을 기억해야 했습니다.
그 결과 생기는 버그 예:
화면이 발전하면서 동일한 비즈니스 규칙이 여러 핸들러에 중복되곤 했습니다: "필드가 비어 있으면 버튼 비활성화", "읽지 않은 항목 강조", "결과가 없을 때 빈 상태 표시" 등. 요구사항이 바뀌면 관련 없는 파일을 뒤져 각 복사본을 수정해야 했습니다.
데이터는 몇 가지 명확한 구조로 모델링할 수 있습니다: 포스트 목록, 사용자 객체, 필터 세트 등. 하지만 UI는 로딩 vs 로드됨, 에러 vs 성공, 읽음 vs 미읽음, 편집 중 vs 보기 모드, 필터 적용 vs 미적용 같은 조합을 추가합니다—종종 동시에 여러 상태가 겹칩니다.
뉴스 피드를 상상해보세요:
"UI는 상태의 함수다" 같은 예측 가능한 규칙이 없다면 여러 DOM 편집을 조정하다 충돌이 발생할 수 있습니다. 리액트의 목표는 업데이트를 신뢰할 수 있게 만드는 것: 데이터를/상태를 바꾸면 UI는 매번 그에 맞게 다시 렌더링됩니다.
컴포넌트는 이름을 붙이고 재사용하며 독립적으로 이해할 수 있는 UI의 작은 조각입니다. 쉽게 말해: 컴포넌트는 입력을 받고 그 입력에 대한 UI 모습을 반환합니다.
이 "입력 → 출력" 프레이밍이 컴포넌트 모델의 핵심입니다. 화면을 하나의 큰 템플릿으로 처리하는 대신 의도적인 빌딩 블록(버튼, 카드, 메뉴, 폼, 섹션 등)으로 분해해 조립합니다.
리액트에서 가장 일반적인 입력은 props(properties의 약자)입니다. props는 컴포넌트를 구성하는 값들입니다: 텍스트, 숫자, 플래그, 이벤트 핸들러, 심지어 다른 UI까지도 전달할 수 있습니다.
출력은 컴포넌트가 렌더링하는 UI입니다. props가 바뀌면 컴포넌트는 다른 출력을 낼 수 있고, 그 과정에서 DOM을 수동으로 찾아 바꿀 필요가 없습니다.
예를 들어 Button 컴포넌트는 label, disabled, onClick 같은 props를 받을 수 있습니다. UserCard는 name, avatarUrl, status를 받을 수 있습니다. 컴포넌트의 인터페이스(props)를 읽으면 "이 UI가 올바르게 렌더링되려면 무엇이 필요한가?"라는 제품 스펙처럼 이해할 수 있습니다.
UI를 컴포넌트로 분해하면 빠르게 이점이 생깁니다:
Modal, Input, Dropdown이 여러 페이지에 등장이는 페이지마다 마크업을 복사해 수정하던 관행에서 큰 전환입니다. 컴포넌트는 중복을 불필요하게, 나아가 받아들일 수 없게 만듭니다.
리액트는 UI를 구성 가능한 부품으로 설계하라고 권합니다. "체크아웃 페이지"는 CheckoutPage가 OrderSummary, ShippingForm, PaymentMethod를 포함하는 컴포넌트 트리가 됩니다. 각 부분은 명확한 입력과 책임을 가집니다.
이런 사고방식의 전환—컴포넌트 우선 사고—이 리액트가 프론트엔드 아키텍처를 바꾼 큰 이유입니다. 팀에 공통의 설계 및 개발 단위(컴포넌트)를 제공했습니다.
리액트의 가장 큰 정신적 변화는 선언적 UI입니다: 특정 상태에서 인터페이스가 어떻게 보여야 하는지를 서술하면 리액트가 상태가 바뀔 때 페이지를 갱신해줍니다.
요소를 수동으로 찾아 텍스트를 수정하고 클래스를 토글해서 DOM을 동기화하는 대신, UI의 "모양"에 집중합니다. 데이터가 바뀌면 UI가 다시 서술되고, 리액트는 필요한 최소한의 변경만 계산합니다.
JSX는 HTML처럼 보이는 문법을 JavaScript 안에서 사용해 컴포넌트 구조를 작성하는 편리한 방법입니다. 완전히 새로운 템플릿 언어를 새로 배우는 게 아니라 "이 컴포넌트는 이 요소 트리를 렌더링한다"는 축약 표현입니다.
핵심 이점은 마크업과 이를 결정하는 로직이 함께 존재해 컴포넌트를 독립적으로 이해하기 쉬워진다는 점입니다.
명령적 코드는 UI를 단계별로 어떻게 업데이트할지에 집중합니다:
// Imperative: manually keep the DOM in sync
function setLoggedIn(isLoggedIn) {
const el = document.querySelector('#status');
el.textContent = isLoggedIn ? 'Welcome back' : 'Please sign in';
el.classList.toggle('ok', isLoggedIn);
el.classList.toggle('warn', !isLoggedIn);
}
선언적 코드는 현재 상태에 대해 어떤 UI여야 하는지에 집중합니다:
function Status({ isLoggedIn }) {
return (
\u003cp className={isLoggedIn ? 'ok' : 'warn'}\u003e
{isLoggedIn ? 'Welcome back' : 'Please sign in'}
\u003c/p\u003e
);
}
렌더링이 순수한 서술로 표현되므로 컴포넌트는 보통 더 읽기 쉽고, 코드 리뷰도 간단하며 리팩터링이 쉬워집니다. 디자이너나 제품 지향 엔지니어, 새로운 팀원도 이벤트 핸들러와 DOM 변형을 뒤지지 않고 JSX만으로 컴포넌트를 이해할 수 있는 경우가 많습니다.
이 명확성은 협업을 개선합니다: UI 결정이 한 곳에 드러나고 변경이 인터페이스의 다른 곳에 숨은 부작용을 만들 가능성이 줄어듭니다.
"상태"는 사용자가 UI와 상호작용하는 동안 시간이 지나며 변할 수 있고 화면에 반영되어야 하는 데이터입니다. 검색 상자의 현재 텍스트, 메뉴가 열려 있는지 여부, 장바구니 항목, 네트워크 요청 결과 등이 여기에 해당합니다. 화면에서 반영해야 하는 값이라면 상태입니다.
리액트의 핵심 움직임은 렌더링을 일련의 수동 DOM 단계가 아니라 상태의 결과로 다루는 것입니다. 특정 상태에 대해 UI가 어떻게 보여야 하는지를 서술합니다. 상태가 갱신되면 리액트는 관련 부분을 다시 렌더링합니다.
이 정신 모델은 "요소를 찾아 텍스트를 바꾸고 이 클래스를 토글하라"는 방식과 다릅니다. 대신 상태를 업데이트하면, 그 상태에서 파생된 UI가 자연스럽게 갱신됩니다.
단방향 데이터 흐름은 데이터가 한 방향으로만 이동한다는 뜻입니다:
이 패턴은 업데이트의 경로를 따라가기가 쉬워 디버깅을 단순화합니다: 이벤트가 발생하면 한 곳에서 상태가 변경되고, UI는 그 새 상태에서 재렌더링됩니다. "누가 이 값을 바꿨지?"라는 모호함이 줄어듭니다.
function Counter() {
const [count, setCount] = React.useState(0);
return (
\u003cdiv\u003e
\u003cp\u003eCount: {count}\u003c/p\u003e
\u003cbutton onClick={() =\u003e setCount(count + 1)}\u003eAdd\u003c/button\u003e
\u003c/div\u003e
);
}
여기서 count는 상태입니다. 버튼을 클릭하면 setCount로 상태를 업데이트하고 리액트가 재렌더링하며 단락에 새 숫자가 표시됩니다. 당신은 직접 DOM을 "수정"하지 않습니다.
이 패턴은 필터링(상태 = 필터 텍스트, UI = 필터된 항목)이나 폼 검증(상태 = 필드 값과 에러, UI = 메시지) 등으로 확장됩니다. 데이터가 먼저 바뀌고 뷰는 그 결과일 뿐입니다.
리액트의 핵심 아이디어는 "페이지를 더 빠르게 다시 그리는 것"이 아닙니다. 그것은 UI를 상태의 결과로 취급하고, 상태 변화가 있을 때 지금 원하는 것과 이전에 있던 것을 비교한 뒤 실제로 바뀐 부분만 업데이트하는 것입니다.
컴포넌트의 상태나 props가 바뀌면 리액트는 컴포넌트를 다시 호출해 새 UI 서술을 생성합니다. 이를 두 장의 스냅샷으로 생각할 수 있습니다:
리액트는 DOM을 지우고 다시 만드는 대신 A에서 B로 가기 위해 필요한 최소한의 DOM 작업 집합을 계산하려고 합니다.
"가상 DOM"은 리액트가 화면에 있어야 할 것을 설명하는 가벼운 트리 형태의 메모리 표현입니다. 그것은 두 번째 브라우저나 더 빠른 DOM이 아니라, 리액트가 효율적으로 검사하고 비교할 수 있는 데이터 구조입니다.
조정은 이전 가상 트리와 다음 가상 트리 사이의 변경점을 알아내는 과정입니다. 리액트는 빠르게 처리하기 위해 몇 가지 휴리스틱을 사용합니다. 예를 들어:
<div>는 <span>이 아님)리액트가 변경점을 알게 되면 실제 DOM에 타겟 업데이트를 적용합니다.
이건 마법이 아닙니다. 성능은 패턴에 달렸습니다: 안정적인 키, 불필요한 재렌더링 회피, 컴포넌트 작업을 작게 유지, 렌더링 중에 비싼 계산을 하지 않기 등. 리액트는 DOM 변동을 줄일 수 있지만 컴포넌트 구조와 데이터 흐름이 앱의 쾌적함을 결정합니다.
리액트의 가장 큰 확장 트릭은 기능이나 프래그먼트를 숨기는 것이 아니라 구성입니다: 컴포넌트를 중첩하고 props로 데이터를 전달하며 children을 사용해 컴포넌트가 다른 UI를 감쌀 수 있게 하는 방식입니다.
팀이 구성(컴포지션)에 집중하면 일회성 페이지 관점에서 벗어나 작은 신뢰할 수 있는 부품들을 재배치해 전체를 다시 쓰지 않고도 변경할 수 있게 됩니다.
children중첩은 UI 구조의 시각적 표현입니다: 페이지는 섹션을 포함하고, 섹션은 카드를 포함하고, 카드는 버튼을 포함합니다. props는 설정 노브(텍스트, 상태, 콜백)이고, children은 구조를 제공하면서 호출자가 내부에 무엇을 넣을지 결정하게 합니다.
좋은 정신 모델: props는 커스터마이즈, children은 내용 삽입, 중첩은 조립.
레이아웃 컴포넌트는 구조와 여백을 정의하지만 비즈니스 로직을 소유하지 않습니다. 예: Page, SidebarLayout, Stack, Modal. 이들은 종종 children에 크게 의존해 동일한 레이아웃이 여러 화면을 감쌀 수 있게 합니다.
재사용 가능한 입력 컴포넌트는 폼 동작과 스타일을 표준화합니다: TextField, Select, DatePicker. 라벨, 에러 상태, 검증 메시지를 화면마다 복사하는 대신 중앙에서 결정하고 간단한 prop API를 노출합니다.
리스트와 항목 컴포넌트는 반복되는 UI를 예측 가능하게 유지합니다. 흔한 분리는 ItemList(데이터 페칭, 페이지네이션, 빈 상태)와 ItemRow(한 항목의 표시 방식)입니다. 이렇게 나누면 데이터 처리 로직을 깨뜨리지 않고 렌더링을 바꾸기 쉽습니다.
훅(Hooks)은 토글, 폼 상태, 페칭 같은 상태ful 동작을 UI 형식에 구애받지 않고 재사용하는 현대적 방법입니다. 이 분리는 디자인을 진화시키는 동안 로직을 일관되게 유지하는 데 도움이 됩니다.
구성은 디자인 시스템이 일관되게 유지되는 방식입니다: 컴포넌트가 "승인된" 빌딩 블록이 되고 레이아웃은 여백과 계층 규칙을 정의합니다. 시스템이 업데이트되면 색상, 타이포그래피, 인터랙션 상태 같은 개선 사항이 제품들에 자동으로 적용됩니다.
상태는 단순히 "변할 수 있는 데이터"입니다. 리액트에서는 상태가 어디에 위치하느냐가 상태 자체만큼 중요합니다.
로컬 상태는 단일 컴포넌트(또는 작은 위젯)에 속하고 다른 곳에서 읽을 필요가 없을 때 적합합니다. 예: 드롭다운이 열려 있는지, 입력의 현재 값, 어떤 탭이 선택되었는지 등.
이 상태를 로컬로 유지하면 조정이 줄어들고 컴포넌트 재사용성이 높아집니다. 실용적 규칙: 오직 하나의 컴포넌트만 관심이 있으면 전역으로 올리지 마세요.
공유된 앱 상태는 UI의 여러 부분이 합의해야 하는 데이터입니다. 흔한 예:
여러 컴포넌트가 동일한 진실의 원천을 필요로 하면 상태 중복은 불일치(예: 헤더는 3개, 장바구니 페이지는 2개라고 표시)로 이어집니다.
상태 끌어올리기: 상태를 가장 가까운 공통 부모로 옮기고 props로 내려보내기. 단순하고 데이터 흐름을 명확하게 유지하는 경우가 많습니다.
Context: prop 드릴링 없이 많은 컴포넌트가 같은 값을 필요로 할 때 유용합니다(테마, 인증 등). 비교적 안정적인 앱 전체 관심사에 적합합니다.
외부 스토어: 상태가 복잡해지고(빈번한 업데이트, 파생 데이터, 페이지 간 워크플로우) 중앙에서 로직과 업데이트를 관리해야 할 때 적합합니다.
리액트의 단방향 데이터 흐름은 각 상태 조각의 명확한 소유자가 있을 때 빛을 발합니다. 가능한 곳에서는 단일 소스 오브 트루스를 선호하고, 나머지는 그 상태로부터 유도(derive)하여 중복 저장을 피하세요.
리액트의 일상적 큰 이점은 렌더링 트릭이 아니라 컴포넌트 경계가 UI 작업을 더 작고 안전한 변경으로 바꾼다는 점입니다. 컴포넌트가 명확한 책임과 안정적인 공개 인터페이스(props)를 가지면 팀은 내부를 리팩터링해도 앱 전반을 다시 써야 하는 상황을 피할 수 있습니다. 이런 안정성은 코드 리뷰를 쉽게 하고 우발적 고장을 줄이며 새로운 팀원이 어디를 고쳐야 할지 이해하기 쉽게 합니다.
유용한 정신 모델은: props와 state가 주어지면 컴포넌트는 예측 가능하게 UI를 서술해야 한다입니다. 부수 효과와 브라우저 API가 존재하긴 하지만, 컴포넌트 로직의 대부분은 결정론적으로 유지될 수 있습니다. 그래서 유지보수 가능한 리액트 테스트는 보통 동작과 출력에 집중합니다:
접근성 검사도 자연스럽게 들어옵니다: 롤과 접근 가능한 이름으로 테스트하면 빠진 라벨, 포커스 상태 문제, 일관성 없는 의미론을 조기에 잡아낼 수 있습니다. 일관성 검사(린팅, 포맷팅, 디자인 시스템 사용)는 동일한 아이디어를 강화합니다: 예측 가능한 컴포넌트가 유지보수하기 쉽습니다.
컴포넌트가 작은 prop API를 노출하고 구현 세부를 숨기면 여러 사람이 병렬로 작업할 수 있습니다—한 사람은 스타일을 조정하고, 다른 사람은 데이터 페칭을 변경하고, 또 다른 사람은 테스트를 업데이트해 충돌 없이 작업이 진행됩니다.
리액트 성능 문제는 보통 "리액트가 느리다"가 아니라 앱이 브라우저에 요구하는 작업량에 관한 문제입니다. 가장 빠른 UI는 가장 적게 작업하는 UI입니다: DOM 노드 수를 줄이고, 레이아웃/리플로우를 줄이며, 비싼 계산과 네트워크 왕복을 줄이는 것이 중요합니다.
자주 발생하는 문제는 불필요한 재렌더링입니다: 작은 상태 변경이 상태가 너무 상위에 있거나 props의 정체성이 매번 바뀌어(새 객체/함수 생성) 큰 하위 트리가 재렌더되는 경우입니다.
또 다른 고전적 문제는 무거운 리스트—수백 또는 수천 개의 행에 이미지, 포맷팅, 이벤트 핸들러가 포함된 경우입니다. 각 행이 "싸게" 보여도 전체 작업량이 쌓이면 스크롤이 버벅거립니다.
구조에서 시작하세요:
또한 사용자가 체감하는 부분에 집중하세요: 입력 지연 줄이기, 첫 의미 있는 페인트 속도 향상, 상호작용을 부드럽게 유지. 자주 발생하는 상호작용에서 20ms 개선은 드물게 발생하는 화면에서 200ms 아끼는 것보다 더 중요할 수 있습니다.
파생 상태는 다른 상태/props에서 계산할 수 있는 데이터입니다(예: firstName + lastName으로 fullName을 만들거나 리스트 + 쿼리로 필터된 항목을 만드는 경우). 이를 저장하면 보통 버그가 생깁니다: 이제 두 개의 진실의 원천이 생겨 어긋날 수 있습니다.
렌더링 중에 파생 값을 계산하거나 계산이 비싸면 메모이제이션을 사용하세요. 저장은 보통 사용자가 입력한 값, 서버 응답, UI 의도(예: 패널이 열려 있는지) 같은 것들만 하세요.
리액트는 단지 더 나은 UI 작성법을 도입한 것뿐 아니라 팀이 프론트엔드를 구성하고 공유하며 유지보수하는 방식을 재조직하도록 유도했습니다. 컴포넌트 우선의 사고방식이 기본이 되기 전에는 많은 프로젝트가 UI를 페이지와 흩어진 스크립트, 템플릿 중심으로 다뤘습니다. 리액트로 단위 아키텍처는 점점 컴포넌트가 되었고, 컴포넌트는 명확한 API(props)와 예측 가능한 동작을 가진 UI 조각이 되었습니다.
리액트는 단일 페이지 애플리케이션(SPA)의 증가와 자연스럽게 맞물렸습니다. 렌더링이 상태로 구동되면 "페이지"는 서버에서 전달되는 템플릿이 아니라 컴포넌트 조합과 클라이언트 측 라우팅이 됩니다. 이 변화로 코드 구조를 별도 HTML 파일 중심이 아니라 기능 영역과 재사용 가능한 UI 부품 중심으로 조직하는 일이 일반화되었습니다.
UI가 재사용 가능한 조각으로 구성되면 그 조각들을 표준화하는 것이 자연스럽습니다. 많은 조직이 마크업을 복사하던 관행에서 버튼, 폼 컨트롤, 모달, 레이아웃 프리미티브 같은 컴포넌트 라이브러리를 구축하는 쪽으로 이동했습니다. 시간이 지나면 이런 라이브러리는 디자인 시스템—공유 컴포넌트와 가이드라인—으로 발전해 팀이 매 화면마다 UI를 다시 발명하지 않고도 일관된 경험을 제공할 수 있게 합니다.
컴포넌트는 팀이 같은 이름으로 대화하게 만듭니다. 모두가 <Button>, <Tooltip>, <CheckoutSummary> 같은 이름을 쓰면 시각적 요소뿐 아니라 동작과 경계에 대해 더 구체적으로 이야기할 수 있습니다. 이런 공유 어휘는 코드로 시스템을 발견하면서 신규 팀원의 온보딩을 빠르게 돕습니다.
리액트의 성공은 더 넓은 프론트엔드 커뮤니티가 UI에 대해 생각하는 방식을 바꿨습니다: 컴포넌트 우선 개발, 선언적 렌더링, 예측 가능한 데이터 흐름이 보편적 기대가 되었습니다. 다른 프레임워크들도 구현 세부는 달라도 유사한 아이디어를 받아들였는데, 그 실무 관행들이 실제 팀에서 확장하기 더 쉽다는 것이 입증되었기 때문입니다.
리액트는 복잡한 UI를 진화시키기 쉽게 만들어 명성을 얻었지만 "공짜"로 얻어지는 것은 아닙니다. 도입 전 트레이드오프를 아는 것은 팀이 올바른 이유로 채택하고 카고컬트(cargo-cult)를 피하는 데 도움이 됩니다.
리액트에는 학습 곡선이 있습니다: 컴포넌트, 훅, 상태 업데이트와 효과 같은 정신 모델을 내면화하는 데 시간이 걸립니다. 현대 리액트는 빌드 도구(번들링, 린팅, TypeScript 선택적 사용 포함)를 전제로 하는 경우가 많아 설정과 유지보수가 필요합니다. 마지막으로 리액트는 추상화 층(컴포넌트 라이브러리, 라우팅, 데이터 페칭 패턴)을 도입하는데, 이는 유용하지만 문제가 생기면 복잡함을 숨길 수 있습니다.
"리액트는 단지 뷰(view)뿐이다." 이론적으로는 맞지만 실제로는 리액트가 아키텍처를 강하게 형성합니다. 컴포넌트 경계, 상태 소유권, 컴포지션 패턴이 데이터 흐름과 코드 조직에 영향을 미칩니다.
"가상 DOM이 항상 더 빠르다." 가상 DOM은 주로 예측 가능한 업데이트와 개발자 경험에 관한 것입니다. 리액트는 빠를 수 있지만 성능은 렌더링 패턴, 메모이제이션, 리스트 크기, 불필요한 재렌더를 피하는지에 달려 있습니다.
리액트는 많은 상호작용 상태가 있고, 장기간 유지될 코드베이스이며, 여러 개발자가 병렬로 작업하는 앱에 적합합니다. 반면에 주로 정적 마케팅 사이트나 작은 위젯 몇 개라면 서버 렌더링 템플릿이나 가벼운 JS, 최소 프레임워크가 더 쉽게 배포하고 유지보수할 수 있습니다.
프로토타입으로 리액트 앱을 빠르게 검증하고 싶다면(컴포넌트 경계, 상태 소유권, 컴포지션 패턴 확인) 바이브 코딩(vibe-coding) 워크플로우가 도움이 될 수 있습니다. 예를 들어, Koder.ai는 채팅으로 기능을 설명하면 작동하는 리액트 프런트엔드와 Go/PostgreSQL 백엔드를 생성하고 스냅샷/롤백으로 반복한 뒤 준비되면 소스 코드를 내보낼 수 있게 합니다. 실제 기능으로 아키텍처 결정을 테스트한 다음 전체 빌드에 커밋하기 전에 검증하는 실용적 방법입니다.
다음 단계: 하나의 실제 기능을 프로토타입으로 만들어 복잡도와 팀 속도를 측정한 뒤, 패턴을 기본값으로 무분별하게 확장하지 말고 의도적으로 확장하세요.
조던 월크는 페이스북 재직 중 리액트를 만든 소프트웨어 엔지니어입니다. 리액트는 깨지기 쉬운 수동 DOM "글루 코드"를 컴포넌트 기반의 상태 주도 방식으로 대체해서, 복잡한 인터페이스를 더 쉽게 확장하고 디버그하며 유지보수할 수 있게 했습니다.
템플릿 기반과 jQuery 스타일의 프론트엔드는 UI 규칙이 마크업과 여기저기 흩어진 이벤트 핸들러에 퍼지는 경향이 있었습니다(“X가 발생하면 Y를 업데이트” 식). 컴포넌트는 UI와 로직을 작은 인터페이스(프로퍼티) 뒤에 묶어 예측 가능한 조각들로 기능을 조합할 수 있게 합니다. 따라서 페이지를 패치하듯 수정하는 대신, 재사용 가능한 조각들로 기능을 구성합니다.
컴포넌트는 입력(대개 props)을 받아 그 입력에 맞는 UI를 반환하는 재사용 가능한 단위입니다.
실무적으로는 다음을 지향하세요:
props는 컴포넌트를 설정하는 입력값(텍스트, 플래그, 콜백, 다른 UI 등)입니다. props를 다음과 같이 생각하세요:
disabled, onSubmit 같은 명확한 이름 선호선언적 UI는 현재 상태에 대해 UI가 어떻게 보여야 하는지를 서술하고, 상태가 바뀌면 그 서술에 따라 UI가 갱신되도록 하는 방식입니다. 즉, DOM을 찾아서 텍스트를 바꾸고 클래스를 토글하는 식의 절차적(명령형) 코드 대신, 상태를 업데이트하면 렌더링이 그 상태를 반영하도록 둡니다.
JSX는 JavaScript 안에서 HTML처럼 보이는 문법으로 컴포넌트 구조를 표현하는 방법입니다. 렌더링 로직과 그가 제어하는 마크업이 함께 있으므로, 컴포넌트를 하나의 단위로 읽고 검토하기 쉬워집니다.
상태(state)는 사용자가 상호작용하면서 시간이 지나며 바뀔 수 있고 화면에 반영되어야 하는 모든 데이터입니다(검색어 텍스트, 로딩 상태, 장바구니 항목 등).
실용적인 규칙: 진짜 소스 오브 트루스(사용자 입력, 서버 응답, UI 의도)는 상태로 저장하고, 그 밖의 값(개수, 필터된 리스트 등)은 그 상태로부터 유도하세요.
단방향 데이터 흐름은 다음과 같습니다:
이 패턴은 디버깅을 쉽게 만듭니다. 이벤트 → 상태 변경 → 재렌더링의 단선적 흐름을 따라가면 무엇이 값을 바꿨는지 추적하기 쉬워집니다.
가상 DOM은 리액트가 내부에서 유지하는 UI의 메모리 표현입니다. 상태나 props가 바뀌면 리액트는 이전 UI 서술과 새 UI 서술을 비교하고 실제 DOM에서 필요한 부분만 업데이트합니다.
일반적인 문제를 피하려면:
key 값을 사용상태를 어디에 둘지 결정하는 방법:
가능하면 단일 소스 오브 트루스를 유지하고 나머지는 유도(derive)하세요.