본문 바로가기
코딩 정보/React

[React][React Pattern] 컴파운드 컴포넌트에 대해 알아보자

by 꽁이꽁설꽁돌 2024. 8. 26.
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/

     

    Home

    Patterns.dev.kr 은 웹 앱의 성능을 위한 바닐라 자바스크립트와 React기반의 디자인 패턴과 컴포넌트 패턴에 대한 정보를 제공합니다.

    patterns-dev-kr.github.io

     

    반응형