generate-component
React uygulamalarında kurumsal standartlara uygun component oluşturma işlemini yaparken bu yetekenek devreye girer.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
React uygulamalarında kurumsal standartlara uygun component oluşturma işlemini yaparken bu yetekenek devreye girer.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | generate-component |
| description | React uygulamalarında kurumsal standartlara uygun component oluşturma işlemini yaparken bu yetekenek devreye girer. |
| applyTo | src/**/*.tsx |
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.
| 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. |
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
export { ProductCard } from './ProductCard';
export type { ProductCardProps } from './ProductCard.types';
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>
);
};
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>
);
};
export interface ProductCardProps {
/** Ürün başlığı */
title: string;
/** Ürün fiyatı (TL cinsinden) */
price: number;
/** Opsiyonel açıklama */
description?: string;
/** Callback: ürün sepete eklendiğinde tetiklenir */
onAddToCart: () => void;
}
Kurallar:
interface tercih edilir; yalnızca union/intersection gerektiğinde type kullanılır.on prefix'i ile başlar: onClick, onChange, onSubmit.? ile işaretlenir; default değer component içinde atanır.React.FC yerine import type { FC } kullanılır (tree-shaking dostu).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 };
}
| 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.memoile sarılan her component'edisplayNameatanır.
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>
);
};
<input> için <label> zorunludur (htmlFor + id eşleşmeli).role veya semantik HTML tag kullanır.role="alert" ile işaretlenir.type="button" veya type="submit" her zaman yazılır.aria-label eklenir.Tercih sırası:
style={{ width: pct + '%' }}).import styles from './ProductCard.module.css';
export const ProductCard: FC<ProductCardProps> = ({ title, isHighlighted }) => (
<div className={`${styles.card} ${isHighlighted ? styles.highlighted : ''}`}>
{title}
</div>
);
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;
}
}
| Öğ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 |
// ❌ default export
export default function ProductCard() { ... }
// ❌ any tipi
const handler = (e: any) => { ... };
// ❌ index key
items.map((item, i) => <li key={i}>{item.name}</li>);
// ❌ inline object / array prop (her render yeni referans üretir)
<Component style={{ margin: 0 }} options={[]} />
// ❌ prop drilling 3+ seviye; bunun yerine Context veya state yönetim kütüphanesi
<A><B><C data={data} /></B></A>
// ❌ useEffect içinde async fonksiyon doğrudan
useEffect(async () => { ... }, []); // async IIFE veya ayrı fonksiyon kullan
// ❌ Boş dependency array ile stale closure
useEffect(() => { doSomething(value); }, []); // value bağımlılığa eklenmeli
Bir component tamamlandığında aşağıdaki maddelerin hepsi doğrulanmalıdır:
FC<Props> tipiyle tanımlı, named export.types.ts dosyasında, JSDoc'luexport default yokany tipi yok<input> için <label> varuseCallback ile sarılmışkey prop unique ve kararlımemo kullanıldıysa displayName atanmışindex.ts barrel export güncellendi