ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Compound 패턴
    React/design patterns 2023. 12. 14. 12:57

    컴파운드 컴포넌트 패턴은 여러 컴포넌트들이 모여 하나의 동작을 할 수 있게 해준다.

    디자인시스템을 만들기 위해 래퍼런스를 찾는 중, Bootstrap이 해당 패턴을 사용하여 컴포넌트를 만들고 있었다.

     

    장점: 컴파운드 패턴은 동작 구현에 필요한 상태를 내부적으로 가지고 있는데 이것을 사용하는 쪽에서는 드러나지 않아 걱정 없이 사용할 수 있다. 또 이 패턴을 사용하면 자식 컴포넌트들을 일일히 import할 필요 없이 기능을 사용할 수 있다.

     

     

    Context API 를 활용해서 컴파운드 패턴을 구현했고,

    사용자가 토글 버튼을 눌렀을 때 나타날 메뉴를 렌더링하는 컴포넌트를 만들어 보았다.

     

    Toggle, List, Item 컴포넌트가 FlyOutContext Provider에 접근할 수 있도록 해당 컴포넌트는 FlyOut의 자식 컴포넌트로 렌더링해야 한다. Static property로 만들어 줬다.

    import React, { useState } from 'react';
    import { createContext, useContext } from 'react';
    
    // Context Create
    const FlyOutContext = createContext();
    
    // Provider
    export function FlyOut({ children }) {
      const [open, setOpen] = useState(false);
      const toggle = () => {
        setOpen((prev) => !prev);
      };
      const providerValue = { open, toggle };
      return (
        <FlyOutContext.Provider value={providerValue}>
          {children}
        </FlyOutContext.Provider>
      );
    }
    
    // Children Component
    function Toggle() {
      const { open, toggle } = useContext(FlyOutContext);
    
      return (
        <div
          className="absolute p-[10px] h-[50px] border bg-black"
          onClick={() => toggle(!open)}
        >
          토글
        </div>
      );
    }
    
    function List({ children }) {
      const { open } = useContext(FlyOutContext);
    
      return (
        open && (
          <ul className="absolute top-[50px] bg-[skyblue] px-[10px]">{children}</ul>
        )
      );
    }
    
    function Item({ children }) {
      return <li>{children}</li>;
    }
    
    // Toggle 컴포넌트가 FlyOutContext 프로바이더에 접근할 수 있도록 해당 컴포넌트는 FlyOut의 자식 컴포넌트로 렌더링 해야한다.
    FlyOut.Toggle = Toggle;
    FlyOut.List = List;
    FlyOut.Item = Item;

     

     

    아래와 같이 FlyOut 컴포넌트만 import 해줘도 Toggle, List, Item 컴포넌트 등을 사용할 수 있게 된다.

    import React from 'react';
    import { useNavigate } from 'react-router-dom';
    import classNames from 'classnames';
    import formatAgo from '../../util/date';
    import styles from './VideoCard.module.scss';
    import { FlyOut } from '../FlyOut/FlyOut';
    
    export default function VideoCard({ video, type, ...rest }) {
      const { title, channelTitle, publishedAt, thumbnails } = video.snippet;
      const navigate = useNavigate();
      const isList = type === 'list';
      return (
        <li
          className={isList ? 'flex gap-1 m-2' : 'relative'}
          onClick={() => {
            navigate(`/videos/watch/${video.id}`, { state: { video } });
          }}
        >
          <FlyOut>
            <FlyOut.Toggle />
            <FlyOut.List>
              <FlyOut.Item>수정</FlyOut.Item>
              <FlyOut.Item>삭제</FlyOut.Item>
            </FlyOut.List>
          </FlyOut>
    
          <img
            className={isList ? 'w-60 mr-2' : 'w-full'}
            src={thumbnails.medium.url}
            alt={title}
          />
    
          <div className={styles.content}>
            <h3 className={classNames(styles.title, 'line-clamp-3')}>{title}</h3>
            <p className={styles.channel}>{channelTitle}</p>
            <p className={styles.publishedAt}>{formatAgo(publishedAt, 'ko')}</p>
          </div>
        </li>
      );
    }

     

     

    결과)

     

    댓글

Designed by Tistory.