Files
frontend/apps/_template/src/widgets/README.md
T

4.5 KiB

Widgets Layer

Widgets는 큰 규모의 독립적인 UI 블록을 위한 레이어입니다. 여러 features와 entities를 조합하여 완전한 기능을 제공하는 복합 컴포넌트를 담습니다.

목적

  • 큰 규모의 독립적인 UI 블록 구성
  • 여러 features와 entities를 하나의 응집력 있는 UI로 결합
  • 여러 페이지에서 재사용되는 복잡한 UI 제공
  • 페이지 레벨 레이아웃 및 섹션 관리

Widget으로 만들어야 하는 경우

Widget은 다음 조건에 해당할 때 사용하세요:

✅ Widget으로 만들기 좋은 경우:

  • 여러 페이지에서 재사용되는 큰 UI 블록
  • 페이지에 여러 개의 큰 독립적 블록이 있고, 이것이 그 중 하나인 경우
  • 페이지 레벨 레이아웃 컴포넌트 (nested routing 시스템 사용 시)

❌ Widget으로 만들지 말아야 하는 경우:

  • UI 블록이 페이지의 대부분을 차지하고 재사용되지 않는 경우 → 페이지 슬라이스 내부에 직접 배치
  • 작은 재사용 컴포넌트 → shared/ui에 배치

구조

각 widget 슬라이스는 다음 세그먼트로 구성됩니다:

widgets/
└── [widget-name]/
    ├── ui/            # 위젯의 메인 UI 컴포넌트
    ├── model/         # 위젯 내부 상태 관리 (선택적)
    ├── api/           # 위젯 전용 API 호출 (선택적)
    ├── lib/           # 위젯 내부 유틸리티 (선택적)
    └── index.ts       # Public API

세그먼트 설명

  • ui: 여러 features와 entities를 조합한 위젯 UI
  • model: 위젯 내부 상태 및 로직 (선택적)
  • api: 위젯이 필요로 하는 데이터 fetching (선택적)
  • lib: 위젯 내부에서만 사용되는 헬퍼 함수 (선택적)
  • index.ts: 슬라이스의 Public API (외부에서 사용할 수 있는 것만 export)

예시

// widgets/header/ui/header.tsx
import { Logo } from 'shared/ui/logo';
import { Navigation } from 'shared/ui/navigation';
import { UserMenu } from 'features/user-menu';
import { NotificationBell } from 'features/notifications';

export function Header() {
  return (
    <header>
      <Logo />
      <Navigation />
      <NotificationBell />
      <UserMenu />
    </header>
  );
}

// widgets/product-list/ui/product-list.tsx
import { useProducts } from '../api/use-products';
import { ProductCard } from 'entities/product';
import { AddToCartButton } from 'features/cart-add-item';

export function ProductList() {
  const { data: products, isLoading } = useProducts();

  if (isLoading) return <div>로딩 중...</div>;

  return (
    <div>
      {products.map((product) => (
        <div key={product.id}>
          <ProductCard product={product} />
          <AddToCartButton productId={product.id} />
        </div>
      ))}
    </div>
  );
}

// widgets/product-list/api/use-products.ts
import { useQuery } from '@tanstack/react-query';
import { getProducts } from 'entities/product';

export function useProducts() {
  return useQuery({
    queryKey: ['products'],
    queryFn: getProducts,
  });
}

// widgets/product-list/index.ts
export { ProductList } from './ui/product-list';

레이어 의존성 규칙

✅ 허용되는 의존성:

  • shared 레이어의 모듈
  • entities 레이어의 모듈
  • features 레이어의 모듈
  • 같은 widget 슬라이스 내의 다른 세그먼트

❌ 금지된 의존성:

  • 다른 widgets 슬라이스 (widget 간 직접 의존 금지)
  • pages 레이어
  • app 레이어

명명 규칙

  • 소문자와 대시 사용: header, sidebar, product-list
  • 명사 형태 권장: UI 블록의 성격을 나타내는 이름 사용
  • 구체적으로: 위젯의 역할이 명확하게 드러나도록

Nested Routing 사용 시

Remix 같은 nested routing 시스템을 사용한다면:

  • Widgets 레이어를 flat routing의 Pages 레이어처럼 사용 가능
  • 완전한 라우터 블록 생성 (데이터 fetching, loading states, error boundaries 포함)
  • 페이지 레이아웃을 이 레이어에 저장 가능

주의사항

  1. 독립성 유지: 각 위젯은 독립적으로 작동 가능해야 합니다
  2. 적절한 크기: 너무 작은 컴포넌트는 shared/ui에, 재사용되지 않으면 pages에 배치
  3. 응집력: 관련된 features와 entities를 의미 있게 조합
  4. 재사용 최적화: 여러 페이지에서 동일한 방식으로 사용될 수 있도록 설계