React는 깊은 상속 계층보다 작은 컴포넌트를 조합해 화면을 만들어. children, 이름 붙은 슬롯, compound component를 언제 고를지 차례로 익혀 보자.
먼저 children으로 빈자리를 열어
Wrapper가 바깥 모양을 소유하고 호출자가 안쪽 내용을 정하면 children이 가장 단순한 계약이야. Card는 테두리와 여백만 렌더링하고, 제목이나 버튼은 Card 안에 둔 JSX가 결정하게 할 수 있어. 부모 컴포넌트는 자식의 구체적인 종류를 몰라도 되므로 새 콘텐츠가 생길 때마다 prop을 추가할 필요가 없어.
타입은 보통 children: React.ReactNode로 둬. ReactNode에는 JSX element뿐 아니라 문자열, 숫자, fragment, null처럼 React가 렌더링할 수 있는 값이 모두 들어가. 하나의 JSX element를 검사하거나 clone해야 하는 특별한 경우에만 더 좁은 ReactElement를 검토해.
자리가 여러 개면 이름 붙은 슬롯을 써
Dialog처럼 header, body, footer의 위치가 정해져 있다면 각각을 ReactNode prop으로 받는 편이 읽기 쉬워. 호출자는 header={...}와 footer={...}를 보며 어느 내용이 어디에 들어가는지 바로 알 수 있고, Dialog는 접근성 역할과 레이아웃 순서를 한곳에서 지켜.
슬롯은 자식끼리 상태를 공유하지 않아도 되는 화면에 잘 맞아. 선택 상태나 등록 순서까지 자식들이 알아야 하는 게 아니라면 Context를 추가할 이유가 없어. children 하나로 충분한지 먼저 보고, 서로 다른 위치가 실제로 필요할 때 이름 붙은 슬롯으로 확장해.
자식들이 같은 상태를 알아야 하면 compound component를 검토해
Tabs가 선택된 값을 소유하고 Tab 자식들이 자신이 선택됐는지 읽어야 한다면 좁은 Context를 사용한 compound component가 자연스러울 수 있어. 호출 지점은 <Tabs><Tab value="recent">최근</Tab></Tabs>처럼 도메인 구조를 그대로 보여 주고, 키보드 이동과 선택 규칙은 Tabs 가족 안에 모여.
대신 Tab이 Tabs 밖에서 렌더링됐을 때 이해하기 쉬운 오류를 내야 하고, 암묵적인 Context 의존성도 문서화해야 해. 라이브러리처럼 같은 사용법이 여러 화면에서 반복될 때는 가치가 크지만, 한 화면의 단순한 레이아웃에 쓰면 구조만 무거워질 수 있어.
asChild는 실제 DOM element를 호출자가 고르게 해
스타일이 있는 Button이 어떤 곳에서는 button이고 다른 곳에서는 Link여야 할 수 있어. Radix UI가 널리 알린 asChild와 Slot 패턴은 불필요한 wrapper를 만들지 않고 자식 element를 실제 DOM node로 사용해. 컴포넌트는 className, 이벤트 핸들러, ref를 자식과 안전하게 병합해야 하고, 호출자는 anchor 안에 button을 넣는 잘못된 중첩을 피할 수 있어.
이 병합 계약은 생각보다 까다로워. 이벤트 순서, ref 결합, disabled 의미가 모두 맞아야 하므로 앱마다 어설픈 cloneElement helper를 만들기보다 검증된 primitive를 쓰는 편이 좋아.
동작을 재사용하려면 상속보다 함수와 훅을 조합해
화면 구조는 children과 슬롯으로, 상태 로직은 커스텀 훅으로, 데이터 변환은 평범한 함수로 나누면 돼. HOC가 필요한 기존 라이브러리는 계속 사용할 수 있지만 새 앱 코드에서 wrapper 계층을 먼저 만들 이유는 드물어. 호출 지점과 데이터 흐름이 그대로 보이는 가장 작은 조합부터 시작해.