리액트 컴포넌트 패턴

Yujin Jung·3일 전

리액트에서 복잡한 UI를 만들다 보면 재사용성, 확장성, 그리고 명확한 책임 분리가 매우 중요해진다.
실제 자주 사용되는 세 가지 패턴을 정리해보았다.

- Compound Component Pattern
- Higher-Order Component(HOC)
- React Portal


✔️ Compound Component Pattern

컴파운드 패턴은 여러 개의 컴포넌트가 마치 하나의 컴포넌트처럼 함께 동작하도록 만드는 패턴이다.
React의 Context API와 주로 함께 사용된다.

언제 쓰는가?

  • 드롭다운, 모달, 탭 UI처럼 “부모 안에 여러 하위 요소가 협력하는 구조”가 필요할 때
  • <Select>, <Select.Option>처럼 개발자가 사용하기 쉽고 직관적인 API를 만들고 싶을 때

예시 코드 – Tabs Component

import { createContext, useContext, useState } from "react";

const TabsContext = createContext<any>(null);

export function Tabs({ children, defaultValue }) {
  const [active, setActive] = useState(defaultValue);
  return (
    <TabsContext.Provider value={{ active, setActive }}>
      <div>{children}</div>
    </TabsContext.Provider>
  );
}

Tabs.List = function TabsList({ children }) {
  return <div>{children}</div>;
};

Tabs.Trigger = function TabsTrigger({ value, children }) {
  const { active, setActive } = useContext(TabsContext);
  return (
    <button
      onClick={() => setActive(value)}
      style={{
        fontWeight: active === value ? "bold" : "normal",
      }}
    >
      {children}
    </button>
  );
};

Tabs.Content = function TabsContent({ value, children }) {
  const { active } = useContext(TabsContext);
  return active === value ? <div>{children}</div> : null;
};

사용법

<Tabs defaultValue="tab1">
  <Tabs.List>
    <Tabs.Trigger value="tab1">1</Tabs.Trigger>
    <Tabs.Trigger value="tab2">2</Tabs.Trigger>
  </Tabs.List>

  <Tabs.Content value="tab1">내용 1</Tabs.Content>
  <Tabs.Content value="tab2">내용 2</Tabs.Content>
</Tabs>

장점

  • 사용성이 직관적
  • UI 요소들을 마음대로 배치해도 내부적으로 잘 동작
  • Context를 통해 상태를 깔끔하게 공유

✔️ Higher-Order Component (HOC)

HOC는 컴포넌트를 인자로 받아 새로운 기능을 덧붙인 컴포넌트를 반환하는 함수다.

공식 정의

A higher-order component is a function that takes a component and returns a new component

언제 사용하는가?

  • 공통 로직을 여러 컴포넌트에서 재사용해야 할 때 (예: 인증 체크, 로깅, 공통 데이터 패칭, 권한 관리))

예시 코드 – 인증 여부 검사 HOC

function withAuth(Component) {
  return function ProtectedComponent(props) {
    const isLogin = Boolean(localStorage.getItem("token"));

    if (!isLogin) return <div>로그인이 필요합니다.</div>;

    return <Component {...props} />;
  };
}

사용법

function MyPage() {
  return <div>마이페이지!</div>;
}

export default withAuth(MyPage);

장점

  • 인증, 공통 로직을 깔끔하게 재사용 가능
  • 코드 중복 감소

단점

  • 컴포넌트 트리가 복잡해지고 디버깅이 어려움
  • React 공식 문서에서는 HOC보다 Custom Hook을 더 권장하는 추세

✔️ React Portal

Portal은 컴포넌트를 DOM 트리 외부로 렌더링하는 기능이다.

대표적인 사용 사례

  • 모달
  • 토스트 메시지
  • 드롭다운 메뉴
  • 오버레이 UI

즉, 시각적으로는 화면 맨 위(z-index 최고)에 떠 있어야 하지만 컴포넌트 구조는 기존 트리 하위에 있을 때 매우 유용하다.


예시 코드 – Modal 구현

import { createPortal } from "react-dom";

export default function Modal({ children }) {
  const modalRoot = document.getElementById("modal-root");

  return createPortal(
    <div className="overlay">
      <div className="modal">{children}</div>
    </div>,
    modalRoot
  );
}

index.html에 Portal 루트 추가

<div id="root"></div>
<div id="modal-root"></div>

사용법

function App() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>모달 열기</button>

      {open && (
        <Modal>
          <h2>모달 콘텐츠</h2>
        </Modal>
      )}
    </>
  );
}

Portal을 쓰는 이유?

  • 모달이 부모의 overflow: hidden이나 z-index 영향을 받지 않도록 하기 위함
  • DOM 계층을 시각적 계층과 분리할 수 있음
profile
매일매일 조금씩 성장하려 노력하는 프론트엔드 개발자입니다!

0개의 댓글