React에서 Slot 패턴과 Coumpound Component 패턴 사용

DongHyun Park·2024년 6월 9일
0

React

목록 보기
6/6
post-thumbnail

React에서 Slot 패턴과 Compound Component 패턴을 사용하여 컴포넌트를 구성하고 재사용성을 높일 수 있습니다. 각각의 패턴을 이해하고 적용 방법을 살펴보겠습니다.

Slot 패턴

Slot 패턴은 주로 다른 프레임워크, 특히 Vue.js에서 사용되는 패턴입니다. 이 패턴은 컴포넌트 내부의 특정 위치에 자식 콘텐츠를 삽입할 수 있도록 합니다.

  1. Slot 컴포넌트
    특정 이름을 가진 슬롯에 자식 컴포넌트를 랜더링할 수 있습니다.

    // Slot.tsx
    import { ReactNode } from "react";
    
    interface SlotProps {
      name: string;
      children: ReactNode;
    }
    
    const Slot = ({ children }: SlotProps) => {
      return <>{children}</>;
    };
    
    export default Slot;
  2. createSlots 함수
    React의 children을 특정 슬롯 이름과 매칭시켜 슬롯 맵을 생성하는 유틸리티 함수입니다. 이 함수는 ReactNode 타입의 자식 요소들과 슬롯 이름 목록을 입력받아, 각 슬록 이름에 해당하는 자식 콘텐츠를 매핑할 객체를 반환합니다.

    // createSlots.tsx
    import { ReactNode, Children, isValidElement } from "react";
    
    interface SlotMap {
      [key: string]: ReactNode;
    }
    
    const createSlots = (children: ReactNode, slots: string[]): SlotMap => {
      const slotMap: SlotMap = {};
    
      Children.forEach(children, (child) => {
        if (isValidElement(child)) {
          const slotName = child.props.name;
          if (slotName && slots.includes(slotName)) {
            slotMap[slotName] = child.props.children;
          }
        }
      });
      return slotMap;
    };
    
    export default createSlots;
  3. Todo 컴포넌트
    Slot 패턴을 사용하여 자식 컴포넌트를 특정 슬롯에 매핑하고 렌더링합니다.

    // Todo.tsx
    import { ReactNode } from "react";
    import createSlots from "../utils/createSlots";
    import TodoProvider from "../Todo/TodoProvider";
    
    interface TodoProps {
      children: ReactNode;
    }
    
    const Todo = ({ children }: TodoProps) => {
      const slots = createSlots(children, ["Header", "Input", "ListArea"]);
    
      return (
        <TodoProvider>
          {slots.Header}
          {slots.Input}
          <div className="mt-5">{slots.ListArea}</div>
        </TodoProvider>
      );
    };
    
    export default Todo;
  4. App.tsx
    App에서 하위 컴포넌트를 Todo 컴포넌트의 특정 슬롯에 배치합니다.

   // App.tsx
   import Divider from "./components/Divider/Divider";
   import TodoHeader from "./components/Header/TodoHeader";
   import TodoInput from "./components/Input/TodoInput";
   import TodoList from "./components/List/TodoList";
   import TodoListTools from "./components/Tools/TodoListTools";
   import TodoListArea from "./components/List/TodoListArea";
   import Todo from "./components/Todo";
   import Slot from "./components/Slot";

   function App() {
     return (
       <main className="min-w-[500px] p-8 mt-8 | bg-white rounded-2xl shadow-lg">
         <Todo>
           <Slot name="Header">
             <TodoHeader />
           </Slot>
           <Slot name="Input">
             <TodoInput className="mt-5" />
           </Slot>
           <Slot name="ListArea">
             <TodoListArea className="mt-5">
               <TodoListTools />
               <Divider />
               <TodoList />
             </TodoListArea>
           </Slot>
         </Todo>
       </main>
     );
   }

   export default App;

장점

  • 유연성: 자식 컴포넌트를 특정 슬롯에 배치할 수 있어 유연하게 컴포넌트를 구성할 수 있습니다.
  • 가독성: 어떤 슬롯에 어떤 컴포넌트가 배치될지 명확하게 보여줍니다.
  • 분리된 로직: 컴포넌트의 로직과 레이아웃을 분리할 수 있습니다.

단점

  • 복잡성: 슬록을 관리하는 로직이 복잡해질 수 있습니다.
  • 타입 안전성: TypeScript를 사용할 때 슬롯 이름과 자식 컴포넌트 간의 타입 안전성을 보장하기 어렵습니다.

Compound Component Pattern

Compound Component 패턴은 여러 개의 하위 컴포넌트가 함께 작동하여 하나의 부모 컴포넌트의 기능을 구현하는 패턴입니다.

  1. Tabs 컴포넌트
    children의 요소를 React.Children.map 을 사용하여 순회하면서 각 자식 요소가 유효한 요소인지 확인합니다.
// tabs.tsx
import React, { useState, ReactNode } from "react";

 interface TabsProps {
   children: ReactNode;
 }

 const Tabs = ({ children }: TabsProps) => {
   const [activeIndex, setActiveIndex] = useState(0);

   return (
     <div>
       <div className="tabs">
         {React.Children.map(children, (child, index) => {
           if (React.isValidElement(child)) {
             return (
               <button onClick={() => setActiveIndex(index)}>
                 {child.props.label}
               </button>
             );
           }
           return null;
         })}
       </div>
       <div className="content">
         {React.Children.toArray(children)[activeIndex]}
       </div>
     </div>
   );
 };

 interface TabProps {
   label: string;
   children: ReactNode;
 }

 Tabs.Tab = ({ children }: TabProps) => {
   return <div>{children}</div>;
 };

 export default Tabs;
  1. App.tsx
	// App.tsx
    import Tabs from "./Common/Tabs";

    function App() {
      return (
        <div className="App">
          <Tabs>
            <Tabs.Tab label="Tab 1">Content 1</Tabs.Tab>
            <Tabs.Tab label="Tab 2">Content 2</Tabs.Tab>
            <Tabs.Tab label="Tab 3">Content 3</Tabs.Tab>
          </Tabs>
        </div>
      );
    }

    export default App;

장점

  • 구조화된 구성: 여러 하위 컴포넌트를 그룹화하여 부모 컴포넌트의 기능을 명확하게 합니다.
  • 상태 공유: 부모 컴포넌트의 상태를 하위 컴포넌트들이 쉽게 공유하고 사용할 수 있습니다.
  • 유지보수성: 관련된 컴포넌트들이 하나의 모듈로 관리되어 유지보수가 용이합니다.

단점

  • 결합도 증가: 컴포넌트 간의 결합도가 높아져 독립적으로 사용하기 어려울 수 있습니다

결론적으로

Slot 패턴과 Compound Component 패턴은 각각의 장단점이 있으며, 특정 상황에 따라 유용하게 사용할 수 있습니다. Slot 패턴은 자식 요소를 특정 슬롯에 배치하는 데 유용하며, Compound Component 패턴은 여러 하위 컴포넌트를 논리적으로 그룹화하여 부모 컴포넌트의 기능을 구현하는 데 유용합니다. 실무에서 이 두 패턴을 적용해보면서 각 패턴의 적합성을 경험하고, 프로젝트의 요구 사항에 맞게 적절한 패턴을 선택하여 사용하는 것이 중요하다 생각합니다.

참고

0개의 댓글