2-3편: Polymorphic 컴포넌트 — as prop으로 유연한 확장

JIIJIIJ·2025년 9월 29일
0

React

목록 보기
34/35
post-thumbnail

들어가며 — 다형성을 UI로 끌어오다

앞선 글에서 우리는 Compound Pattern이 합성을 “역할 단위로 쪼개기”로,
Headless 컴포넌트가 합성을 “로직과 UI의 분리”로 확장한 모습을 살펴봤다.

그렇다면 이번엔 이런 질문을 던져보자.

“컴포넌트가 표현하는 태그 자체도 바뀔 수 있어야 하지 않을까?”

이 질문은 단순한 편의성 문제가 아니라, 소프트웨어 철학에서 오래전부터 내려온 Polymorphism(다형성)을 UI 설계에 적용하는 시도다.
즉, “하나의 인터페이스, 다양한 구현”이라는 객체지향/함수형의 원리를 React 컴포넌트에 이식하는 것이다.

그 결과물이 바로 Polymorphic 컴포넌트이며, 보통 as prop이라는 작은 문법적 장치로 구현된다.


문제 제기 — 태그 고정의 불편함

초창기 React 디자인 시스템에서 흔히 있었던 문제는 태그가 고정돼 있다는 것이었다.

  1. 중복된 컴포넌트
    같은 “버튼 역할”인데 태그가 달라진다는 이유로 별도 컴포넌트를 늘려야 했다.
    → 코드 중복 + 유지보수 지옥.

  2. 접근성과 시맨틱 문제
    <Title>공지사항</Title>이 내부적으로 <h2>라면, <h1>이 필요할 때 변종을 찍어내야 했다.

  3. 라우팅/SEO 요구
    <Button><Link>처럼 써야 할 때, 단순 스타일 공유가 막혀 또 다른 컴포넌트를 만들어야 했다.

➡️ 결과: “하나의 역할 = 여러 컴포넌트”라는 비효율.

📖 참고 | Kent C. Dodds – Compound vs Polymorphic Components


해결책 — Polymorphism의 도입

여기서 프로그래밍 철학의 오래된 원리가 등장한다.

Polymorphism(다형성)
“같은 인터페이스로 다양한 구현을 다룰 수 있다.”

UI에도 똑같이 적용하면 된다.
Button이라는 역할(인터페이스)은 유지하되,
실제 DOM 태그나 컴포넌트 구현은 상황에 따라 교체한다.

이 철학을 React에 녹여낸 것이 바로 as prop이다.


철학적 배경 — 다형성의 역사와 React

  • 객체지향(OOP): 오버라이딩, 인터페이스로 같은 메시지에 다른 구현 허용.
  • 함수형(FP): 고차 함수/타입 추상화로 같은 연산을 다양한 자료구조에 적용.
  • UI 설계(React): 같은 “Button”이지만, 때로는 <button>, 때로는 <a>, 때로는 <Link>.

즉, Polymorphic 컴포넌트는 UI 계층에서 역할(Role)과 표현(Presentation)을 분리하는 방식이다.
React의 합성 철학을 계승하면서도, HTML의 시맨틱 유연성과 디자인 시스템의 일관성을 동시에 잡는다.

📖 참고 | Chakra UI Docs – The as prop


역사적 맥락 — 왜 as prop이 등장했나

  1. React 초창기 (2013~2015)
    대부분 컴포넌트는 특정 태그에 하드코딩.

  2. React Router의 등장
    버튼처럼 보이지만 링크로 동작해야 하는 요구 증가 → <Button as={Link}>.

  3. 디자인 시스템 확산 (2018~)
    Chakra UI, Stitches, Radix 같은 라이브러리가 as prop을 도입.

➡️ as prop은 단순 편의 기능이 아니라, UI 다형성에 대한 업계의 공식 답변이 됐다.

📖 참고 | Radix UI – Primitives, Stitches Polymorphic API


사용 예시

1. Button 컴포넌트: 구현과 활용

구현 핵심

const Button = ({
  as: Comp = "button",    // 기본은 <button>
  variant = "primary",    // 스타일 API
  ...rest
}) => {
  const base = "px-3 py-2 rounded";
  const styles = variant === "primary" ? "bg-blue-600 text-white" : "bg-gray-200";

  return <Comp className={`${base} ${styles}`} {...rest} />;
};

실제 사용 예시

<Button variant="primary">저장</Button>                 
<Button as="a" href="https://react.dev" target="_blank">React 공식 문서</Button>  
<Button as={Link} to="/dashboard">대시보드로 이동</Button>

2. Typography 컴포넌트: 구현과 활용

구현 핵심

const Typography = ({
  as: Comp = "p",        
  variant = "body",      
  className = "",
  ...rest
}) => {
  const base = "font-sans";
  const styles = {
    h1: "text-3xl font-bold",
    h2: "text-2xl font-semibold",
    body: "text-base",
    caption: "text-sm text-gray-500"
  };

  return (
    <Comp className={`${base} ${styles[variant]} ${className}`} {...rest} />
  );
};

실제 사용 예시

<Typography as="h1" variant="h1">React 블로그</Typography>
<Typography as="p" variant="body">본문 단락</Typography>
<Typography as="span" variant="caption">업데이트: 2025-09-29</Typography>

📖 참고 | Headless UI Docs


