136 lines
4.5 KiB
Markdown
136 lines
4.5 KiB
Markdown
# 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)
|
|
|
|
## 예시
|
|
|
|
```typescript
|
|
// 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. **재사용 최적화**: 여러 페이지에서 동일한 방식으로 사용될 수 있도록 설계
|