728x90
반응형
목차
컴파운드 컴포넌트란?
Compound Components는 React의 강력한 기능 중 하나로 Compound Components를 사용하면 유연하고 재사용 가능한 컴포넌트를 설계할 수 있으며, 코드의 가독성과 유지 보수성을 높일 수 있다.
html 요소를 통해 예를 들면 <option>과 <select>가 있다. 둘은 각각 썻을 때 별 기능이 없지만 함께 하면 유용한 기능을 사용할 수 있다.
컴파운트 컴포넌트 리액트 패턴 만들기
Accordion.jsx
import { createContext, useContext, useState } from "react";
import AccordionItem from "./AccordionItem";
const AccordionContext = createContext();
export function useAccordionContext() {
const ctx = useContext(AccordionContext);
//콘텍스트가 아닌 곳에서 사용했을 경우 오류 출력
if (!ctx) {
throw new Error(
"Accordion-related components must be wrapped be <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: openItemId,
toggleItem,
};
return (
<AccordionContext.Provider value={contextValue}>
<ul className={className}>{children}</ul>
</AccordionContext.Provider>
);
}
Accordion.Item = AccordionItem;
아래와 같이 context api를 통해 useHook처럼 각각의 독립적인 상태를 만들 수 있다.
AccordionItem.jsx
import { useAccordionContext } from "./Accordion";
export default function AccordionItem({ id, title, className, children }) {
const { openItemId, toggleItem } = useAccordionContext();
//열리고 닫힘 구현
const isOpen = openItemId === id;
return (
<li className={className}>
<h3 onClick={() => toggleItem(id)}>{title}</h3>
<div
className={
isOpen ? "accordion-item-content open" : "accordion-item-content"
}
>
{children}
</div>
</li>
);
}
위의 코드를 더 재활용 가능하게 만들 수는 없을까?
제목과 내용또한 분리한 뒤 구성요소에 추가 해주자!
AccordionTitle.jsx
import { useAccordionContext } from "./Accordion";
export default function AccordionTitle({ className, id, children }) {
const { toggleItem } = useAccordionContext();
return (
<h3 className={className} onClick={() => toggleItem(id)}>
{children}
</h3>
);
}
AccordionContent.jsx
import { useAccordionContext } from "./Accordion";
export default function AccordionContent({ className, children, id }) {
const { openItemId } = useAccordionContext();
const isOpen = openItemId === id;
return (
<div
className={
isOpen ? `${className ?? ""} open` : `${className ?? ""} close`
}
>
{children}
</div>
);
}
아래 컴포넌트가 훨씬 더 간결해진 것을 볼 수 있다.
AccordionItem.jsx
export default function AccordionItem({ className, children }) {
return <li className={className}>{children}</li>;
}
Pattern.jsx
import Accordion from "./components/Arcodian/Accordion";
function Pattern() {
return (
<main>
<section>
<h2>Why work with us?</h2>
<Accordion className={"accordion"}>
<Accordion.Item className={"accordion-item"}>
<Accordion.Title id="experience" className={"accordion-item-title"}>
"we got 20 years of experience"
</Accordion.Title>
<Accordion.Content
className={"accordion-item-content"}
id="experience"
>
<article>
<p>contents..</p>
<p>contents..</p>
</article>
</Accordion.Content>
</Accordion.Item>
<Accordion.Item className={"accordion-item"}>
<Accordion.Title
id="experience2"
className={"accordion-item-title"}
>
"we got 40 years of experience"
</Accordion.Title>
<Accordion.Content
id="experience2"
className={"accordion-item-content"}
>
<article>
<p>contents..</p>
<p>contents..</p>
</article>
</Accordion.Content>
</Accordion.Item>
</Accordion>
</section>
</main>
);
}
export default Pattern;
위의 코드를 보면 content와 title모두에 id를 전달하는 것을 볼 수 있다.
이것을 한번만 전달하도록 context로 더 최적화 해보자
이것 또한 context로 id만 전달해 주면 인자로 넘겨줄 필요가 없다.
AccordionItem.jsx
import { createContext, useContext} from "react";
const AccordionItemContext = createContext();
export function useAccordionItemContext() {
const ctx = useContext(AccordionItemContext);
if (!ctx) {
throw new Error(
"AccordionItem-related components must be wrapped by <Accordion.Item>"
);
}
return ctx;
}
export default function AccordionItem({ id, className, children }) {
return (
<AccordionItemContext.Provider value={id}>
<li className={className}>{children}</li>
</AccordionItemContext.Provider>
);
}
AccordionTitle.jsx
import { useAccordionContext } from "./Accordion";
import { useAccordionItemContext } from "./AccordionItem";
export default function AccordionTitle({ className, children }) {
const id = useAccordionItemContext();
const { toggleItem } = useAccordionContext();
return (
<h3 className={className} onClick={() => toggleItem(id)}>
{children}
</h3>
);
}
AccordionContent.jsx
import { useAccordionContext } from "./Accordion";
import { useAccordionItemContext } from "./AccordionItem";
export default function AccordionContent({ className, children }) {
const id = useAccordionItemContext();
const { openItemId } = useAccordionContext();
const isOpen = openItemId === id;
return (
<div
className={
isOpen ? `${className ?? ""} open` : `${className ?? ""} close`
}
>
{children}
</div>
);
}
완성된 Pattern.jsx
import Accordion from "./components/Arcodian/Accordion";
function Pattern() {
return (
<main>
<section>
<h2>Why work with us?</h2>
<Accordion className={"accordion"}>
<Accordion.Item id="experience" className={"accordion-item"}>
<Accordion.Title className={"accordion-item-title"}>
"we got 20 years of experience"
</Accordion.Title>
<Accordion.Content className={"accordion-item-content"}>
<article>
<p>contents..</p>
<p>contents..</p>
</article>
</Accordion.Content>
</Accordion.Item>
<Accordion.Item id="experience2" className={"accordion-item"}>
<Accordion.Title className={"accordion-item-title"}>
"we got 40 years of experience"
</Accordion.Title>
<Accordion.Content className={"accordion-item-content"}>
<article>
<p>contents..</p>
<p>contents..</p>
</article>
</Accordion.Content>
</Accordion.Item>
</Accordion>
</section>
</main>
);
}
export default Pattern;
그외에 다양한 패턴들을 만나보고 싶다면..
참고
https://patterns-dev-kr.github.io/
반응형
'코딩 정보 > React' 카테고리의 다른 글
[React][Vite] 프로젝트 시작하기 전 구축을 해보자 (1) | 2024.08.28 |
---|---|
[React][React-Query] React-Query를 통해 fallback을 구현해 보자 (0) | 2024.08.27 |
[React][Tanstack Query] 개념과 활용 방법에 대해 알아보자 (0) | 2024.08.26 |
[React] 배포 과정에 대해서 알아보자 (0) | 2024.08.24 |
[React] 로그인 시 토큰 기반 인증 만들어보기 (0) | 2024.08.24 |