react · high priority
React 컴포넌트 패턴 — Compound · Headless · Hook 합성
재사용성의 단위는 *컴포넌트* 가 아니라 *행위* — 패턴 별 트레이드오프와 선택 기준
학습 개요
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“레고 시스템”
*Custom Hook* 은 *레고 안의 작은 메커니즘* — 톱니, 스프링, 모터. 혼자서는 모양이 없지만 다른 부품과 합치면 다양한 동작을 만듭니다. *Headless* 는 *모터 + 회로 묶음* — 차종이 무엇이든 동력은 같습니다. *Compound* 는 *레고 세트* — 자동차의 차체, 바퀴, 문이 한 박스에 들어 있고 사용자가 *순서와 위치* 만 자유롭게 조립. *Slot/asChild* 는 *모듈러 어댑터* — 같은 회로를 USB 도, 라이트닝도 결합 가능하게 해주는 어댑터입니다.
핵심 개념
Compound Components
부모가 *암묵적 상태* 를 들고, 자식들은 그 상태를 Context 로 구독해 공동 작업. <Tabs> 안에 <Tabs.List> <Tabs.Trigger> <Tabs.Content> 같은 구성. *prop 폭발* 의 표준 해독제.
Headless Components
*로직만* 제공하고 UI 는 사용자에게 맡김. 접근성·키보드·포커스·ARIA 등 어려운 부분은 라이브러리가, 마크업·스타일은 앱 코드가. Radix UI Primitives, Headless UI, React Aria 가 대표.
Custom Hooks
재사용 단위가 *상태 + 효과* 일 때, 컴포넌트가 아니라 훅으로 묶음. useToggle, useDisclosure, useDebouncedValue 등. UI 에 묶이지 않아 *가장 가벼운 재사용 단위*.
Slot / asChild 합성
컴포넌트가 *기본 트리 구조* 만 정하고, asChild 또는 slots 로 *내부 마크업/태그* 를 교체. Radix 의 <Slot> 이 대표적 — <Button asChild><a href> 처럼 사용.
| 패턴 | 재사용 단위 | 스타일 결합 | 내부 상태 노출 | 대표 라이브러리 |
|---|---|---|---|---|
| Compound | UI + 로직 묶음 | 강 (스타일까지 같이 옴) | Context 로 암묵 | shadcn/ui (래퍼) |
| Headless | 로직만 | 약 (마크업·스타일은 사용자) | 훅/렌더 함수로 명시 | Radix Primitives, Headless UI, React Aria |
| Custom Hooks | 상태/효과 | 없음 | 리턴 값 | use-* 라이브러리들 |
| Slot/asChild | 트리 구조 | 약~중 | 주입된 child 로 위임 | Radix Slot, Vue slot |
render props · HOC 는 어디로 갔나
*HOC* (withRouter 등) 는 hook 등장과 함께 거의 은퇴했고, *render props* 도 일반 UI 에서는 hook 으로 대체되었습니다. 그러나 *지속적 렌더 트리* 가 필요한 곳 — Downshift, React Spring, Formik 같은 라이브러리 — 에서는 render props 가 여전히 유효합니다. 면접에서 "왜 render props 가 줄었나" 는 결국 hook 의 등장으로 설명하면 됩니다.
실무 적용
어떤 상황에서 사용하는가
디자인 시스템 팀이 새 Modal/Toast/Combobox 컴포넌트를 만들어야 한다. 팀에는 시니어/주니어가 섞여 있고, 다양한 페이지에서 다른 디자인 변형이 필요.
어떻게 적용하는가
(1) 어려운 부분(포커스 트랩·Esc 처리·ARIA·keyboard nav)은 *Radix Primitives* 를 헤드리스 layer 로 채택. (2) 그 위에 회사 wrapper 를 *Compound* 형태로 (`<Modal.Root> <Modal.Header> <Modal.Body> <Modal.Actions>`) 노출. (3) `<Button asChild>` 로 Link/외부 라우터 결합. (4) 폼 제어 같은 로직은 *Custom Hook* 으로 (`useDisclosure`, `useFormState`). (5) wrapper 코드는 shadcn/ui 처럼 *복사 가능한 형태로 레포에 둔다* — 팀이 디자인 토큰을 직접 수정 가능. (6) Storybook 으로 변형을 시각화, 시각 회귀 테스트(Chromatic) 로 회귀 방지.
흔한 실수와 안티패턴
- "모든 변형을 props 로" — 결국 30개 prop, 변형 추가가 두려워짐.
- "무조건 Headless" — 팀이 작아 ARIA/키보드 직접 구현하느라 시간만 낭비.
- Compound 자식을 Root 밖에서 사용해도 에러가 안 나서 디버깅 어려움 — useContext null 검사 누락.
- asChild 를 자식이 React.forwardRef 미지원 컴포넌트에 적용해 ref 전달 실패.
- Custom Hook 추상화를 1회 사용에도 적용해 *읽기 비용* 만 늘림.
흔한 오해
"Compound 와 Headless 는 같은 개념이다."
교정다르다. Compound 는 *부모-자식 묶음* 의 합성 방식, Headless 는 *로직만 제공하는 layer* 다. Headless 가 Compound 형태의 API 를 가질 수도 있다.
왜 중요Radix 는 Headless 인 동시에 Compound 형태의 API 를 가진다 — 둘은 직교 축이다.
"render props 와 HOC 는 죽었다."
교정일반 UI 에선 hook 으로 대체됐지만, *지속적 렌더 트리* 가 필요한 라이브러리(Downshift, Spring) 에선 render props 가 여전히 유효.
왜 중요hook 은 호출 위치 제약(Rules of Hooks) 이 있어 *조건부 트리 렌더* 가 어렵다.
"Slot/asChild 는 가독성을 해치니 피해야 한다."
교정오히려 *prop 폭발* 을 막아 가독성을 살린다. Radix·shadcn 의 표준이 된 이유.
왜 중요`<Button as="a" href>` 식 polymorphic prop 보다 *자식 태그를 그대로 두는 asChild* 가 타입과 의미를 더 잘 보존한다.
"shadcn/ui 도 라이브러리니까 그냥 npm 으로 받아 쓰면 된다."
교정shadcn/ui 는 *복사 가능한 코드 모음* 이지 npm 패키지가 아니다 — 코드를 *내 레포로 복사* 해 자유롭게 수정하는 게 의도된 사용법.
왜 중요디자인 시스템 wrapper 는 *변경되는 자산* 이라, 라이브러리 lock-in 보다 코드 복사가 합리적.
면접 질문
답변 방향 힌트
"부모 상태 + 자식 구독" 구조.
반드시 언급할 키워드
- 부모 컴포넌트가 *암묵 상태* 보유 (`activeValue`)
- Context Provider 로 자식이 구독
- 자식들은 마크업이 자유롭게 조합 가능
- Trigger/Content 가 같은 value 로 매칭
- `useTabs` 훅이 Root 밖 사용을 throw
예상 꼬리 질문
- Trigger 컴포넌트의 ARIA 속성이 무엇이 되어야 하는지, 키보드 내비는 어떻게 처리하시겠습니까?
- Compound 의 단점 한 가지와 그 완화책을 답해주세요.
자기 점검
Compound Components 와 Headless Components 의 *축* 이 어떻게 다른가?
기대 키워드
자주 하는 오해
둘이 같은 개념이라고 생각하기 쉽지만, Compound 는 *합성 방식*, Headless 는 *layer 의 성격* 으로 직교한다.
asChild 패턴이 polymorphic `as` prop 보다 *낫다* 는 주장의 근거 두 가지를 답하라.
기대 키워드
자주 하는 오해
"as 도 똑같은 거 아닌가" — `as` 는 generic type explosion 과 구문상 가독성 문제가 크다.
한 번만 쓸 로직을 *Custom Hook 으로 추출* 하는 것의 단점은?
기대 키워드
자주 하는 오해
"hook 추출은 항상 좋다" — 1회용 hook 은 오히려 가독성을 해친다.
학습 자료
- Radix UI PrimitivesHeadless 컴포넌트의 사실상 표준 — Compound API + 접근성/키보드 처리.Docradix-ui.com
- React Hooks: Compound ComponentsCompound 패턴을 hook 으로 풀어내는 가장 정석적인 글.BlogKent C. Dodds
- Headless Component: a pattern for composing React UIsHeadless 패턴의 정의, 동기, 디자인 시스템 에서의 위치.Blogmartinfowler.com
- React Aria — Behaviors as hooksAdobe 의 헤드리스 React 훅 라이브러리 — ARIA 패턴을 훅으로.Docreact-spectrum.adobe.com
- 21 Fantastic React Design Patterns and When to Use Them주요 React 패턴들의 *언제 쓸지* 를 정리한 카탈로그.BlogPersson Dennis