OPEN HYPER STEP
← 목록으로 (stack-analysis)
STACK-ANALYSIS · 9 / 90
stack-analysis
CHAPTER 9 / 90
읽기 약 2
FUNCTION

컴포넌트 패턴: Compound + Render Props


핵심 개념

Compound Component·Render Props·HOC·Hooks 비교 — 드롭다운·모달·탭.

본문

Compound Component

TYPESCRIPT📋 코드 (56줄)
// 부모 + 자식이 함께 동작하는 컴포넌트 묶음
import { createContext, useContext, useState } from 'react';

const TabsContext = createContext<{
  active: string;
  setActive: (v: string) => void;
} | null>(null);

function Tabs({ children, defaultValue }: { children: React.ReactNode; defaultValue: string }) {
  const [active, setActive] = useState(defaultValue);
  return (
    <TabsContext.Provider value={{ active, setActive }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }: { children: React.ReactNode }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ value, children }: { value: string; children: React.ReactNode }) {
  const ctx = useContext(TabsContext)!;
  return (
    <button
      className={ctx.active === value ? 'active' : ''}
      onClick={() => ctx.setActive(value)}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }: { value: string; children: React.ReactNode }) {
  const ctx = useContext(TabsContext)!;
  if (ctx.active !== value) return null;
  return <div className="tab-panel">{children}</div>;
}

// 묶기
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

export { Tabs };


// 사용 — 직관적·유연
<Tabs defaultValue="profile">
  <Tabs.List>
    <Tabs.Tab value="profile">프로필</Tabs.Tab>
    <Tabs.Tab value="settings">설정</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel value="profile">프로필 내용</Tabs.Panel>
  <Tabs.Panel value="settings">설정 내용</Tabs.Panel>
</Tabs>

Render Props (이제는 Hooks로 대체)

TYPESCRIPT📋 코드 (29줄)
// 옛날 패턴 — 함수를 자식으로
function MouseTracker({ children }: {
  children: (pos: { x: number; y: number }) => React.ReactNode
}) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  // ...
  return <div onMouseMove={...}>{children(pos)}</div>;
}

<MouseTracker>
  {({ x, y }) => <p>{x}, {y}</p>}
</MouseTracker>


// 모던 — Custom Hook
function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const handler = (e: MouseEvent) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', handler);
    return () => window.removeEventListener('mousemove', handler);
  }, []);
  return pos;
}

function App() {
  const { x, y } = useMousePosition();
  return <p>{x}, {y}</p>;
}

HOC (Higher-Order Component)

TYPESCRIPT📋 코드 (27줄)
// 옛날 패턴 — 컴포넌트를 감싸는 함수
function withAuth<P>(Component: React.ComponentType<P>) {
  return function AuthWrapped(props: P & { user?: User }) {
    const user = useAuthUser();
    if (!user) return <div>로그인 필요</div>;
    return <Component {...props} user={user} />;
  };
}

const ProtectedPage = withAuth(MyPage);


// 모던 — Custom Hook
function useRequireAuth() {
  const user = useAuthUser();
  const router = useRouter();
  useEffect(() => {
    if (!user) router.push('/login');
  }, [user]);
  return user;
}

function MyPage() {
  const user = useRequireAuth();
  if (!user) return null;
  return <div>Welcome, {user.name}</div>;
}

모달 — Compound Pattern

TYPESCRIPT📋 코드 (35줄)
function Modal({ isOpen, onClose, children }: {
  isOpen: boolean;
  onClose: () => void;
  children: React.ReactNode;
}) {
  if (!isOpen) return null;
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}

Modal.Header = ({ children }: { children: React.ReactNode }) => (
  <div className="modal-header">{children}</div>
);
Modal.Body = ({ children }: { children: React.ReactNode }) => (
  <div className="modal-body">{children}</div>
);
Modal.Footer = ({ children }: { children: React.ReactNode }) => (
  <div className="modal-footer">{children}</div>
);


// 사용
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
  <Modal.Header>알림</Modal.Header>
  <Modal.Body>정말 삭제하시겠습니까?</Modal.Body>
  <Modal.Footer>
    <button onClick={() => setIsOpen(false)}>취소</button>
    <button onClick={handleDelete}>확인</button>
  </Modal.Footer>
</Modal>

다음 챕터

CH.10 "스타일링 비교" — Tailwind vs CSS Modules vs styled.


AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude

무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6

내 코드의 컴포넌트 패턴 부분을 분석해서
실전 분석 + 개선 우선순위를 알려줘.
ChatGPT

무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro

컴포넌트 패턴 관련 인기 라이브러리/패턴 5개를
비교 분석해서 패턴 추출를 알려줘.
Gemini

무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro

내 프로젝트 전체에서 컴포넌트 패턴
최적화 가능 위치를 보고해줘.
Grok

무료: Grok 4.1 / SuperGrok $30/mo

2026년 한국 프론트엔드 시장의
컴포넌트 패턴 트렌드를 솔직히 알려줘.

⭐ 이것만 기억하세요
컴포넌트 패턴: Compound + Render Props 이 3가지만 확실히 잡으세요
1.Compound Component는 직관적 + 유연 — 부모/자식 묶음으로 동작
2.모던 React는 Render Props/HOC 대신 Custom Hook 우선
3.다음 챕터 CH.10에서 스타일링 — 3가지 접근법 비교


공유하기
진행도 9 / 90