۱۴۰۴/۰۸/۱۱ Nebular

(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ها رو قبول کنه
Buttonchildren, onClick, disabled
Inputvalue, onChange, placeholder, type
TextFieldlabel, 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 UIFormControl + FormLabel + Input
Radix UIInput, 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 رعایت شده؟
Inputvalue, onChangeبلهدرست
TextFieldlabel, 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>

Accept Cookies
Accept Cookies
[your-shortcode]