[React] 제네릭을 활용한 재사용 가능한 데이터 카드 컴포넌트 설계

2026. 5. 8. 18:37·Dev Notes/Frontend

1. 들어가며

프론트엔드 개발을 진행하다 보면 디자인 형태는 완전히 동일하지만, 주입되는 데이터의 구조가 달라 매번 새로운 컴포넌트를 생성해야 하는 상황을 마주하게 됩니다.

특히 금융 도메인이나 대시보드 형태의 서비스를 개발할 때 이러한 문제가 자주 발생합니다. 주식, 현금, 부동산 등 각 자산군마다 백엔드에서 내려주는 API 응답 형태가 다르기 때문입니다. 이 글에서는 이러한 상황에서 TypeScript의 제네릭을 활용하여 컴포넌트를 유연하게 추상화하고 중복 코드를 제거한 과정을 공유합니다.


2. 문제 상황: 늘어나는 보일러플레이트와 중복 코드

초기에는 직관적인 구현을 위해 데이터 타입별로 컴포넌트를 분리했습니다. 백엔드에서 전달받는 주식과 현금 데이터의 타입은 다음과 같이 서로 다른 필드명을 가지고 있었습니다.

interface StockInfo {
  stockName: string;
  currentPrice: number;
}

interface CashInfo {
  accountName: string;
  balance: number;
}

이러한 타입 차이 때문에 각 데이터에 맞는 컴포넌트를 일일이 만들어야 했습니다.

// ❌ Bad: 데이터 타입마다 컴포넌트를 따로 생성하는 방식
function StockCard({ stockData }: { stockData: StockInfo }) {
  return (
    <div className="card">
      <h3>{stockData.stockName}</h3>
      <p>{stockData.currentPrice}</p>
    </div>
  );
}

function CashCard({ cashData }: { cashData: CashInfo }) {
  return (
    <div className="card">
      <h3>{cashData.accountName}</h3>
      <p>{cashData.balance}</p>
    </div>
  );
}

이 방식은 치명적인 단점이 존재했습니다.

  • UI 디자인(예: .card의 여백이나 색상)이 변경될 경우 모든 Card 컴포넌트를 찾아가 수정해야 합니다.
  • 새로운 자산 타입이 추가될 때마다 거의 동일한 형태의 컴포넌트를 새로 작성해야 합니다.

3. 해결 방안 모색: 제어 역전(IoC)과 뷰(View) 계층의 분리

문제의 원인은 UI를 렌더링하는 로직과 데이터의 구조에 의존하는 로직이 강하게 결합되어 있다는 점이었습니다.

이를 해결하기 위해 컴포넌트는 '어떤 형태의 데이터'가 들어오는지 몰라도 되도록, 데이터 추출 로직을 외부로 위임하는 방식을 채택했습니다. 즉, 컴포넌트가 직접 데이터를 해석하는 대신 외부에서 로직을 주입받는 방식인 제어 역전 개념을 도입했습니다. 이를 통해 컴포넌트는 오직 화면을 그리는 역할에만 집중하도록 설계했습니다.


4. 제네릭을 활용한 컴포넌트 추상화 적용

TypeScript의 제네릭을 사용하여, 앞서 언급한 제어 역전 패턴을 적용했습니다. 어떤 데이터 타입(T)이 들어오든 대응할 수 있는 DataCard 컴포넌트를 구현하고, 객체에서 필요한 값을 뽑아내는 함수를 Props로 주입받도록 설계했습니다.

import React from 'react';

// 공통으로 사용될 Card 컴포넌트의 Props 정의
interface DataCardProps<T> {
  data: T;
  extractTitle: (item: T) => string;
  extractAmount: (item: T) => number;
}

export function DataCard<T>({ data, extractTitle, extractAmount }: DataCardProps<T>) {
  return (
    <div className="rounded-xl border p-6 shadow-sm">
      <h3 className="text-lg font-semibold text-gray-800">
        {extractTitle(data)}
      </h3>
      <p className="mt-4 text-2xl font-bold text-gray-900">
        {extractAmount(data).toLocaleString()}원
      </p>
    </div>
  );
}

