효율적인 개발을 위해서는 검증된 패턴과 템플릿을 재사용하는 것이 중요합니다. 이 가이드에서는 프로젝트 타입별 스타터 규칙, 업종별 규칙 템플릿, 주요 코딩 스타일 가이드를 MDC로 구현하는 방법을 상세히 다룹니다.
1. 프로젝트 타입별 스타터 규칙
Create React App (CRA) 템플릿
---
description: Create React App project starter rules
version: "2.1.0"
projectType: "create-react-app"
framework: "react"
buildTool: "webpack"
tags: ["react", "cra", "frontend", "starter"]
globs: ["src/**/*.{ts,tsx,js,jsx}", "public/**/*", "package.json"]
alwaysApply: false
---
# Create React App 프로젝트 규칙
## ?? 프로젝트 구조 및 설정
### 1. 디렉터리 구조
src/
├── components/ # 재사용 가능한 컴포넌트
│ ├── ui/ # 기본 UI 컴포넌트
│ └── feature/ # 기능별 컴포넌트
├── hooks/ # 커스텀 훅
├── services/ # API 호출 로직
├── utils/ # 유틸리티 함수
├── types/ # TypeScript 타입 정의
├── styles/ # 전역 스타일
└── assets/ # 정적 자원
### 2. 컴포넌트 작성 규칙
// ? 권장: 함수형 컴포넌트 + TypeScript
import React, { useState, useCallback, useMemo } from 'react';
import './Button.css';
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
children: React.ReactNode;
className?: string;
'data-testid'?: string;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
onClick,
children,
className = '',
'data-testid': testId,
...restProps
}) => {
const handleClick = useCallback((event: React.MouseEvent<HTMLButtonNode>) => {
if (disabled || loading) return;
onClick?.(event);
}, [disabled, loading, onClick]);
const buttonClass = useMemo(() => {
return [
'btn',
`btn--${variant}`,
`btn--${size}`,
loading && 'btn--loading',
disabled && 'btn--disabled',
className
].filter(Boolean).join(' ');
}, [variant, size, loading, disabled, className]);
return (
<button
type="button"
className={buttonClass}
onClick={handleClick}
disabled={disabled || loading}
data-testid={testId}
{...restProps}
>
{loading && <span className="btn__spinner" />}
{children}
</button>
);
};
### 3. 상태 관리 패턴
// Context API를 활용한 전역 상태
import React, { createContext, useContext, useReducer } from 'react';
interface AppState {
user: User | null;
theme: 'light' | 'dark';
loading: boolean;
}
type AppAction =
| { type: 'SET_USER'; payload: User }
| { type: 'SET_THEME'; payload: 'light' | 'dark' }
| { type: 'SET_LOADING'; payload: boolean };
const AppContext = createContext<{
state: AppState;
dispatch: React.Dispatch<AppAction>;
} | null>(null);
export const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppContext must be used within AppProvider');
}
return context;
};
### 4. 테스트 가이드라인
// ? 권장: React Testing Library 사용
import { render, screen, userEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button Component', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
});
it('calls onClick when clicked', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await userEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('does not call onClick when disabled', async () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick} disabled>Click me</Button>);
await userEvent.click(screen.getByRole('button'));
expect(handleClick).not.toHaveBeenCalled();
});
});
## ?? 체크리스트
- [ ] TypeScript 설정 완료
- [ ] ESLint + Prettier 구성
- [ ] 테스트 환경 설정
- [ ] 컴포넌트 스토리북 구성
- [ ] CI/CD 파이프라인 설정
@cra-component-template.tsx
@cra-hook-template.ts
@cra-test-template.spec.tsx
Vite 프로젝트 템플릿
---
description: Vite project starter rules and optimizations
version: "1.8.0"
projectType: "vite"
framework: "react"
buildTool: "vite"
tags: ["vite", "react", "fast-build", "hmr"]
globs: ["src/**/*.{ts,tsx,js,jsx}", "vite.config.{ts,js}", "index.html"]
alwaysApply: false
---
# Vite 프로젝트 최적화 규칙
## ? Vite 특화 최적화
### 1. Vite 설정 최적화
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [
react({
// Fast Refresh 최적화
fastRefresh: true,
// JSX 런타임 자동 설정
jsxRuntime: 'automatic'
})
],
// 개발 서버 최적화
server: {
port: 3000,
open: true,
hmr: {
overlay: true
}
},
// 빌드 최적화
build: {
// 청크 분할 전략
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
ui: ['@mui/material', '@emotion/react']
}
}
},
// 소스맵 설정
sourcemap: true,
// 압축 설정
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
},
// 경로 별칭 설정
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@utils': resolve(__dirname, 'src/utils'),
'@types': resolve(__dirname, 'src/types')
}
},
// 환경 변수 설정
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
}
});
### 2. 동적 임포트 최적화
// ? 권장: 라우트 레벨 코드 스플리팅
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { LoadingSpinner } from '@components/ui/LoadingSpinner';
// 지연 로딩 컴포넌트
const Home = React.lazy(() => import('@/pages/Home'));
const About = React.lazy(() => import('@/pages/About'));
const Dashboard = React.lazy(() => import('@/pages/Dashboard'));
export const App: React.FC = () => {
return (
<Router>
<div className="app">
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</div>
</Router>
);
};
// 조건부 임포트
const ImportAnalytics = () => {
if (process.env.NODE_ENV === 'production') {
return import('@/utils/analytics');
}
return Promise.resolve(null);
};
### 3. 환경별 설정
// src/config/index.ts
interface Config {
apiUrl: string;
enableAnalytics: boolean;
logLevel: 'debug' | 'info' | 'warn' | 'error';
}
const config: Config = {
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3001',
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
logLevel: (import.meta.env.VITE_LOG_LEVEL as Config['logLevel']) || 'info'
};
export default config;
// 타입 안전한 환경 변수
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_ENABLE_ANALYTICS: string;
readonly VITE_LOG_LEVEL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
### 4. HMR 최적화
// ? HMR 상태 보존
import { useState, useEffect } from 'react';
export const useHMRState = <T>(key: string, initialValue: T): [T, (value: T) => void] => {
const [state, setState] = useState<T>(() => {
// HMR 중 상태 복원
if (import.meta.hot?.data && key in import.meta.hot.data) {
return import.meta.hot.data[key];
}
return initialValue;
});
useEffect(() => {
// HMR 중 상태 보존
if (import.meta.hot) {
import.meta.hot.data[key] = state;
}
}, [key, state]);
return [state, setState];
};
// HMR 바운더리
if (import.meta.hot) {
import.meta.hot.accept();
}
@vite-config-template.ts
@vite-component-template.tsx
@vite-env-template.d.ts
Next.js 프로젝트 템플릿
---
description: Next.js 13+ App Router project template
version: "3.2.0"
projectType: "nextjs"
framework: "nextjs"
router: "app-router"
tags: ["nextjs", "app-router", "ssr", "typescript"]
globs: ["app/**/*.{ts,tsx}", "pages/**/*.{ts,tsx}", "next.config.{js,ts}"]
alwaysApply: false
---
# Next.js App Router 프로젝트 템플릿
## ??? App Router 구조 및 패턴
### 1. 앱 디렉터리 구조
app/
├── layout.tsx # 루트 레이아웃
├── page.tsx # 홈 페이지
├── loading.tsx # 로딩 UI
├── error.tsx # 에러 UI
├── not-found.tsx # 404 페이지
├── globals.css # 전역 스타일
├── products/
│ ├── layout.tsx # 제품 섹션 레이아웃
│ ├── page.tsx # 제품 목록 페이지
│ ├── [id]/
│ │ ├── page.tsx # 제품 상세 페이지
│ │ └── loading.tsx # 상세 페이지 로딩
│ └── loading.tsx # 제품 목록 로딩
└── api/
└── products/
└── route.ts # API 라우트
### 2. 페이지 컴포넌트 패턴
// app/products/[id]/page.tsx
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { ProductDetail } from '@/components/ProductDetail';
import { getProduct } from '@/lib/api';
interface ProductPageProps {
params: { id: string };
searchParams: { [key: string]: string | string[] | undefined };
}
// 정적 메타데이터 생성
export async function generateMetadata({
params
}: ProductPageProps): Promise<Metadata> {
const product = await getProduct(params.id);
if (!product) {
return {
title: 'Product Not Found'
};
}
return {
title: product.name,
description: product.description,
openGraph: {
title: product.name,
description: product.description,
images: [
{
url: product.imageUrl,
width: 1200,
height: 630,
alt: product.name
}
]
},
twitter: {
card: 'summary_large_image',
title: product.name,
description: product.description,
images: [product.imageUrl]
}
};
}
// 정적 파라미터 생성 (선택사항)
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
id: product.id
}));
}
export default async function ProductPage({
params,
searchParams
}: ProductPageProps) {
// 서버 컴포넌트에서 데이터 페칭
const product = await getProduct(params.id);
if (!product) {
notFound();
}
return (
<div className="container mx-auto px-4 py-8">
<ProductDetail
product={product}
variant={searchParams.variant as string}
/>
</div>
);
}
### 3. 레이아웃 컴포넌트
// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { Providers } from '@/components/Providers';
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: {
template: '%s | My App',
default: 'My App'
},
description: 'A modern Next.js application',
keywords: ['Next.js', 'React', 'TypeScript'],
authors: [{ name: 'Your Name' }],
creator: 'Your Name',
publisher: 'Your Company',
formatDetection: {
email: false,
address: false,
telephone: false
}
};
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html lang="ko" suppressHydrationWarning>
<body className={inter.className}>
<Providers>
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
{children}
</main>
<Footer />
</div>
</Providers>
</body>
</html>
);
}
### 4. API 라우트 패턴
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { getProducts, createProduct } from '@/lib/db';
// 요청 검증 스키마
const CreateProductSchema = z.object({
name: z.string().min(1).max(100),
price: z.number().positive(),
description: z.string().max(1000)
});
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');
const products = await getProducts({ page, limit });
return NextResponse.json({
success: true,
data: products.items,
pagination: {
page,
limit,
total: products.total,
totalPages: Math.ceil(products.total / limit)
}
});
} catch (error) {
console.error('Products GET error:', error);
return NextResponse.json(
{ success: false, error: 'Failed to fetch products' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const validatedData = CreateProductSchema.parse(body);
const product = await createProduct(validatedData);
return NextResponse.json(
{ success: true, data: product },
{ status: 201 }
);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: 'Validation failed', details: error.errors },
{ status: 400 }
);
}
console.error('Products POST error:', error);
return NextResponse.json(
{ success: false, error: 'Failed to create product' },
{ status: 500 }
);
}
}
### 5. 데이터 페칭 패턴
// lib/api.ts
import { unstable_cache } from 'next/cache';
// 캐시된 데이터 페칭
export const getProduct = unstable_cache(
async (id: string) => {
const response = await fetch(`${process.env.API_URL}/products/${id}`, {
next: { revalidate: 3600 } // 1시간 캐시
});
if (!response.ok) {
throw new Error('Failed to fetch product');
}
return response.json();
},
['product'],
{
tags: ['products'],
revalidate: 3600
}
);
// 스트리밍 데이터
export async function getProductsStream() {
const response = await fetch(`${process.env.API_URL}/products`, {
next: { revalidate: 0 } // 실시간 데이터
});
return response.body;
}
@nextjs-page-template.tsx
@nextjs-layout-template.tsx
@nextjs-api-template.ts
@nextjs-config-template.js
2. 업종별 규칙 템플릿
핀테크 프로젝트 템플릿
---
description: Fintech project security and compliance rules
version: "2.3.0"
industry: "fintech"
compliance: ["PCI-DSS", "SOX", "GDPR"]
securityLevel: "high"
tags: ["fintech", "security", "compliance", "payments"]
globs: ["src/**/*.{ts,tsx,js,jsx}", "**/*payment*", "**/*transaction*", "**/*auth*"]
alwaysApply: true
---
# 핀테크 프로젝트 보안 및 규정 준수 규칙
## ?? 보안 필수 요구사항
### 1. 데이터 보호 및 암호화
// ? 민감한 데이터 처리 패턴
import CryptoJS from 'crypto-js';
interface SecureData {
encryptedValue: string;
iv: string;
tag: string;
}
class DataEncryption {
private static readonly SECRET_KEY = process.env.ENCRYPTION_KEY;
static encrypt(data: string): SecureData {
if (!this.SECRET_KEY) {
throw new Error('암호화 키가 설정되지 않았습니다');
}
const iv = CryptoJS.lib.WordArray.random(16);
const encrypted = CryptoJS.AES.encrypt(data, this.SECRET_KEY, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return {
encryptedValue: encrypted.toString(),
iv: iv.toString(),
tag: encrypted.key.toString()
};
}
static decrypt(secureData: SecureData): string {
if (!this.SECRET_KEY) {
throw new Error('복호화 키가 설정되지 않았습니다');
}
const decrypted = CryptoJS.AES.decrypt(
secureData.encryptedValue,
this.SECRET_KEY,
{
iv: CryptoJS.enc.Hex.parse(secureData.iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
}
// ? 금지: 민감한 데이터 로깅
// console.log('User SSN:', user.ssn); // 절대 금지!
// ? 권장: 마스킹된 데이터 로깅
const maskSensitiveData = (data: string): string => {
if (data.length <= 4) return '****';
return data.slice(0, 2) + '*'.repeat(data.length - 4) + data.slice(-2);
};
console.log('Card Number:', maskSensitiveData(cardNumber));
### 2. 결제 처리 보안
// 결제 트랜잭션 검증
interface PaymentTransaction {
id: string;
amount: number;
currency: string;
merchantId: string;
timestamp: Date;
signature: string;
}
class PaymentValidator {
static validateTransaction(transaction: PaymentTransaction): boolean {
// 금액 검증
if (transaction.amount <= 0 || transaction.amount > 1000000) {
throw new Error('유효하지 않은 거래 금액');
}
// 통화 검증
const validCurrencies = ['USD', 'EUR', 'KRW', 'JPY'];
if (!validCurrencies.includes(transaction.currency)) {
throw new Error('지원하지 않는 통화');
}
// 서명 검증
const expectedSignature = this.generateSignature(transaction);
if (transaction.signature !== expectedSignature) {
throw new Error('거래 서명이 유효하지 않음');
}
// 시간 검증 (5분 이내)
const currentTime = new Date();
const timeDiff = currentTime.getTime() - transaction.timestamp.getTime();
if (timeDiff > 5 * 60 * 1000) {
throw new Error('거래 시간이 만료됨');
}
return true;
}
private static generateSignature(transaction: Omit<PaymentTransaction, 'signature'>): string {
const payload = `${transaction.id}${transaction.amount}${transaction.currency}${transaction.merchantId}${transaction.timestamp.toISOString()}`;
return CryptoJS.HmacSHA256(payload, process.env.PAYMENT_SECRET!).toString();
}
}
### 3. 사용자 활동 감사 로그
// 감사 로그 시스템
interface AuditLog {
userId: string;
action: string;
resource: string;
timestamp: Date;
ipAddress: string;
userAgent: string;
result: 'success' | 'failure';
details?: Record<string, any>;
}
class AuditLogger {
static async logUserAction(
userId: string,
action: string,
resource: string,
request: Request,
result: 'success' | 'failure',
details?: Record<string, any>
): Promise<void> {
const auditLog: AuditLog = {
userId,
action,
resource,
timestamp: new Date(),
ipAddress: this.getClientIP(request),
userAgent: request.headers.get('user-agent') || 'unknown',
result,
details: this.sanitizeDetails(details)
};
// 보안 민감한 액션은 즉시 알림
const criticalActions = ['login_failed', 'payment_failed', 'account_locked'];
if (criticalActions.includes(action) && result === 'failure') {
await this.sendSecurityAlert(auditLog);
}
// 감사 로그 저장 (암호화된 저장소)
await this.saveAuditLog(auditLog);
}
private static sanitizeDetails(details?: Record<string, any>): Record<string, any> {
if (!details) return {};
const sensitiveFields = ['password', 'ssn', 'creditCard', 'bankAccount'];
const sanitized = { ...details };
sensitiveFields.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});
return sanitized;
}
}
### 4. 컴플라이언스 체크리스트
// 자동 컴플라이언스 검사
class ComplianceChecker {
static async runPCIDSSChecks(): Promise<ComplianceResult> {
const checks = [
this.checkDataEncryption,
this.checkAccessControls,
this.checkNetworkSecurity,
this.checkVulnerabilityManagement,
this.checkSecurityTesting,
this.checkSecurityPolicies
];
const results: ComplianceCheckResult[] = [];
for (const check of checks) {
try {
const result = await check();
results.push(result);
} catch (error) {
results.push({
requirement: check.name,
status: 'failed',
error: error.message
});
}
}
return {
standard: 'PCI-DSS',
timestamp: new Date(),
results,
overallStatus: results.every(r => r.status === 'passed') ? 'compliant' : 'non-compliant'
};
}
private static async checkDataEncryption(): Promise<ComplianceCheckResult> {
// 데이터 암호화 검증 로직
const encryptionStatus = await this.verifyEncryptionImplementation();
return {
requirement: 'Data Encryption (PCI-DSS 3.4)',
status: encryptionStatus.isCompliant ? 'passed' : 'failed',
details: encryptionStatus.details
};
}
}
## ?? 핀테크 보안 체크리스트
- [ ] 모든 민감한 데이터 암호화
- [ ] 접근 제어 및 권한 관리 구현
- [ ] 감사 로그 시스템 구축
- [ ] 결제 트랜잭션 검증 로직
- [ ] 보안 취약점 스캔 자동화
- [ ] 컴플라이언스 자동 검사
- [ ] 보안 사고 대응 계획 수립
@fintech-security-utils.ts
@payment-validation.ts
@audit-logger.ts
@compliance-checker.ts
이커머스 프로젝트 템플릿
---
description: E-commerce project patterns and best practices
version: "2.0.0"
industry: "ecommerce"
features: ["cart", "checkout", "inventory", "orders"]
tags: ["ecommerce", "shopping", "inventory", "payments"]
globs: ["src/**/*.{ts,tsx}", "**/*product*", "**/*cart*", "**/*order*"]
alwaysApply: false
---
# 이커머스 프로젝트 개발 표준
## ?? 핵심 기능 구현 패턴
### 1. 제품 관리 시스템
// 제품 데이터 모델
interface Product {
id: string;
name: string;
description: string;
price: number;
compareAtPrice?: number;
sku: string;
inventory: {
quantity: number;
trackQuantity: boolean;
allowBackorder: boolean;
};
images: ProductImage[];
variants?: ProductVariant[];
categories: string[];
tags: string[];
seo: {
title?: string;
description?: string;
slug: string;
};
status: 'active' | 'draft' | 'archived';
createdAt: Date;
updatedAt: Date;
}
// 제품 서비스
class ProductService {
static async getProduct(id: string): Promise<Product | null> {
try {
const product = await this.fetchFromAPI(`/products/${id}`);
return this.transformProduct(product);
} catch (error) {
console.error('Failed to fetch product:', error);
return null;
}
}
static async searchProducts(params: ProductSearchParams): Promise<ProductSearchResult> {
const query = this.buildSearchQuery(params);
const response = await this.fetchFromAPI('/products/search', { method: 'POST', body: query });
return {
products: response.products.map(this.transformProduct),
pagination: response.pagination,
filters: response.filters,
facets: response.facets
};
}
static calculateDiscount(product: Product): DiscountInfo {
if (!product.compareAtPrice || product.compareAtPrice <= product.price) {
return { hasDiscount: false, percentage: 0, amount: 0 };
}
const amount = product.compareAtPrice - product.price;
const percentage = Math.round((amount / product.compareAtPrice) * 100);
return {
hasDiscount: true,
percentage,
amount,
savings: amount
};
}
}
### 2. 장바구니 상태 관리
// 장바구니 상태 관리 (Zustand 사용)
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartItem {
productId: string;
variantId?: string;
quantity: number;
price: number;
product: Pick<Product, 'id' | 'name' | 'images' | 'sku'>;
}
interface CartState {
items: CartItem[];
isLoading: boolean;
error: string | null;
// Actions
addItem: (item: Omit<CartItem, 'quantity'>, quantity?: number) => void;
updateQuantity: (productId: string, variantId: string | undefined, quantity: number) => void;
removeItem: (productId: string, variantId?: string) => void;
clearCart: () => void;
// Computed values
getTotalItems: () => number;
getTotalPrice: () => number;
getItemCount: (productId: string, variantId?: string) => number;
}
export const useCartStore = create<CartState>()(
persist(
(set, get) => ({
items: [],
isLoading: false,
error: null,
addItem: (newItem, quantity = 1) => {
set(state => {
const existingIndex = state.items.findIndex(
item => item.productId === newItem.productId && item.variantId === newItem.variantId
);
if (existingIndex >= 0) {
// 기존 아이템 수량 업데이트
const updatedItems = [...state.items];
updatedItems[existingIndex].quantity += quantity;
return { items: updatedItems };
} else {
// 새 아이템 추가
return {
items: [...state.items, { ...newItem, quantity }]
};
}
});
},
updateQuantity: (productId, variantId, quantity) => {
set(state => ({
items: state.items.map(item =>
item.productId === productId && item.variantId === variantId
? { ...item, quantity: Math.max(0, quantity) }
: item
).filter(item => item.quantity > 0)
}));
},
removeItem: (productId, variantId) => {
set(state => ({
items: state.items.filter(
item => !(item.productId === productId && item.variantId === variantId)
)
}));
},
clearCart: () => set({ items: [] }),
getTotalItems: () => {
return get().items.reduce((total, item) => total + item.quantity, 0);
},
getTotalPrice: () => {
return get().items.reduce((total, item) => total + (item.price * item.quantity), 0);
},
getItemCount: (productId, variantId) => {
const item = get().items.find(
item => item.productId === productId && item.variantId === variantId
);
return item?.quantity || 0;
}
}),
{
name: 'shopping-cart',
partialize: (state) => ({ items: state.items })
}
)
);
### 3. 주문 처리 워크플로우
// 주문 처리 상태 머신
type OrderStatus =
| 'pending'
| 'confirmed'
| 'processing'
| 'shipped'
| 'delivered'
| 'cancelled'
| 'refunded';
interface Order {
id: string;
customerId: string;
items: OrderItem[];
shippingAddress: Address;
billingAddress: Address;
paymentMethod: PaymentMethod;
status: OrderStatus;
totals: OrderTotals;
timestamps: {
createdAt: Date;
confirmedAt?: Date;
shippedAt?: Date;
deliveredAt?: Date;
};
tracking?: TrackingInfo;
}
class OrderService {
static async createOrder(orderData: CreateOrderRequest): Promise<Order> {
// 1. 재고 확인
await this.validateInventory(orderData.items);
// 2. 가격 검증
await this.validatePricing(orderData.items);
// 3. 결제 처리
const paymentResult = await PaymentService.processPayment({
amount: orderData.total,
paymentMethod: orderData.paymentMethod,
customerId: orderData.customerId
});
if (!paymentResult.success) {
throw new Error('결제 처리 실패');
}
// 4. 주문 생성
const order = await this.saveOrder({
...orderData,
status: 'confirmed',
paymentId: paymentResult.transactionId
});
// 5. 재고 차감
await InventoryService.reserveItems(orderData.items);
// 6. 주문 확인 이메일 발송
await EmailService.sendOrderConfirmation(order);
// 7. 이벤트 발행
await EventBus.publish('order.created', { orderId: order.id });
return order;
}
static async updateOrderStatus(orderId: string, newStatus: OrderStatus): Promise<void> {
const order = await this.getOrder(orderId);
if (!order) {
throw new Error('주문을 찾을 수 없습니다');
}
// 상태 전환 검증
this.validateStatusTransition(order.status, newStatus);
// 상태별 추가 처리
switch (newStatus) {
case 'shipped':
await this.handleShipped(order);
break;
case 'delivered':
await this.handleDelivered(order);
break;
case 'cancelled':
await this.handleCancelled(order);
break;
}
await this.updateOrder(orderId, { status: newStatus });
}
private static validateStatusTransition(current: OrderStatus, next: OrderStatus): void {
const validTransitions: Record<OrderStatus, OrderStatus[]> = {
pending: ['confirmed', 'cancelled'],
confirmed: ['processing', 'cancelled'],
processing: ['shipped', 'cancelled'],
shipped: ['delivered'],
delivered: ['refunded'],
cancelled: [],
refunded: []
};
if (!validTransitions[current].includes(next)) {
throw new Error(`Invalid status transition: ${current} -> ${next}`);
}
}
}
### 4. 재고 관리 시스템
// 실시간 재고 관리
class InventoryService {
private static readonly stockCache = new Map<string, number>();
static async checkAvailability(productId: string, variantId?: string, quantity = 1): Promise<boolean> {
const currentStock = await this.getCurrentStock(productId, variantId);
return currentStock >= quantity;
}
static async reserveItems(items: OrderItem[]): Promise<void> {
const reservations: InventoryReservation[] = [];
try {
// 모든 아이템에 대해 재고 예약 시도
for (const item of items) {
const reservation = await this.createReservation({
productId: item.productId,
variantId: item.variantId,
quantity: item.quantity,
expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10분 후 만료
});
reservations.push(reservation);
}
} catch (error) {
// 실패 시 모든 예약 롤백
await this.rollbackReservations(reservations);
throw error;
}
}
static async updateStock(productId: string, variantId: string | undefined, delta: number): Promise<void> {
const key = `${productId}:${variantId || 'default'}`;
// 낙관적 잠금을 사용한 재고 업데이트
let retries = 3;
while (retries > 0) {
try {
const currentStock = await this.getCurrentStock(productId, variantId);
const newStock = currentStock + delta;
if (newStock < 0) {
throw new Error('재고가 부족합니다');
}
await this.setStock(productId, variantId, newStock, currentStock);
// 캐시 업데이트
this.stockCache.set(key, newStock);
// 재고 변경 이벤트 발행
await EventBus.publish('inventory.updated', {
productId,
variantId,
oldStock: currentStock,
newStock,
delta
});
break;
} catch (error) {
if (error.message.includes('version mismatch') && retries > 1) {
retries--;
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms 대기
} else {
throw error;
}
}
}
}
// 재고 부족 알림
static async checkLowStockAlerts(): Promise<void> {
const lowStockItems = await this.getLowStockItems();
for (const item of lowStockItems) {
await NotificationService.sendLowStockAlert({
productId: item.productId,
variantId: item.variantId,
currentStock: item.stock,
threshold: item.lowStockThreshold,
supplier: item.supplier
});
}
}
}
@ecommerce-product-service.ts
@cart-store.ts
@order-service.ts
@inventory-service.ts
게임 개발 프로젝트 템플릿
---
description: Game development patterns and optimization rules
version: "1.5.0"
industry: "gaming"
platform: ["web", "mobile", "desktop"]
gameType: ["casual", "rpg", "strategy", "action"]
tags: ["game-dev", "unity", "performance", "optimization"]
globs: ["src/**/*.{ts,js,cs}", "Assets/**/*.cs", "**/*game*", "**/*player*"]
alwaysApply: false
---
# 게임 개발 최적화 및 패턴 가이드
## ?? 게임 개발 핵심 패턴
### 1. 게임 오브젝트 풀링
// 오브젝트 풀 패턴으로 성능 최적화
class ObjectPool<T> {
private pool: T[] = [];
private createFn: () => T;
private resetFn: (obj: T) => void;
private maxSize: number;
constructor(
createFunction: () => T,
resetFunction: (obj: T) => void,
initialSize = 10,
maxSize = 100
) {
this.createFn = createFunction;
this.resetFn = resetFunction;
this.maxSize = maxSize;
// 초기 오브젝트 생성
for (let i = 0; i < initialSize; i++) {
this.pool.push(this.createFn());
}
}
get(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return this.createFn();
}
release(obj: T): void {
if (this.pool.length < this.maxSize) {
this.resetFn(obj);
this.pool.push(obj);
}
}
clear(): void {
this.pool.length = 0;
}
}
// 총알 오브젝트 풀 예시
interface Bullet {
x: number;
y: number;
vx: number;
vy: number;
active: boolean;
sprite: PIXI.Sprite;
}
const bulletPool = new ObjectPool<Bullet>(
() => ({
x: 0,
y: 0,
vx: 0,
vy: 0,
active: false,
sprite: new PIXI.Sprite(bulletTexture)
}),
(bullet) => {
bullet.active = false;
bullet.sprite.visible = false;
},
50, // 초기 50개
200 // 최대 200개
);
### 2. 게임 상태 관리
// 상태 머신 패턴
abstract class GameState {
abstract enter(): void;
abstract update(deltaTime: number): void;
abstract exit(): void;
abstract handleInput(input: InputEvent): void;
}
class GameStateMachine {
private currentState: GameState | null = null;
private states: Map<string, GameState> = new Map();
addState(name: string, state: GameState): void {
this.states.set(name, state);
}
changeState(stateName: string): void {
const newState = this.states.get(stateName);
if (!newState) {
throw new Error(`State ${stateName} not found`);
}
if (this.currentState) {
this.currentState.exit();
}
this.currentState = newState;
this.currentState.enter();
}
update(deltaTime: number): void {
if (this.currentState) {
this.currentState.update(deltaTime);
}
}
handleInput(input: InputEvent): void {
if (this.currentState) {
this.currentState.handleInput(input);
}
}
}
// 게임 상태 구현 예시
class MenuState extends GameState {
enter(): void {
console.log('메뉴 상태 진입');
// UI 활성화, 배경음악 재생 등
}
update(deltaTime: number): void {
// 메뉴 애니메이션 업데이트
}
exit(): void {
console.log('메뉴 상태 종료');
// UI 비활성화
}
handleInput(input: InputEvent): void {
if (input.type === 'click' && input.target === 'startButton') {
gameStateMachine.changeState('playing');
}
}
}
class PlayingState extends GameState {
enter(): void {
console.log('게임 플레이 시작');
// 게임 로직 초기화
}
update(deltaTime: number): void {
// 게임 로직 업데이트
this.updateEntities(deltaTime);
this.checkCollisions();
this.updatePhysics(deltaTime);
}
exit(): void {
console.log('게임 플레이 종료');
// 점수 저장, 리소스 정리
}
handleInput(input: InputEvent): void {
// 플레이어 입력 처리
}
private updateEntities(deltaTime: number): void {
// 엔티티 업데이트 로직
}
private checkCollisions(): void {
// 충돌 검사 로직
}
private updatePhysics(deltaTime: number): void {
// 물리 시뮬레이션
}
}
### 3. 엔티티 컴포넌트 시스템 (ECS)
// ECS 아키텍처 구현
interface Component {
readonly type: string;
}
interface Transform extends Component {
type: 'transform';
x: number;
y: number;
rotation: number;
scaleX: number;
scaleY: number;
}
interface Velocity extends Component {
type: 'velocity';
vx: number;
vy: number;
}
interface Health extends Component {
type: 'health';
current: number;
maximum: number;
}
class Entity {
private static nextId = 0;
public readonly id: number;
private components: Map<string, Component> = new Map();
constructor() {
this.id = Entity.nextId++;
}
addComponent<T extends Component>(component: T): void {
this.components.set(component.type, component);
}
getComponent<T extends Component>(type: string): T | undefined {
return this.components.get(type) as T;
}
hasComponent(type: string): boolean {
return this.components.has(type);
}
removeComponent(type: string): void {
this.components.delete(type);
}
}
abstract class System {
protected entities: Set<Entity> = new Set();
abstract update(deltaTime: number): void;
addEntity(entity: Entity): void {
if (this.matchesComponents(entity)) {
this.entities.add(entity);
}
}
removeEntity(entity: Entity): void {
this.entities.delete(entity);
}
protected abstract matchesComponents(entity: Entity): boolean;
}
// 움직임 시스템
class MovementSystem extends System {
protected matchesComponents(entity: Entity): boolean {
return entity.hasComponent('transform') && entity.hasComponent('velocity');
}
update(deltaTime: number): void {
for (const entity of this.entities) {
const transform = entity.getComponent<Transform>('transform')!;
const velocity = entity.getComponent<Velocity>('velocity')!;
transform.x += velocity.vx * deltaTime;
transform.y += velocity.vy * deltaTime;
}
}
}
// 렌더링 시스템
class RenderSystem extends System {
private renderer: PIXI.Renderer;
constructor(renderer: PIXI.Renderer) {
super();
this.renderer = renderer;
}
protected matchesComponents(entity: Entity): boolean {
return entity.hasComponent('transform') && entity.hasComponent('sprite');
}
update(deltaTime: number): void {
for (const entity of this.entities) {
const transform = entity.getComponent<Transform>('transform')!;
const sprite = entity.getComponent<any>('sprite')!;
sprite.position.set(transform.x, transform.y);
sprite.rotation = transform.rotation;
sprite.scale.set(transform.scaleX, transform.scaleY);
}
}
}
### 4. 성능 최적화 기법
// 공간 분할을 통한 충돌 검사 최적화
class QuadTree {
private bounds: Rectangle;
private maxObjects: number;
private maxDepth: number;
private depth: number;
private objects: GameObject[] = [];
private nodes: QuadTree[] = [];
constructor(bounds: Rectangle, maxObjects = 10, maxDepth = 5, depth = 0) {
this.bounds = bounds;
this.maxObjects = maxObjects;
this.maxDepth = maxDepth;
this.depth = depth;
}
clear(): void {
this.objects.length = 0;
for (const node of this.nodes) {
node.clear();
}
this.nodes.length = 0;
}
split(): void {
const halfWidth = this.bounds.width / 2;
const halfHeight = this.bounds.height / 2;
const x = this.bounds.x;
const y = this.bounds.y;
this.nodes[0] = new QuadTree(
new Rectangle(x + halfWidth, y, halfWidth, halfHeight),
this.maxObjects, this.maxDepth, this.depth + 1
);
this.nodes[1] = new QuadTree(
new Rectangle(x, y, halfWidth, halfHeight),
this.maxObjects, this.maxDepth, this.depth + 1
);
this.nodes[2] = new QuadTree(
new Rectangle(x, y + halfHeight, halfWidth, halfHeight),
this.maxObjects, this.maxDepth, this.depth + 1
);
this.nodes[3] = new QuadTree(
new Rectangle(x + halfWidth, y + halfHeight, halfWidth, halfHeight),
this.maxObjects, this.maxDepth, this.depth + 1
);
}
insert(object: GameObject): void {
if (this.nodes.length > 0) {
const index = this.getIndex(object);
if (index !== -1) {
this.nodes[index].insert(object);
return;
}
}
this.objects.push(object);
if (this.objects.length > this.maxObjects && this.depth < this.maxDepth) {
if (this.nodes.length === 0) {
this.split();
}
let i = 0;
while (i < this.objects.length) {
const index = this.getIndex(this.objects[i]);
if (index !== -1) {
this.nodes[index].insert(this.objects.splice(i, 1)[0]);
} else {
i++;
}
}
}
}
retrieve(object: GameObject): GameObject[] {
const returnObjects: GameObject[] = [];
const index = this.getIndex(object);
if (index !== -1 && this.nodes.length > 0) {
returnObjects.push(...this.nodes[index].retrieve(object));
}
returnObjects.push(...this.objects);
return returnObjects;
}
private getIndex(object: GameObject): number {
let index = -1;
const verticalMidpoint = this.bounds.x + this.bounds.width / 2;
const horizontalMidpoint = this.bounds.y + this.bounds.height / 2;
const topQuadrant = object.y < horizontalMidpoint && object.y + object.height < horizontalMidpoint;
const bottomQuadrant = object.y > horizontalMidpoint;
if (object.x < verticalMidpoint && object.x + object.width < verticalMidpoint) {
if (topQuadrant) {
index = 1;
} else if (bottomQuadrant) {
index = 2;
}
} else if (object.x > verticalMidpoint) {
if (topQuadrant) {
index = 0;
} else if (bottomQuadrant) {
index = 3;
}
}
return index;
}
}
// 프레임 레이트 독립적 게임 루프
class GameLoop {
private lastTime = 0;
private accumulator = 0;
private readonly fixedDeltaTime = 1000 / 60; // 60 FPS
private readonly maxFrameTime = 250; // 최대 프레임 시간 (4 FPS)
start(): void {
this.lastTime = performance.now();
this.loop();
}
private loop = (): void => {
const currentTime = performance.now();
let frameTime = currentTime - this.lastTime;
this.lastTime = currentTime;
// 너무 긴 프레임 시간 제한
frameTime = Math.min(frameTime, this.maxFrameTime);
this.accumulator += frameTime;
// 고정 시간 간격으로 물리 업데이트
while (this.accumulator >= this.fixedDeltaTime) {
this.updatePhysics(this.fixedDeltaTime);
this.accumulator -= this.fixedDeltaTime;
}
// 가변 시간 간격으로 렌더링
const alpha = this.accumulator / this.fixedDeltaTime;
this.render(alpha);
requestAnimationFrame(this.loop);
};
private updatePhysics(deltaTime: number): void {
// 물리 시뮬레이션, 게임 로직 업데이트
}
private render(alpha: number): void {
// 보간을 사용한 부드러운 렌더링
}
}
@game-object-pool.ts
@game-state-machine.ts
@ecs-system.ts
@quadtree-collision.ts
3. 코딩 스타일 가이드 MDC 구현
Google 스타일 가이드 템플릿
---
description: Google TypeScript Style Guide implementation
version: "1.3.0"
styleGuide: "google"
language: "typescript"
framework: "any"
tags: ["google-style", "typescript", "formatting", "conventions"]
globs: ["**/*.{ts,tsx}"]
alwaysApply: true
---
# Google TypeScript 스타일 가이드
## ?? 포맷팅 및 네이밍 규칙
### 1. 네이밍 컨벤션
// ? 올바른 네이밍
class UserService { // PascalCase for classes
private readonly apiEndpoint: string; // camelCase for properties
async getUserById(userId: string): Promise<User> { // camelCase for methods
const API_VERSION = 'v1'; // SCREAMING_SNAKE_CASE for constants
return this.fetchUser(userId);
}
}
interface UserPreferences { // PascalCase for interfaces
theme: 'light' | 'dark';
language: string;
}
type DatabaseConfig = { // PascalCase for types
host: string;
port: number;
};
enum HttpStatus { // PascalCase for enums
OK = 200,
NOT_FOUND = 404,
INTERNAL_SERVER_ERROR = 500
}
// ? 잘못된 네이밍
class userservice { } // 클래스명은 PascalCase
const User_Name = 'john'; // 변수명은 camelCase
function Get_User() { } // 함수명은 camelCase
### 2. 타입 정의 규칙
// ? 명시적 타입 정의
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
// 제네릭 타입 매개변수는 의미있는 이름 사용
interface Repository<TEntity, TKey> {
findById(id: TKey): Promise<TEntity | null>;
save(entity: TEntity): Promise<TEntity>;
delete(id: TKey): Promise<boolean>;
}
// 유니온 타입은 명확하게 정의
type Theme = 'light' | 'dark' | 'auto';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
// 복잡한 타입은 단계적으로 구성
type BaseUser = {
id: string;
email: string;
createdAt: Date;
};
type UserProfile = BaseUser & {
firstName: string;
lastName: string;
avatar?: string;
};
type AdminUser = UserProfile & {
permissions: string[];
lastLoginAt?: Date;
};
### 3. 함수 정의 규칙
// ? 함수 시그니처는 명확하게
async function processUserData(
users: User[],
options: {
includeInactive?: boolean;
sortBy?: 'name' | 'createdAt';
limit?: number;
} = {}
): Promise<ProcessedUser[]> {
// 구현
}
// 순수 함수 우선 사용
function calculateTax(amount: number, rate: number): number {
if (amount < 0 || rate < 0) {
throw new Error('Amount and rate must be non-negative');
}
return amount * rate;
}
// 고차 함수는 타입 안전하게
function createValidator<T>(
schema: (value: unknown) => value is T
): (input: unknown) => T {
return (input: unknown): T => {
if (!schema(input)) {
throw new Error('Validation failed');
}
return input;
};
}
### 4. 클래스 구조 규칙
// ? 클래스 멤버 순서 및 접근 제한자
class UserManager {
// 1. 정적 멤버 (public -> private)
public static readonly DEFAULT_ROLE = 'user';
private static instance: UserManager;
// 2. 인스턴스 필드 (readonly -> public -> private)
private readonly logger: Logger;
public readonly config: UserConfig;
private users: Map<string, User> = new Map();
// 3. 생성자
constructor(config: UserConfig, logger: Logger) {
this.config = config;
this.logger = logger;
}
// 4. 정적 메서드
public static getInstance(config: UserConfig, logger: Logger): UserManager {
if (!UserManager.instance) {
UserManager.instance = new UserManager(config, logger);
}
return UserManager.instance;
}
// 5. 공개 메서드
public async createUser(userData: CreateUserRequest): Promise<User> {
this.validateUserData(userData);
const user = await this.buildUser(userData);
this.users.set(user.id, user);
this.logger.info(`User created: ${user.id}`);
return user;
}
public getUserById(id: string): User | null {
return this.users.get(id) || null;
}
// 6. 비공개 메서드
private validateUserData(userData: CreateUserRequest): void {
if (!userData.email || !this.isValidEmail(userData.email)) {
throw new Error('Valid email is required');
}
if (!userData.password || userData.password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
}
private async buildUser(userData: CreateUserRequest): Promise<User> {
return {
id: this.generateUserId(),
email: userData.email,
role: userData.role || UserManager.DEFAULT_ROLE,
createdAt: new Date(),
isActive: true
};
}
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
private generateUserId(): string {
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
### 5. 에러 처리 패턴
// ? 커스텀 에러 클래스
abstract class AppError extends Error {
abstract readonly statusCode: number;
abstract readonly isOperational: boolean;
constructor(message: string, public readonly context?: Record<string, unknown>) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
readonly statusCode = 400;
readonly isOperational = true;
constructor(message: string, public readonly field: string) {
super(message, { field });
}
}
class NotFoundError extends AppError {
readonly statusCode = 404;
readonly isOperational = true;
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`, { resource, id });
}
}
// Result 패턴 사용
type Result<T, E = Error> = {
success: true;
data: T;
} | {
success: false;
error: E;
};
async function safeApiCall<T>(apiCall: () => Promise<T>): Promise<Result<T>> {
try {
const data = await apiCall();
return { success: true, data };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error : new Error('Unknown error')
};
}
}
### 6. 비동기 처리 규칙
// ? Promise 체이닝보다 async/await 선호
async function processUserRegistration(userData: UserRegistration): Promise<User> {
try {
// 순차 처리
const validatedData = await validateUserData(userData);
const hashedPassword = await hashPassword(validatedData.password);
const user = await createUserInDatabase({ ...validatedData, password: hashedPassword });
// 병렬 처리
const [emailResult, profileResult] = await Promise.all([
sendWelcomeEmail(user.email),
createUserProfile(user.id)
]);
if (!emailResult.success) {
logger.warn('Failed to send welcome email', { userId: user.id });
}
return user;
} catch (error) {
logger.error('User registration failed', { userData: userData.email, error });
throw error;
}
}
// Promise.allSettled 사용으로 부분 실패 처리
async function notifyMultipleUsers(userIds: string[], message: string): Promise<NotificationResult[]> {
const notificationPromises = userIds.map(userId =>
sendNotification(userId, message).catch(error => ({ userId, error }))
);
const results = await Promise.allSettled(notificationPromises);
return results.map((result, index) => ({
userId: userIds[index],
success: result.status === 'fulfilled',
error: result.status === 'rejected' ? result.reason : undefined
}));
}
@google-style-template.ts
@typescript-patterns.ts
@error-handling-patterns.ts
Airbnb 스타일 가이드 템플릿
---
description: Airbnb JavaScript/TypeScript Style Guide implementation
version: "2.1.0"
styleGuide: "airbnb"
language: "typescript"
framework: "react"
tags: ["airbnb-style", "react", "javascript", "typescript"]
globs: ["**/*.{ts,tsx,js,jsx}"]
alwaysApply: true
---
# Airbnb JavaScript/TypeScript 스타일 가이드
## ?? Airbnb 규칙 구현
### 1. 변수 선언 및 할당
// ? const를 기본으로, 재할당이 필요한 경우만 let 사용
const users = ['alice', 'bob', 'charlie'];
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
let currentUser = null; // 재할당이 필요한 경우
if (isAuthenticated) {
currentUser = getCurrentUser();
}
// ? 구조 분해 할당 적극 활용
const { name, email, age } = user;
const [first, second, ...rest] = items;
// 함수 매개변수도 구조 분해
function createUser({ name, email, age = 18 }: CreateUserParams) {
return { id: generateId(), name, email, age };
}
// ? 템플릿 리터럴 사용
const message = `Hello, ${user.name}! You have ${user.notifications.length} new notifications.`;
// ? 피해야 할 패턴
var userName = 'john'; // var 사용 금지
const greeting = 'Hello, ' + user.name + '!'; // 문자열 연결 대신 템플릿 리터럴
### 2. 함수 정의 패턴
// ? 화살표 함수 우선 사용 (단, 메서드는 제외)
const calculateTotal = (items: CartItem[]): number => {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
};
// 단일 표현식인 경우 간결하게
const isEven = (num: number): boolean => num % 2 === 0;
const getFullName = (user: User): string => `${user.firstName} ${user.lastName}`;
// ? 고차 함수 활용
const createApiClient = (baseURL: string) => {
return {
get: <T>(endpoint: string): Promise<T> => fetch(`${baseURL}${endpoint}`).then(res => res.json()),
post: <T>(endpoint: string, data: unknown): Promise<T> =>
fetch(`${baseURL}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(res => res.json())
};
};
// ? 순수 함수 선호
const addTax = (price: number, taxRate: number): number => price * (1 + taxRate);
const formatCurrency = (amount: number, currency = 'USD'): string =>
new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);
### 3. 객체 및 배열 조작
// ? 스프레드 연산자 활용
const originalUser = { name: 'John', age: 30 };
const updatedUser = { ...originalUser, age: 31, city: 'New York' };
const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5, 6];
// ? 객체 메서드 단축 구문
const userService = {
users: [] as User[],
// 메서드 단축 구문
addUser(user: User) {
this.users.push(user);
},
getUserById(id: string) {
return this.users.find(user => user.id === id);
},
// 계산된 속성명
[Symbol.iterator]() {
return this.users[Symbol.iterator]();
}
};
// ? 배열 메서드 체이닝
const processUsers = (users: User[]): ProcessedUser[] => {
return users
.filter(user => user.isActive)
.map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`,
displayName: user.displayName || user.firstName
}))
.sort((a, b) => a.fullName.localeCompare(b.fullName));
};
### 4. React 컴포넌트 패턴
// ? 함수형 컴포넌트 + TypeScript
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
children: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
loading = false,
children,
className = '',
disabled,
...restProps
}) => {
// ? early return 패턴
if (loading) {
return (
<button className={`btn btn--${variant} btn--loading`} disabled>
<Spinner size="sm" />
Loading...
</button>
);
}
// ? 조건부 클래스명 처리
const buttonClass = [
'btn',
`btn--${variant}`,
`btn--${size}`,
className,
].filter(Boolean).join(' ');
return (
<button
className={buttonClass}
disabled={disabled || loading}
{...restProps}
>
{children}
</button>
);
};
// ? 커스텀 훅 패턴
const useLocalStorage = <T>(key: string, initialValue: T) => {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.warn(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = useCallback((value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
const removeValue = useCallback(() => {
try {
window.localStorage.removeItem(key);
setStoredValue(initialValue);
} catch (error) {
console.warn(`Error removing localStorage key "${key}":`, error);
}
}, [key, initialValue]);
return [storedValue, setValue, removeValue] as const;
};
### 5. 모듈 시스템 및 Import
// ? Named import 우선 사용
import { useState, useEffect, useCallback } from 'react';
import { debounce, throttle } from 'lodash';
import { ApiClient } from '@/services/api';
// ? 타입 import 분리
import type { User, UserPreferences } from '@/types/user';
import type { ComponentProps } from 'react';
// ? 절대 경로 사용 (상대경로 최소화)
import { Button } from '@/components/ui/Button';
import { validateEmail } from '@/utils/validation';
import { UserService } from '@/services/UserService';
// ? Export 패턴
// Named export 우선
export const API_ENDPOINTS = {
USERS: '/api/users',
POSTS: '/api/posts'
} as const;
export class UserRepository {
// 구현
}
export const createUser = (userData: CreateUserData): Promise<User> => {
// 구현
};
// Default export는 컴포넌트나 주요 클래스에만 사용
export default UserRepository;
### 6. 조건문 및 반복문
// ? 삼항 연산자 적절히 사용
const statusColor = user.isActive ? 'green' : 'red';
const displayName = user.nickname || user.firstName || 'Anonymous';
// ? 단락 평가 활용
const handleClick = () => {
isLoading && showSpinner();
hasPermission && executeAction();
};
// ? Array 메서드 우선 사용
const activeUsers = users.filter(user => user.isActive);
const userNames = users.map(user => user.name);
const hasAdminUser = users.some(user => user.role === 'admin');
// ? 조건문 정리
const canEditPost = (user: User, post: Post): boolean => {
return user.id === post.authorId ||
user.role === 'admin' ||
user.permissions.includes('edit_all_posts');
};
// 복잡한 조건은 함수로 추출
if (canEditPost(currentUser, selectedPost)) {
enableEditMode();
}
@airbnb-style-template.ts
@react-patterns-airbnb.tsx
@functional-programming-patterns.ts
4. 템플릿 사용 가이드
템플릿 선택 가이드
---
description: Template selection and customization guide
version: "1.0.0"
category: "meta"
---
# MDC 템플릿 선택 및 커스터마이징 가이드
## ?? 프로젝트별 템플릿 선택
### 새 프로젝트 시작 시
# 프로젝트 타입 확인 체크리스트
echo "프로젝트 타입을 확인하세요:"
echo "1. 프레임워크: React/Vue/Angular/Vanilla"
echo "2. 빌드 도구: Vite/Webpack/Parcel/Rollup"
echo "3. 언어: TypeScript/JavaScript"
echo "4. 스타일: Tailwind/CSS Modules/Styled Components"
echo "5. 상태 관리: Redux/Zustand/Context API"
# 템플릿 다운로드 스크립트
#!/bin/bash
PROJECT_TYPE=$1
FRAMEWORK=$2
case "$PROJECT_TYPE" in
"web-app")
if [ "$FRAMEWORK" = "react" ]; then
echo "React 웹 앱 템플릿 적용 중..."
cp templates/react-webapp/* .cursor/rules/
elif [ "$FRAMEWORK" = "vue" ]; then
echo "Vue 웹 앱 템플릿 적용 중..."
cp templates/vue-webapp/* .cursor/rules/
fi
;;
"api")
echo "API 서버 템플릿 적용 중..."
cp templates/api-server/* .cursor/rules/
;;
"fullstack")
echo "풀스택 템플릿 적용 중..."
cp templates/fullstack/* .cursor/rules/
;;
esac
### 업종별 특화 규칙 적용
// 업종별 규칙 매트릭스
const industryRuleMatrix = {
fintech: {
required: ['security-baseline', 'data-encryption', 'audit-logging'],
recommended: ['pci-compliance', 'fraud-detection', 'payment-validation'],
optional: ['kyc-verification', 'aml-checks']
},
ecommerce: {
required: ['product-management', 'cart-handling', 'order-processing'],
recommended: ['inventory-tracking', 'payment-integration', 'seo-optimization'],
optional: ['recommendation-engine', 'analytics-tracking']
},
healthcare: {
required: ['hipaa-compliance', 'data-privacy', 'audit-trails'],
recommended: ['patient-data-security', 'medical-validation'],
optional: ['telemedicine-integration', 'hl7-standards']
},
gaming: {
required: ['performance-optimization', 'object-pooling', 'game-loop'],
recommended: ['asset-management', 'physics-integration', 'multiplayer-sync'],
optional: ['analytics-events', 'monetization-tracking']
}
};
// 자동 규칙 추천 시스템
function recommendRules(projectType: string, industry: string): RuleRecommendation {
const baseRules = getBaseRules(projectType);
const industryRules = industryRuleMatrix[industry] || {};
return {
essential: [...baseRules.core, ...industryRules.required],
recommended: [...baseRules.recommended, ...industryRules.recommended],
optional: [...baseRules.optional, ...industryRules.optional],
conflicts: detectRuleConflicts([...baseRules.core, ...industryRules.required])
};
}
### 템플릿 커스터마이징
## 템플릿 수정 가이드
### 1. 기본 템플릿 복사
# 기본 템플릿을 프로젝트에 복사
cp -r templates/react-typescript .cursor/rules/base/
# 프로젝트별 수정사항 적용
echo "프로젝트명: MyApp" >> .cursor/rules/base/project-info.mdc
echo "팀명: Frontend Team" >> .cursor/rules/base/project-info.mdc
### 2. 규칙 우선순위 조정
# .cursor/rules/priority-config.yml
ruleOverrides:
- ruleId: "typescript-strict"
priority: 10
reason: "타입 안전성이 최우선"
- ruleId: "performance-optimization"
priority: 8
reason: "사용자 경험 중요"
- ruleId: "code-formatting"
priority: 5
reason: "팀 협업을 위한 기본"
### 3. 팀별 커스터마이징
// 팀별 규칙 설정 생성기
interface TeamConfig {
teamName: string;
preferences: {
codeStyle: 'google' | 'airbnb' | 'standard';
framework: string;
testingLibrary: string;
stateManagement: string;
};
restrictions: string[];
customRules: string[];
}
function generateTeamRules(config: TeamConfig): void {
const teamRulesDir = `.cursor/rules/teams/${config.teamName}`;
// 팀별 디렉터리 생성
ensureDirectory(teamRulesDir);
// 기본 규칙 적용
applyBaseRules(teamRulesDir, config.preferences);
// 제한사항 적용
applyRestrictions(teamRulesDir, config.restrictions);
// 커스텀 규칙 추가
addCustomRules(teamRulesDir, config.customRules);
console.log(`${config.teamName} 팀 규칙이 생성되었습니다.`);
}
5. 템플릿 배포 및 공유
템플릿 저장소 구축
---
description: Template repository structure and distribution
version: "1.2.0"
category: "distribution"
---
# MDC 템플릿 저장소 관리
## ?? 저장소 구조
mdc-templates/
├── README.md
├── CONTRIBUTING.md
├── templates/
│ ├── project-types/
│ │ ├── react-app/
│ │ ├── vue-app/
│ │ ├── nextjs-app/
│ │ ├── node-api/
│ │ └── fullstack/
│ ├── industries/
│ │ ├── fintech/
│ │ ├── ecommerce/
│ │ ├── healthcare/
│ │ └── gaming/
│ ├── style-guides/
│ │ ├── google/
│ │ ├── airbnb/
│ │ └── standard/
│ └── frameworks/
│ ├── react/
│ ├── vue/
│ ├── angular/
│ └── svelte/
├── scripts/
│ ├── install-template.sh
│ ├── update-template.sh
│ └── validate-template.sh
└── docs/
├── getting-started.md
├── customization.md
└── api-reference.md
### 템플릿 설치 스크립트
#!/bin/bash
# scripts/install-template.sh
set -e
TEMPLATE_NAME=""
PROJECT_DIR="."
TEMPLATE_REPO="https://github.com/your-org/mdc-templates"
# 옵션 파싱
while [[ $# -gt 0 ]]; do
case $1 in
-t|--template)
TEMPLATE_NAME="$2"
shift 2
;;
-d|--directory)
PROJECT_DIR="$2"
shift 2
;;
-h|--help)
echo "사용법: $0 -t TEMPLATE_NAME [-d PROJECT_DIR]"
echo "예시: $0 -t react-typescript -d ./my-project"
exit 0
;;
*)
echo "알 수 없는 옵션: $1"
exit 1
;;
esac
done
if [ -z "$TEMPLATE_NAME" ]; then
echo "템플릿 이름을 지정해주세요. -h 옵션으로 도움말을 확인하세요."
exit 1
fi
echo "?? MDC 템플릿 설치 시작..."
echo "템플릿: $TEMPLATE_NAME"
echo "디렉터리: $PROJECT_DIR"
# 임시 디렉터리에 템플릿 저장소 클론
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
git clone --depth 1 "$TEMPLATE_REPO" "$TEMP_DIR"
# 템플릿 존재 확인
TEMPLATE_PATH="$TEMP_DIR/templates/$TEMPLATE_NAME"
if [ ! -d "$TEMPLATE_PATH" ]; then
echo "? 템플릿 '$TEMPLATE_NAME'을 찾을 수 없습니다."
echo "사용 가능한 템플릿:"
ls "$TEMP_DIR/templates/" | sed 's/^/ - /'
exit 1
fi
# .cursor/rules 디렉터리 생성
mkdir -p "$PROJECT_DIR/.cursor/rules"
# 템플릿 파일 복사
echo "?? 템플릿 파일 복사 중..."
cp -r "$TEMPLATE_PATH"/* "$PROJECT_DIR/.cursor/rules/"
# 템플릿 변수 치환
echo "?? 템플릿 변수 치환 중..."
if [ -f "$PROJECT_DIR/package.json" ]; then
PROJECT_NAME=$(node -p "require('$PROJECT_DIR/package.json').name" 2>/dev/null || echo "my-project")
else
PROJECT_NAME=$(basename "$PROJECT_DIR")
fi
find "$PROJECT_DIR/.cursor/rules" -name "*.mdc" -type f -exec sed -i.bak \
-e "s/{{PROJECT_NAME}}/$PROJECT_NAME/g" \
-e "s/{{TEMPLATE_NAME}}/$TEMPLATE_NAME/g" \
-e "s/{{INSTALL_DATE}}/$(date -u +%Y-%m-%dT%H:%M:%SZ)/g" \
{} \;
# 백업 파일 정리
find "$PROJECT_DIR/.cursor/rules" -name "*.bak" -delete
echo "? 템플릿 설치 완료!"
echo "?? 다음 파일들이 생성되었습니다:"
find "$PROJECT_DIR/.cursor/rules" -name "*.mdc" | sed 's/^/ - /'
echo ""
echo "?? 다음 단계:"
echo "1. .cursor/rules/ 디렉터리의 템플릿을 프로젝트에 맞게 수정하세요"
echo "2. Cursor에서 프로젝트를 열어 규칙이 적용되는지 확인하세요"
echo "3. 팀원들과 규칙을 공유하고 피드백을 받으세요"
### 템플릿 업데이트 시스템
// 템플릿 버전 관리 시스템
interface TemplateMetadata {
name: string;
version: string;
description: string;
author: string;
tags: string[];
dependencies: string[];
compatibleWith: string[];
lastUpdated: Date;
downloadCount: number;
}
class TemplateManager {
private templates: Map<string, TemplateMetadata> = new Map();
async installTemplate(templateName: string, targetDir: string): Promise<void> {
const template = await this.fetchTemplate(templateName);
// 의존성 확인
await this.checkDependencies(template.dependencies);
// 호환성 확인
this.validateCompatibility(template.compatibleWith);
// 템플릿 설치
await this.extractTemplate(template, targetDir);
// 설치 후 검증
await this.validateInstallation(templateName, targetDir);
console.log(`? 템플릿 '${templateName}' 설치 완료`);
}
async updateTemplate(templateName: string): Promise<void> {
const currentVersion = await this.getCurrentVersion(templateName);
const latestVersion = await this.getLatestVersion(templateName);
if (currentVersion === latestVersion) {
console.log(`템플릿 '${templateName}'은 이미 최신 버전입니다.`);
return;
}
console.log(`템플릿 업데이트: ${currentVersion} → ${latestVersion}`);
// 백업 생성
await this.createBackup(templateName);
try {
await this.installTemplate(templateName, './');
console.log(`? 템플릿 '${templateName}' 업데이트 완료`);
} catch (error) {
console.error('템플릿 업데이트 실패, 백업에서 복원 중...');
await this.restoreFromBackup(templateName);
throw error;
}
}
async listAvailableTemplates(): Promise<TemplateMetadata[]> {
const response = await fetch('https://api.mdc-templates.com/templates');
return response.json();
}
async searchTemplates(query: string, filters: {
framework?: string;
industry?: string;
styleGuide?: string;
} = {}): Promise<TemplateMetadata[]> {
const allTemplates = await this.listAvailableTemplates();
return allTemplates.filter(template => {
const matchesQuery = template.name.includes(query) ||
template.description.includes(query) ||
template.tags.some(tag => tag.includes(query));
const matchesFramework = !filters.framework ||
template.compatibleWith.includes(filters.framework);
const matchesIndustry = !filters.industry ||
template.tags.includes(filters.industry);
const matchesStyleGuide = !filters.styleGuide ||
template.tags.includes(filters.styleGuide);
return matchesQuery && matchesFramework && matchesIndustry && matchesStyleGuide;
});
}
}
// CLI 인터페이스
const templateManager = new TemplateManager();
// 사용 예시
// mdc install react-typescript
// mdc update react-typescript
// mdc search "fintech" --framework=react
// mdc list --industry=ecommerce
@template-installer.sh
@template-manager.ts
@template-metadata.json
이 종합적인 MDC 템플릿 라이브러리 가이드를 통해 다양한 프로젝트 타입, 업종, 코딩 스타일에 맞는 검증된 규칙 템플릿을 효율적으로 활용할 수 있습니다. 템플릿을 기반으로 프로젝트를 시작하면 개발 초기 설정 시간을 크게 단축하고 일관된 코드 품질을 유지할 수 있습니다.