Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- react
- 백준1264
- 웹개발
- 코드개선
- securitypatch
- prettier
- 웹프로토콜
- 프론트엔드
- 백준
- 개발자성장
- TypeScript
- 개발공부
- React Query
- 개발블로그
- Nest.js
- 개발
- Design system
- WebDev
- nextjsmiddleware
- frontend
- 알고리즘
- 리액트쿼리
- 그룹단어
- 리액트
- 디자인 시스템
- 알고리즘스터디
- 모음의개수
- NextJs
- jotai
- Next.js
Archives
- Today
- Total
한땀한땀
헤드리스 컴포넌트와 합성 컴포넌트: 학습한 내용 정리 본문
프론트엔드 개발에서 UI 컴포넌트를 설계할 때, 재사용성과 확장성을 고려하는 것이 중요하다는 것을 다시금 깨달았습니다. 이 과정에서 "헤드리스 컴포넌트(Headless Component)"와 "합성 컴포넌트(Compound Component)"라는 개념을 배우게 되었고, 이를 정리해 보려고 합니다. 혹시 잘못된 부분이 있다면 언제든 피드백 부탁드립니다! 😊
헤드리스 컴포넌트 (Headless Component)
개념
헤드리스 컴포넌트는 UI를 직접 렌더링하지 않고, 상태와 로직만을 관리하는 컴포넌트다. UI를 어떻게 렌더링할지는 이를 사용하는 쪽에서 결정하도록 설계되어 있어, 유연성이 높고 재사용성이 뛰어나다.
예제
import { useState, ReactNode } from 'react';
type DropdownProps = {
children: (isOpen: boolean, toggle: () => void) => ReactNode;
};
const Dropdown = ({ children }: DropdownProps) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return <>{children(isOpen, toggle)}</>;
};
// 사용 예시
const MyComponent = () => {
return (
<Dropdown>
{(isOpen, toggle) => (
<div>
<button onClick={toggle}>토글</button>
{isOpen && <div className="dropdown-menu">메뉴 내용</div>}
</div>
)}
</Dropdown>
);
};
이와 같이 설계하면, Dropdown 컴포넌트는 오직 상태만을 관리하고, 사용자는 원하는 방식으로 UI를 구성할 수 있다
합성 컴포넌트 (Compound Component)
개념
합성 컴포넌트는 하나의 컴포넌트를 여러 개의 작은 컴포넌트로 나누어 내부적으로 조합할 수 있도록 설계하는 방식이다. 부모 컴포넌트가 자식 컴포넌트들에게 상태나 컨텍스트를 제공하고, 각각의 자식 컴포넌트는 이 정보를 활용하여 UI를 렌더링한다.
React의 Context API를 활용하는 것이 일반적이며, 대표적인 예로 Tabs, Accordion 패턴이 있다.
예제
import { createContext, useContext, useState, ReactNode } from 'react';
const TabsContext = createContext<{ activeTab: string; setActiveTab: (tab: string) => void } | null>(null);
type TabsProps = { children: ReactNode };
const Tabs = ({ children }: TabsProps) => {
const [activeTab, setActiveTab] = useState('');
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs-container">{children}</div>
</TabsContext.Provider>
);
};
type TabProps = { name: string; children: ReactNode };
const Tab = ({ name, children }: TabProps) => {
const context = useContext(TabsContext);
if (!context) throw new Error('Tab must be used within a Tabs');
return context.activeTab === name ? <div className="tab-content">{children}</div> : null;
};
type TabListProps = { children: ReactNode };
const TabList = ({ children }: TabListProps) => {
return <div className="tab-list">{children}</div>;
};
type TabButtonProps = { name: string; children: ReactNode };
const TabButton = ({ name, children }: TabButtonProps) => {
const context = useContext(TabsContext);
if (!context) throw new Error('TabButton must be used within a Tabs');
return (
<button className="tab-button" onClick={() => context.setActiveTab(name)}>
{children}
</button>
);
};
// 사용 예시
const MyTabs = () => {
return (
<Tabs>
<TabList>
<TabButton name="tab1">Tab 1</TabButton>
<TabButton name="tab2">Tab 2</TabButton>
</TabList>
<Tab name="tab1">이것은 Tab 1의 내용입니다.</Tab>
<Tab name="tab2">이것은 Tab 2의 내용입니다.</Tab>
</Tabs>
);
};
이와 같이 설계하면, Tabs 내부의 구조를 유연하게 조합할 수 있다. TabList, TabButton, Tab이 각각 독립적으로 동작하지만, Tabs를 통해 연결된다.
헤드리스 컴포넌트와 합성 컴포넌트 함께 사용하기
이 두 가지 개념을 조합하여 더 유연한 UI 컴포넌트를 만들 수도 있다. 예를 들어, 헤드리스 상태 관리를 활용한 Accordion을 합성 컴포넌트 방식으로 구현할 수 있습니다.
const useAccordion = () => {
const [openIndex, setOpenIndex] = useState<number | null>(null);
const toggle = (index: number) => setOpenIndex(openIndex === index ? null : index);
return { openIndex, toggle };
};
const AccordionContext = createContext<{ openIndex: number | null; toggle: (index: number) => void } | null>(null);
const Accordion = ({ children }: { children: ReactNode }) => {
const accordion = useAccordion();
return <AccordionContext.Provider value={accordion}>{children}</AccordionContext.Provider>;
};
const AccordionItem = ({ index, title, children }: { index: number; title: string; children: ReactNode }) => {
const context = useContext(AccordionContext);
if (!context) throw new Error('AccordionItem must be used within an Accordion');
return (
<div>
<button onClick={() => context.toggle(index)}>{title}</button>
{context.openIndex === index && <div>{children}</div>}
</div>
);
};
이렇게 하면 상태는 헤드리스 훅에서 관리하고, UI 구조는 합성 컴포넌트 방식으로 정의할 수 있다.
'FrontEnd Dev' 카테고리의 다른 글
React에서 Buzzvil CPE/CPA 연동하기 – 실제 사례 기반 구현 가이드 (2) | 2025.04.14 |
---|---|
React Query 버전 업그레이드 및 커스텀 타입 제거 (0) | 2025.04.04 |
Next.js 13에서 15로 마이그레이션 (0) | 2025.04.01 |
Next.js 보안 이슈 대응: x-middleware-subrequest 헤더 우회 차단하기 (0) | 2025.03.31 |
나만의 Next.js 프로젝트 컨벤션 (0) | 2025.03.09 |