(ISP) در کامپوننت TextField
Interface Segregation Principle (ISP)
اصل جداسازی رابطها
“یک کامپوننت نباید مجبور باشه propهایی رو قبول کنه که استفاده نمیکنه.”
به زبان ساده و واقعی:
“هر کامپوننت فقط چیزایی رو ببینه که واقعاً نیاز داره.”
چرا مهمه؟ (مثال واقعی)
فرض کن یه Input داری که این propها رو قبول میکنه:
jsx
<Input
label="نام"
icon="user"
error="این فیلد الزامی است"
helperText="حداقل ۳ حرف"
placeholder="نام خود را وارد کنید"
value={name}
onChange={handleChange}
/>
مشکل:
- Input فقط یه فیلد ورودی هست.
- اما مجبوره label, icon, error, helperText رو هم مدیریت کنه!
- اگر فقط یه input ساده بخوای، بازم باید این propها رو بدی (یا null بدی).
راهحل ISP: جداسازی مسئولیتها
| کامپوننت | مسئولیت |
|---|---|
| Input | فقط ورودی |
| Label | فقط لیبل |
| ErrorMessage | فقط خطا |
| TextField | همه رو ترکیب کنه |
کد با رعایت ISP (جاوااسکریپت خالص)
jsx
// components/ui/Input.js
export default function Input({ value, onChange, placeholder, type = 'text', ...props }) {
return (
<input
type={type}
value={value}
onChange={onChange}
placeholder={placeholder}
className="border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500"
{...props}
/>
);
}
jsx
// components/ui/Label.js
export default function Label({ children }) {
return <label className="block text-sm font-medium text-gray-700 mb-1">{children}</label>;
}
jsx
// components/ui/ErrorMessage.js
export default function ErrorMessage({ children }) {
return <p className="text-sm text-red-600 mt-1">{children}</p>;
}
jsx
// components/form/TextField.js
import Input from '../ui/Input';
import Label from '../ui/Label';
import ErrorMessage from '../ui/ErrorMessage';
export default function TextField({
label,
value,
onChange,
error,
helperText,
placeholder,
type = 'text'
}) {
return (
<div className="space-y-1">
{label && <Label>{label}</Label>}
<Input value={value} onChange={onChange} placeholder={placeholder} type={type} />
{error && <ErrorMessage>{error}</ErrorMessage>}
{helperText && <p className="text-xs text-gray-500">{helperText}</p>}
</div>
);
}
استفاده
۱. وقتی همه چیز رو میخوای:
jsx
<TextField
label="ایمیل"
value={email}
onChange={handleChange}
placeholder="example@domain.com"
error={emailError}
helperText="ایمیل معتبر وارد کنید"
/>
۲. وقتی فقط یه input ساده میخوای:
jsx
<Input
value={search}
onChange={handleSearch}
placeholder="جستجو..."
/>
نمیخوای label و error بدی؟ نمیدی!
ISP به چه دردی میخوره؟
| مزیت | توضیح |
|---|---|
| کامپوننتهای تمیز | Input فقط ورودی است |
| استفاده مجدد | Input رو در ۱۰ جا مختلف میتونی استفاده کنی |
| تست آسان | Input رو جدا تست میکنی |
| انعطاف | TextField فقط برای فرمهای کامل |
| بدون propهای اضافی | Input مجبور نیست label رو قبول کنه |
کی از ISP استفاده کنیم؟
| شرایط | استفاده از ISP |
|---|---|
| کامپوننتهای UI داری (مثل Input, Button) | حتماً |
| فرمهای پیچیده داری | حتماً |
| میخوای کامپوننتها قابل استفاده مجدد باشن | حتماً |
| فقط یه input ساده داری | شاید نه |
مثال واقعی: بدون ISP (بد)
jsx
// Input خیلی شلوغ!
<Input
label="نام"
showLabel={true}
icon="user"
showIcon={true}
error="الزامی است"
showError={true}
helperText="..."
showHelper={true}
value={...}
onChange={...}
/>
مشکل: Input داره نقش Label, Icon, Error رو هم بازی میکنه!
مثال واقعی: با ISP (خوب)
jsx
<div className="space-y-1">
<Label>نام</Label>
<div className="flex">
<span className="inline-flex items-center px-3 border border-r-0 rounded-l-md bg-gray-50">
کاربر
</span>
<Input value={name} onChange={handleChange} />
</div>
<ErrorMessage>این فیلد الزامی است</ErrorMessage>
</div>
هر کامپوننت فقط کار خودش رو میکنه.
ISP در Design System
| کامپوننت | فقط این propها رو قبول کنه |
|---|---|
| Button | children, onClick, disabled |
| Input | value, onChange, placeholder, type |
| TextField | label, error, helperText + Input props |
جمعبندی ساده
| سوال | پاسخ |
|---|---|
| ISP چیه؟ | جداسازی کامپوننتها بر اساس مسئولیت |
| چرا مهمه؟ | کامپوننتها تمیز، قابل استفاده مجدد و تستپذیر بشن |
| کی استفاده کنیم؟ | هر وقت کامپوننت UI داری که چند نقش داره |
| مثال؟ | Input ≠ TextField |
خلاصه تصویری
text
بد (نقض ISP):
┌─────────────────────┐
│ Input │ ← label, icon, error, helperText
└─────────────────────┘
خوب (رعایت ISP):
┌─────────┐ ┌───────┐ ┌────────────┐ ┌──────────────┐
│ Label │ │ Input │ │ ErrorMessage│ │ TextField │ ← ترکیب
└─────────┘ └───────┘ └────────────┘ └──────────────┘
اگر خواستی، یه Design System کامل با ISP برات بسازم که شامل:
- Input, Label, ErrorMessage
- TextField, SelectField, CheckboxField
- Form با useForm
- همه با جاوااسکریپت خالص
سوال ۱:
آیا ISP واقعاً در پروژههای واقعی با Next.js استفاده میشه؟ یا فقط تئوریه؟
پاسخ: بله، ۱۰۰٪ استفاده میشه — و نه فقط تئوری!
مثالهای واقعی (استاندارد صنعت):
| کتابخانه | نحوه رعایت ISP |
|---|---|
| Shadcn/ui (محبوبترین در Next.js) | Input جدا، Label جدا، FormField ترکیب |
| MUI (Material UI) | TextField = ترکیب Input + Label + HelperText |
| Chakra UI | FormControl + FormLabel + Input |
| Radix UI | Input, Label, Text جدا — ترکیب با asChild |
همه اینها دقیقاً ISP رو رعایت کردن.
مثال از Shadcn/ui (کتابخانه رسمی Next.js):
tsx
// shadcn/ui — Input.js
export const Input = React.forwardRef(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn("flex h-10 w-full rounded-md border ...", className)}
ref={ref}
{...props}
/>
);
});
tsx
// FormField.jsx
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>ایمیل</FormLabel>
<FormControl>
<Input placeholder="m@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
Input فقط ورودی است. FormField ترکیب میکنه. ISP کاملاً رعایت شده.
سوال ۲:
“حجم کد زیاد نمیشه؟”
پاسخ: در ابتدا کمی بیشتره، اما در بلندمدت ۱۰ برابر صرفهجویی میکنی!
مقایسه حجم کد (مثال واقعی)
| روش | تعداد فایل | حجم اولیه | حجم بلندمدت |
|---|---|---|---|
| بد (یک Input بزرگ) | ۱ فایل | کم | زیاد (تکرار) |
| خوب (ISP) | ۴ فایل | کمی بیشتر | خیلی کمتر |
بد: یک Input بزرگ
jsx
// ۱ فایل — ۱۰۰ خط
<Input label="..." error="..." icon="..." helperText="..." />
در ۵۰ صفحه: ۵۰ بار تکرار کد مشابه
خوب: ISP
jsx
// ۴ فایل کوچک
<Input />
<Label />
<ErrorMessage />
<TextField /> ← ترکیب
در ۵۰ صفحه:
- ۴۰ بار فقط <Input />
- ۱۰ بار <TextField /> بدون تکرار!
سوال ۳:
“ما propها رو از Input گرفتیم و به TextField دادیم — پس TextField داره propهای زیادی میگیره!”
پاسخ: درست متوجه شدی! اما این دقیقاً هدف ISP هست!
ISP نمیگه:
“هیچ کامپوننتی نباید prop زیاد داشته باشه”
ISP میگه:
“کامپوننت نباید propهایی رو قبول کنه که خودش استفاده نمیکنه”
مقایسه:
| کامپوننت | propها | استفاده میکنه؟ | ISP رعایت شده؟ |
|---|---|---|---|
| Input | value, onChange | بله | درست |
| TextField | label, error, value, onChange | بله (همه رو استفاده میکنه) | درست |
TextField همه propها رو استفاده میکنه → ISP رعایت شده!
چرا TextField prop زیاد داره؟ چون مسئولیت ترکیب داره!
jsx
<TextField
label="نام"
value={name}
onChange={handleChange}
error={error}
helperText="حداقل ۳ حرف"
/>
TextField مثل یک فرمساز هست. وظیفهاش ترکیب کامپوننتهای کوچک است.
ISP در عمل (چطور کار میکنه؟)
jsx
// فقط input ساده میخوای؟
<Input value={search} onChange={handleSearch} />
// فرم کامل میخوای؟
<TextField label="ایمیل" error={error} />
// فقط لیبل؟
<Label>نام کاربری</Label>
هر کامپوننت فقط چیزایی که نیاز داره رو میگیره.
ISP چه مزایایی در پروژه واقعی داره؟
| مزیت | مثال واقعی |
|---|---|
| استفاده مجدد | Input در مودال، سرچبار، فرم |
| تست آسان | Input رو جدا تست میکنی |
| تغییر آسان | استایل Label رو یکجا عوض میکنی |
| تیمهای مختلف | تیم A: Input در مودال، تیم B: TextField در فرم |
| رفع باگ | اگر error مشکل داشت، فقط ErrorMessage رو درست میکنی |
ISP در Next.js (پروژه واقعی)
text
components/
ui/
Input.js ← فقط ورودی
Label.js ← فقط لیبل
ErrorMessage.js ← فقط خطا
Button.js
form/
TextField.js ← ترکیب
SelectField.js
FormActions.js
hooks/
useForm.js
همه پروژههای حرفهای Next.js همین ساختار رو دارن.
جمعبندی ساده
| سوال | پاسخ |
|---|---|
| ISP در Next.js استفاده میشه؟ | بله، در همه پروژههای بزرگ |
| حجم کد زیاد میشه؟ | در ابتدا کمی، در بلندمدت خیلی کمتر |
| TextField prop زیاد داره؟ | درست است — چون مسئولیت ترکیب داره |
| ISP چیه؟ | “هر کامپوننت فقط چیزایی که استفاده میکنه رو بگیره” |
مثال نهایی از پروژه واقعی
jsx
// صفحه ثبتنام
<TextField label="ایمیل" name="email" type="email" />
// سرچبار در هدر
<div className="relative">
<Input placeholder="جستجو..." className="pr-10" />
<button className="absolute left-3 top-2">جستجو</button>
</div>
// فقط لیبل در تنظیمات
<Label className="text-lg font-bold">تنظیمات حساب</Label>