애플리케이션에는 상호 의존적인 컴포넌트가 있는 경우가 많습니다.
이들은 state와 로직을 함께 공유하며 서로 의존하고 있습니다.
select, 드롭다운 컴포넌트 또는 메뉴 항목과 같은 컴포넌트에서 이러한 패턴을 자주 볼 수 있습니다.
복합 컴포넌트 패턴을 사용하면 하나의 작업을 수행하기 위해 모두 함께 작동하는 컴포넌트를 만들 수 있습니다.
Chakra UI 에서 볼 수 있는
<Tabs>
<Tabs.TabList>
<Tabs.Tab>탭1</Tabs.Tab>
<Tabs.Tab>탭2</Tabs.Tab>
</Tabs.TabList>
<Tabs.TabPanels>
<Tabs.TabPanel>내용1</Tabs.TabPanel>
<Tabs.TabPanel>내용2</Tabs.TabPanel>
</Tabs.TabPanels>
</Tabs>
<상위 컴포넌트. 하위 컴포넌트 명> 으로 컴포넌트 이름을 명명하는 게 특징 입니다.
Context API
React.Children.map 이나 React .cloneElement 를 사용할 경우
1. React.Children.map의 경우 Wrapper로 감싸면 props 전달이 안되어 컴포넌트 중첩이 제한되고
2.React .cloneElement 는 props 의 얕은 병합이 수행되어, 이미 존재하는 props는 새 props와 병합 혹은 덮어써지는 문제가 발생하게 됩니다.
그래서 context api로 훨씬 깔끔히 구현이 가능합니다.
- 중첩 깊이에 상관없이 데이터 접근 가능
- Props 충돌 문제 없음
- 더 명확한 컴포넌트 관계
- TypeScript와의 더 나은 통합
하나를 누르면 클릭한 목록은 열리고, 다른 목록은 닫히는 아코디언 메뉴를 만든다고 가정합시다
// App.jsx
import Accordion from "./components/Accordion/Accordion";
function App() {
return (
<section>
<h2>너 내 동료가 되어라?</h2>
<Accordion className="accordion">
<Accordion.Item id='experience' className="accordion-item" title="우린 20년의 역사가 있다">
<article>
<p>배멀미는 없는가?</p>
<p>대한민국 국적의 어선의 경우 보통 참치배라 부르는 다랑어 선망어선이나 다랑어 연승어선이 널리 알려져 있고, 트롤어선,메로라 불리는 이빨고기 저연승어선, 꽁치를 잡는 봉수망어선과 오징어채낚이선 등이 흔한 축에 속한다.</p>
</article>
</Accordion.Item>
<Accordion.Item id='recruit' className="accordion-item" title="어서 입사해라">
<article>
<p>새우는 좋아하는가?</p>
<p>다만 현재 원양어선에 초임 승조원으로 승선하기 위해서는 해기사 면허가 필수이며 면허가 없다면 승선이 힘들다</p>
</article>
</Accordion.Item>
</Accordion>
</section>
)
}
export default App;
이와 같은 구조가 있다면 현재 App 컴포넌트에서는 Accordion 만 임포트하고 있는걸 확인 할 수 있습니다.
// Accordion.jsx
import { createContext, useContext, useState } from "react";
import AccordionItem from "./AccordionItem.jsx";
const AccordionContext = createContext();
export function useAccordionContext() {
const ctx = useContext(AccordionContext);
if (!ctx) throw new Error('Accordion components must be used within an Accordion')
return ctx;
}
export default function Accordion({ children, className }) {
const [openItemId, setOpenItemId] = useState(null);
function toggleItem(id) {
setOpenItemId(prevId => prevId === id ? null : id);
}
const contextValue = {
openItemId,
toggleItem,
}
return (
<AccordionContext.Provider value={contextValue}>
<ul className={className}>
{children}
</ul>
</AccordionContext.Provider>
)
}
Accordion.Item = AccordionItem
상위 컴포넌트인 Accordion.jsx 에서 createContext 로 context api를 구현 후, 하위 컴포넌트를 감싸고
openItemId state 와 openItemId setter 함수인 toggleItem 함수를 공유해주는 역할을 하고 있습니다.
AccordionItem을 import 하여 Accordion.Item = AccordionItem 로 변환해주는 것도 이 컴포넌트의 역할 입니다.
// AccordionItem.jsx
import { useAccordionContext } from "./Accordion";
export default function AccordionItem({ id, className, title, children }) {
const { openItemId, toggleItem } = useAccordionContext();
const isOpen = openItemId === id;
function handleClick() {
toggleItem(id);
}
return (
<li className={className}>
<h3 onClick={handleClick}>{title}</h3>
<div className={isOpen ? 'accordion-item-content open' : 'accordion-item-content' }>{children}</div>
</li>
)
}
하위 컴포넌트에서는 현재 클릭한 id 가 내 id 라면 display: block 으로 보여주고
아닐 경우는 display: none 으로 AccordionItem 을 안보여주고 있습니다
// index.css
<style>
.accordion-item-content {
display: none;
padding: 1rem;
background-color: #2c344a;
}
.open {
display: block;
}
</style>
이 처럼 context api 를 사용하여 상호 공유된 state와 로직을 통해 상호 동작을 하는 모습을 구현할 수 있습니다
참고 : https://www.patterns.dev/react/compound-pattern
'Design & Rendering Patterns > React) Design Patterns' 카테고리의 다른 글
[React Design] HOC Pattern 고차 컴포넌트 패턴 (0) | 2024.11.25 |
---|