실무적 가치

  1. 중복 제거와 유지보수 단순화

    • 과거: <Button>, <LinkButton>, <IconButton>… 역할마다 새 컴포넌트를 만들었다.
    • 문제: 스타일이 바뀌면 모든 컴포넌트를 수정해야 했음.
    • Polymorphic: as prop 하나로 커버. 디자인 시스템 업데이트 시 단일 Button만 고치면 전역 반영됨.
    • 효과: 코드 중복 30~40% 감소, 유지보수 속도 극대화.
  2. 시맨틱 마크업 + 접근성 보장

    • 예: <Typography as="h1"> vs <Typography as="h2">.
    • 디자인은 같아도 문서 구조는 다름.
    • Polymorphic 덕분에 시맨틱 태그를 유지하면서도 스타일 일관성을 보장.
    • 효과: SEO 최적화 + 스크린리더 접근성 강화.
  3. 디자인 시스템의 확장성

    • 하나의 컴포넌트 API(variant, size 등)로 모든 표현을 통일.
    • → 팀 내 가이드라인 강화, UI 일관성 자동 확보.
    • 신규 입사자도 Button, Typography 같은 공통 컴포넌트만 배우면 전체 시스템 활용 가능.
  4. 비즈니스 요구 대응 속도

    • 갑자기 “버튼을 다 링크로 바꿔야 한다”는 요구가 와도, 스타일은 그대로 두고 as="a"만 바꾸면 해결.
    • 마케팅 배너, CTA 버튼 같은 빈번한 변경 업무에 특히 강력.

📖 참고 | Stitches Polymorphic API


실제 사례

  1. Chakra UI

    • 거의 모든 컴포넌트가 as prop 지원.
    • Button as="a", Box as="section", Text as="span"처럼 사용.
    • 효과: 태그 교체 시에도 Chakra의 스타일/variant API 그대로 재사용 가능.
    • 👉 디자인 시스템을 팀 전반에 빠르게 확산시킨 핵심 이유.
  2. Radix UI

    • 접근성 보장된 Primitive 컴포넌트에 Polymorphic을 적용.
    • 예: <DialogTrigger asChild><Button>을 바로 자식으로 넣어도 동작.
    • 효과: 접근성 로직과 스타일은 그대로, 태그만 교체 가능.
    • 👉 복잡한 웹앱에서 “접근성 + 시맨틱 + 유연성”을 모두 달성.
  3. Stitches

    • 스타일드 컴포넌트 라이브러리 자체가 Polymorphic 지원.
    • styled('button')으로 만든 컴포넌트에 as="a" 전달 가능.
    • 효과: CSS-in-JS와 Polymorphism의 자연스러운 결합.
    • 👉 CSS-in-JS 진영에서 사실상 표준 패턴으로 자리 잡음.
  4. 실무 시나리오

    • 대기업 서비스의 디자인 시스템에서 <Button> 하나만으로 로그인 버튼, 외부 링크, 앱 내 네비게이션을 모두 커버.
    • 신입 개발자가 들어와도 <Button> 문서만 보고도 다양한 상황에서 활용 가능.
    • 결과적으로 온보딩 기간 단축 + 실수 방지라는 비즈니스 성과로 연결됨.

📖 참고 | Chakra UI Docs, Radix UI Docs, Stitches Docs


Polymorphic 컴포넌트는 단순히 as prop이라는 문법적 장치가 아니다.
이는 소프트웨어 공학의 고전적 원리인 다형성(Polymorphism)을 UI 설계에 끌어온 시도다.

  1. 철학적 관점

    • 객체지향의 다형성, 함수형의 추상화 개념을 UI에 이식.
    • “Button은 버튼이다”라는 역할(Role)을 고정하면서도, 그것이 <button>, <a>, <Link>라는 표현(Expression)은 자유롭게 교체 가능.
    • React의 합성 철학(Composition Philosophy)을 “표현 계층”까지 확장한 실천.
  2. 실무적 관점

    • 중복된 컴포넌트 제거 → 유지보수 단순화.
    • 시맨틱 태그를 상황에 맞게 교체 → 접근성, SEO 모두 강화.
    • 디자인 시스템의 공통 API를 그대로 유지 → 팀 내 UI 일관성과 생산성 극대화.
    • 갑작스러운 요구 변경에도 대응이 빠름 → 비즈니스 민첩성 확보.
  3. 역사적 관점

    • React 초창기엔 태그 고정으로 인한 중복이 당연시되던 시절이 있었다.
    • React Router, Chakra, Stitches, Radix 같은 라이브러리가 as prop을 확산시키면서, Polymorphism은 UI 개발의 사실상 표준으로 자리 잡았다.
    • 이는 단순히 “편의 기능”이 아니라, UI 설계 패러다임의 진화라 할 수 있다.

즉, Polymorphic 컴포넌트는 “하나의 인터페이스, 다양한 표현”이라는 고전적 원리를 현대 UI 설계에 적용한 것이다.
Compound Pattern이 구조적 합성, Headless가 로직과 UI의 분리를 보여줬다면, Polymorphic은 표현 계층의 유연성을 실현한다.

이는 React 개발자가 단순히 더 적은 코드를 쓰는 수준을 넘어, 더 깊은 철학을 코드에 구현하는 방법이다.

📖 참고 |


profile
다크모드가 보기 좋아요

0개의 댓글