이제 이 컴포넌트를 사용할 때는 아래와 같이 사용할 데이터와 그 데이터에 맞는 추출 로직만 전달하면 됩니다.

// ✅ Good: 하나의 컴포넌트로 다양한 데이터 타입 대응
function Dashboard() {
  const stock: StockInfo = { stockName: '애플', currentPrice: 150000 };
  const cash: CashInfo = { accountName: '월급통장', balance: 3000000 };

  return (
    <>
      <DataCard
        data={stock}
        extractTitle={(s) => s.stockName}
        extractAmount={(s) => s.currentPrice}
      />
      <DataCard
        data={cash}
        extractTitle={(c) => c.accountName}
        extractAmount={(c) => c.balance}
      />
    </>
  );
}

5. 마무리 및 회고

제네릭과 제어 역전을 활용하여 컴포넌트를 설계한 결과, UI 코드의 중복을 획기적으로 줄일 수 있었습니다.

디자인 시스템이 변경되더라도 DataCard 컴포넌트 하나만 수정하면 전체 서비스에 반영되므로 유지보수성이 크게 향상되었습니다. 단순히 동작하는 코드를 넘어, 변경에 유연하게 대처할 수 있는 아키텍처를 고민하는 것의 중요성을 깨달을 수 있었습니다.


6. 참고 자료

  • TypeScript Handbook - Generics: 다양한 타입에 재사용 가능한 컴포넌트를 생성하는 제네릭의 기본 개념과 활용법.
    https://www.typescriptlang.org/docs/handbook/2/generics.html
  • React 공식 문서 - Passing Props to a Component: 리액트 컴포넌트 간 데이터를 안전하고 효율적으로 전달하는 방법.
    https://react.dev/learn/passing-props-to-a-component
  • Kent C. Dodds - Inversion of Control: 프론트엔드 컴포넌트 설계 시 제어권을 외부로 위임하여 재사용성을 극대화하는 IoC 패턴에 대한 심도 있는 고찰.
    https://kentcdodds.com/blog/inversion-of-control

'Dev Notes > Frontend' 카테고리의 다른 글

[MSW] REST 스냅샷과 WebSocket 실시간 데이터를 함께 쓸 때 생기는 경쟁 조건  (0) 2026.06.10
[MSW] MSW v2가 WebSocket을 막을 때 — SockJS 폴백으로 우회한 방법  (0) 2026.05.15
'Dev Notes/Frontend' 카테고리의 다른 글
  • [MSW] REST 스냅샷과 WebSocket 실시간 데이터를 함께 쓸 때 생기는 경쟁 조건
  • [MSW] MSW v2가 WebSocket을 막을 때 — SockJS 폴백으로 우회한 방법
lalunru
lalunru
머릿속에 흩어진 생각들을 글로 정리하며 성장합니다.
  • lalunru
    lalunru.blog
    lalunru
  • 전체
    오늘
    어제
    • 분류 전체보기 (11)
      • Dev Notes (5)
        • Frontend (3)
        • Unity (2)
      • Projects (6)
        • Unity (3)
        • Frontend (1)
        • Machine learning (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
    • Portfolio
  • 공지사항

  • 인기 글

  • 태그

    공포게임
    프론트엔드
    tailwindcss
    react
    악성URL
    XR Interaction Toolkit
    STOMP
    셰이더토이
    피처엔지니어링
    프래그먼트셰이더
    Unity
    다중엔딩
    zustand
    glsl
    포트폴리오
    shap
    NPR
    ToonShading
    컴포넌트설계
    Meta Quest
    Meta Quest 2
    c#
    navmesh
    셰이더
    websocket
    MSW
    TypeScript
    VR
    Shadertoy
    트러블슈팅
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
lalunru
[React] 제네릭을 활용한 재사용 가능한 데이터 카드 컴포넌트 설계
상단으로

티스토리툴바