Compound Component는 부모와 자식 컴포넌트 간의 공통된 상태를 공유한다. 이 패턴은 컴포넌트에서 로직과 UI를 명시적으로 분리할 수 있어 관리가 쉽다는 장점이 있다.
대표적인 예로 <select>
와 <option>
의 관계를 들 수 있다.
< select name = "pets" >
< option value = "dog" >Dog</ option >
< option value = "cat" >Cat</ option >
< option value = "hamster" >Hamster</ option >
< option value = "parrot" >Parrot</ option >
< option value = "spider" >Spider</ option >
< option value = "goldfish" >Goldfish</ option >
</ select >
<select>
는 부모 컴포넌트 로서 공통 상태와 로직을 제공하고 관리한다.
<option>
은 자식 컴포넌트 로서 부모 컴포넌트가 관리하는 상태와 로직에 따라 동작한다.
React에서 이러한 Compound Pattern을 활용하면, 상태와 로직을 부모에서 관리하고 자식 컴포넌트는 UI 역할에 집중하도록 설계할 수 있다.
이번 글에서는 Stepper 컴포넌트 를 예시로 Compound Pattern을 적용하는 방법을 살펴보자.
# Stepper Compound Component 구현하기
# Stepper란?
Stepper는 사용자가 특정 작업의 진행 상태를 단계별로 시각화할 수 있는 UI 요소입니다. 예를 들어, 쇼핑몰 결제 과정에서
장바구니
배송 정보 입력
결제 정보 입력
주문 확인
이러한 순차적인 단계를 나타낼 때 자주 사용된다.
# Stepper 구조 설계
Stepper 컴포넌트에는 기본적으로 다음 세가지의 구조로 구성된다:
Stepper
: 공통 상태를 **Stepper.Step
과 Stepper.Navigation
**같은 자식 컴포넌트에게 제공한다.
Stepper.Step
: 특정 단계의 내용을 표시하는 자식 컴포넌트 .
Stepper.Navigation
: 특정 단계로 이동할 수 있는 네비게이션 UI를 제공하는 자식 컴포넌트 .
# 코드 구현
# 1. Stepper 컨텍스트 생성
React의 Context API
를 사용해 Stepper의 자식들에게 상태를 제공한다.
import { useContext } from 'react'
import { createContext, useState, type ReactNode } from 'react'
const StepperContext = createContext <{ ids : string []; step : string ; setStep : ( step : string ) => void }>( null ! )
type StepperProps = { children : ReactNode ; ids : string []; defaultId ?: string }
export default function Stepper ({ children, ids, defaultId } : StepperProps) {
const [ step , setStep ] = useState < string >(defaultId ! )
return < StepperContext.Provider value ={ { ids, step, setStep } } > { children } </ StepperContext.Provider >
}
# 2. Step 컴포넌트 구현
각 단계의 내용을 표시하는 컴포넌트. 현재 step
과 id
가 일치할 때만 렌더링된다.
function Step ({ id , children } : { id : string ; children : ReactNode }) {
const { step } = useContext (StepperContext)
return id === step && <> { children } </>
}
Stepper.Step = Step
# 4. Navigation 컴포넌트 구현
사용자가 이전, 이후 단계를 선택할 수 있도록 구성한다.
function Navigation () {
const { ids , step , setStep } = useContext (StepperContext)
const prevStep = () => setStep (ids[ids. indexOf (step) - 1 ])
const nextStep = () => setStep (ids[ids. indexOf (step) + 1 ])
return (
< div >
< button disabled ={ ids. at ( 0 ) === step } onClick ={ prevStep } >
이전
</ button >
< button disabled ={ ids. at ( - 1 ) === step } onClick ={ nextStep } >
다음
</ button >
</ div >
)
}
Stepper.Navigation = Navigation
# 사용 예시
위에서 구성한 Stepper
를 활용하여 간단한 UI를 구성해보자.
import Stepper from './Stepper'
function App () {
return (
< Stepper defaultId = "step1" ids ={ [ 'step1' , 'step2' , 'step3' ] } >
< Stepper.Step id = "step1" >
< div >1단계: 사용자 정보 입력</ div >
</ Stepper.Step >
< Stepper.Step id = "step2" >
< div >2단계: 배송 정보 입력</ div >
</ Stepper.Step >
< Stepper.Step id = "step3" >
< div >3단계: 결제 정보 입력</ div >
</ Stepper.Step >
< Stepper.Navigation />
</ Stepper >
)
}
# 마무리
Compound Pattern은 Radix-UI와 같은 UI 라이브러리에서 자주 볼 수 있는 패턴이다.
select. dialog, form, card 등 다양한 UI들을 Compound Pattern을 적용시키면 UI와 로직의 역할을 분리하고, 재사용성과 확장성을 높일 수 있다.
Stepper 예제처럼 상태와 로직을 부모에서 관리하며, 자식 컴포넌트는 자신의 역할에만 집중할 수 있도록 설계해보자.
Compound Pattern은 React 애플리케이션에서 복잡한 UI를 설계할 때 좋은 패턴이다.
# References
patterns.dev - Compound Pattern
react.dev - createContext