| name | generate-component |
| description | React uygulamalarında kurumsal standartlara uygun component oluşturma işlemini yaparken bu yetekenek devreye girer. |
| applyTo | src/**/*.tsx |
generate-component Skill
React 18 + TypeScript + Vite projesinde kurumsal standartlara uygun component üretme kurallarını tanımlar.
Bu skill tetiklendiğinde aşağıdaki tüm kurallar zorunlu olarak uygulanır.
1. GENEL PRENSİPLER
| Kural | Açıklama |
|---|
| Functional Only | Class component kesinlikle yasak. Yalnızca function component. |
| TypeScript Strict | tsconfig.json'da "strict": true aktif. Tüm tipler explicit tanımlanmalı. |
| Named Export | Her component export ile dışa aktarılır; export default yasak. |
| Single Responsibility | Her component tek bir iş yapar. Karmaşık logic custom hook'a taşınır. |
| Immutability | State doğrudan mutate edilmez; her zaman yeni referans üretilir. |
| No Magic Strings | Sabit string/number değerler constants dosyasına alınır. |
2. DOSYA & KLASÖR YAPISI
Her component kendi klasörüne sahip olur:
src/
└── components/
└── ProductCard/
├── ProductCard.tsx ← Component implementasyonu
├── ProductCard.types.ts ← Interface / Type tanımları
├── ProductCard.module.css ← Scoped stiller (isteğe bağlı)
├── useProductCard.ts ← Component'e özgü custom hook (isteğe bağlı)
└── index.ts ← Barrel export
Barrel Export (index.ts)
export { ProductCard } from './ProductCard';
export type { ProductCardProps } from './ProductCard.types';
3. COMPONENT ŞABLONU
3.1 Temel Şablon
import type { FC } from 'react';
import type { ProductCardProps } from './ProductCard.types';
export const ProductCard: FC<ProductCardProps> = ({ title, price, onAddToCart }) => {
return (
<div className="product-card">
<h3>{title}</h3>
<span>{price}</span>
<button type="button" onClick={onAddToCart}>
Sepete Ekle
</button>
</div>
);
};
3.2 State İçeren Component
import { useState, useCallback, type FC } from 'react';
import type { CounterProps } from './Counter.types';
export const Counter: FC<CounterProps> = ({ initialValue = 0, max }) => {
const [count, setCount] = useState<number>(initialValue);
const increment = useCallback(() => {
setCount((prev) => (max !== undefined ? Math.min(prev + 1, max) : prev + 1));
}, [max]);
const decrement = useCallback(() => {
setCount((prev) => Math.max(prev - 1, 0));
}, []);
return (
<div>
<button type="button" onClick={decrement}>-</button>
<span>{count}</span>
<button type="button" onClick={increment}>+</button>
</div>
);
};
4. TİP TANIMLARI (ProductCard.types.ts)
export interface ProductCardProps {
title: string;
price: number;
description?: string;
onAddToCart: () => void;
}
Kurallar:
interface tercih edilir; yalnızca union/intersection gerektiğinde type kullanılır.
- Her prop için JSDoc comment zorunludur.
- Callback proplar
on prefix'i ile başlar: onClick, onChange, onSubmit.
- Opsiyonel proplar
? ile işaretlenir; default değer component içinde atanır.
React.FC yerine import type { FC } kullanılır (tree-shaking dostu).
5. CUSTOM HOOK ŞABLONU (useProductCard.ts)
Component içinde 3+ satırı aşan logic varsa custom hook'a çıkarılır.
import { useState, useCallback } from 'react';
interface UseProductCardOptions {
initialQuantity?: number;
}
interface UseProductCardReturn {
quantity: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export function useProductCard({ initialQuantity = 1 }: UseProductCardOptions = {}): UseProductCardReturn {
const [quantity, setQuantity] = useState<number>(initialQuantity);
const increment = useCallback(() => setQuantity((q) => q + 1), []);
const decrement = useCallback(() => setQuantity((q) => Math.max(q - 1, 1)), []);
const reset = useCallback(() => setQuantity(initialQuantity), [initialQuantity]);
return { quantity, increment, decrement, reset };
}
6. PERFORMANS KURALLARI
| Durum | Çözüm |
|---|
| Pahalı hesaplama var | useMemo kullan |
| Callback prop olarak iletilecek fonksiyon | useCallback kullan |
| Aynı props ile re-render engellenmeli | React.memo ile sar |
| Liste render | Her item'ın key prop'u unique ve kararlı olmalı (index kullanma) |
import { memo, useMemo, useCallback, type FC } from 'react';
export const ProductList: FC<ProductListProps> = memo(({ products, onSelect }) => {
const sortedProducts = useMemo(
() => [...products].sort((a, b) => a.title.localeCompare(b.title)),
[products],
);
const handleSelect = useCallback(
(id: string) => onSelect(id),
[onSelect],
);
return (
<ul>
{sortedProducts.map((p) => (
<li key={p.id}>
<button type="button" onClick={() => handleSelect(p.id)}>
{p.title}
</button>
</li>
))}
</ul>
);
});
ProductList.displayName = 'ProductList';
React.memo ile sarılan her component'e displayName atanır.
7. FORM COMPONENT ŞABLONU
import { useState, type FC, type FormEvent } from 'react';
import type { LoginFormProps, LoginFormValues } from './LoginForm.types';
const INITIAL_VALUES: LoginFormValues = { email: '', password: '' };
export const LoginForm: FC<LoginFormProps> = ({ onSubmit }) => {
const [values, setValues] = useState<LoginFormValues>(INITIAL_VALUES);
const [errors, setErrors] = useState<Partial<LoginFormValues>>({});
const validate = (vals: LoginFormValues): Partial<LoginFormValues> => {
const errs: Partial<LoginFormValues> = {};
if (!vals.email.includes('@')) errs.email = 'Geçerli bir e-posta girin.';
if (vals.password.length < 8) errs.password = 'Şifre en az 8 karakter olmalı.';
return errs;
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const errs = validate(values);
if (Object.keys(errs).length > 0) {
setErrors(errs);
return;
}
onSubmit(values);
};
return (
<form onSubmit={handleSubmit} noValidate>
<label htmlFor="email">E-posta</label>
<input
id="email"
type="email"
value={values.email}
onChange={(e) => setValues((v) => ({ ...v, email: e.target.value }))}
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && <span id="email-error" role="alert">{errors.email}</span>}
<label htmlFor="password">Şifre</label>
<input
id="password"
type="password"
value={values.password}
onChange={(e) => setValues((v) => ({ ...v, password: e.target.value }))}
aria-invalid={!!errors.password}
aria-describedby={errors.password ? 'password-error' : undefined}
/>
{errors.password && <span id="password-error" role="alert">{errors.password}</span>}
<button type="submit">Giriş Yap</button>
</form>
);
};
8. ERİŞİLEBİLİRLİK (A11Y) KURALLARI
- Her
<input> için <label> zorunludur (htmlFor + id eşleşmeli).
- İnteraktif elementler
role veya semantik HTML tag kullanır.
- Hata mesajları
role="alert" ile işaretlenir.
- Butonlarda
type="button" veya type="submit" her zaman yazılır.
- İkon-only butonlara
aria-label eklenir.
9. STİL YÖNETİMİ
Tercih sırası:
- CSS Modules — scoped, çakışma olmaz.
- Inline style — yalnızca dinamik, tek değer için (ör.
style={{ width: pct + '%' }}).
- Global CSS — yalnızca reset/base için.
import styles from './ProductCard.module.css';
export const ProductCard: FC<ProductCardProps> = ({ title, isHighlighted }) => (
<div className={`${styles.card} ${isHighlighted ? styles.highlighted : ''}`}>
{title}
</div>
);
10. HATA SINIRI (Error Boundary)
Sayfa-seviyesi component'lerde hata sınırı sarılır:
import { Component, type ReactNode, type ErrorInfo } from 'react';
interface Props { children: ReactNode; fallback?: ReactNode; }
interface State { hasError: boolean; }
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error('[ErrorBoundary]', error, info.componentStack);
}
render(): ReactNode {
if (this.state.hasError) {
return this.props.fallback ?? <p>Bir hata oluştu.</p>;
}
return this.props.children;
}
}
11. ADLANDIRMA KONVANSİYONLARI
| Öğe | Kural | Örnek |
|---|
| Component dosyası | PascalCase | ProductCard.tsx |
| Hook dosyası | camelCase, use prefix | useProductCard.ts |
| Tip dosyası | PascalCase + .types.ts | ProductCard.types.ts |
| CSS Module | Component adıyla aynı + .module.css | ProductCard.module.css |
| Prop interface | Component adı + Props | ProductCardProps |
| Event handler | handle prefix (local) / on prefix (prop) | handleClick / onClick |
| Boolean prop | is, has, can, should prefix | isLoading, hasError |
| Sabit değer | SCREAMING_SNAKE_CASE | MAX_QUANTITY |
12. YASAK KALIPLAR
export default function ProductCard() { ... }
const handler = (e: any) => { ... };
items.map((item, i) => <li key={i}>{item.name}</li>);
<Component style={{ margin: 0 }} options={[]} />
<A><B><C data={data} /></B></A>
useEffect(async () => { ... }, []);
useEffect(() => { doSomething(value); }, []);
13. COMPONENT ÜRETİM KONTROL LİSTESİ
Bir component tamamlandığında aşağıdaki maddelerin hepsi doğrulanmalıdır: