اصول SOLID در JS/React
S (Single Responsibility Principle):
- هر کامپوننت یک وظیفه خاص داشته باشد
چرا مهمه؟
چون هرچی یک کامپوننت مسئولیتهای بیشتری داشته باشه، تست کردن، دیباگ کردن و توسعهاش سختتر میشه. کامپوننتها باید مثل یه ربات تمیزکار باشن: فقط یه کار انجام بدن و درست انجام بدن.
یعنی:
- Component = فقط UI
- Service = فقط logic
- API = فقط اتصال به سرور
- Store = فقط مدیریت state
// ❌ Bad: چند مسئولیت متفاوت در یک کامپوننت
function UserProfile({ user }) {
// نمایش اطلاعات
// مدیریت فرم
// فراخوانی API
}
// ✅ Good: جداسازی مسئولیتها
function UserProfile({ user }) {
return (
<div>
<UserInfo user={user} />
<UserSettingsForm userId={user.id} />
</div>
);
}
O (Open/Closed Principle):
- کامپوننتها باید قابل توسعه باشن، بدون اینکه نیاز باشه تغییرشون بدی.
// استفاده از Composition
function Button({ children, variant = "primary", size = "medium", ...props }) {
const baseClasses = "btn";
const variantClasses = {
primary: "btn-primary",
secondary: "btn-secondary",
danger: "btn-danger",
};
const sizeClasses = {
small: "btn-sm",
medium: "btn-md",
large: "btn-lg",
};
return (
<button
className={`${baseClasses} ${variantClasses[variant]
} ${sizeClasses[size]
}`}
{...props}
>
{children}
</button>
);
}
// قابل توسعه بدون تغییر Button
function IconButton({ icon, children, ...props }) {
return (
<Button {...props}>
<Icon name={icon} />
{children}
</Button>
);
}
L (Liskov Substitution Principle):
- کامپوننتهای فرزند باید بتوانند جایگزین والد شوند.
// Good کامپوننتها قابل تعویض هستن
function BaseInput({ label, ...props }) {
return (
<div className="input-group">
{label && <label>{label}</label>}
<input {...props} />
</div>
);
}
function EmailInput(props) {
return <BaseInput type="email" {...props} />;
}
function PasswordInput(props) {
return <BaseInput type="password" {...props} />;
}
// . قابل استفاده به جای یکدیگر
<BaseInput label="Username" />
<EmailInput label="Email" />
<PasswordInput label="Password" />
I (Interface Segregation):
- کامپوننتها نباید propsهای سنگین یا بیاستفاده بگیرن.
چرا؟
چون تبدیل میشن به چاقوی سوئیسی React: همهچیز دارن ولی نصفش هیچوقت استفاده نمیشه.
// ❌ Bad: props بزرگ و حجیم
function UserCard({ user, showEmail, showPhone, showAddress }) {
return (
<div>
<h3>{user.name}</h3>
{showEmail && <p>{user.email}</p>}
{showPhone && <p>{user.phone}</p>}
{showAddress && <p>{user.address}</p>}
</div>
);
}
// ✅ Good: کامپوننتهای کوچک و تخصصی
function UserCard({ user, children }) {
return (
<div>
<h3>{user.name}</h3>
{children}
</div>
);
}
function UserCardWithEmail({ user }) {
return (
<UserCard user={user}>
<p>{user.email}</p>
</UserCard>
);
}
D (Dependency Inversion):
- کامپوننت باید به abstraction وابسته باشد، نه implementation.
این یعنی منطقت رو inject کن، hardcode نکن.
// ❌ Bad: وابستگی مستقیم به fetch
function UserList() {
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(setUsers);
}, []
);
}
// ✅ Good: abstraction
const getUserService = apiClient => ({
getUsers: () => apiClient.get("/users"),
});
function UserList({ userService }) {
useEffect(() => {
userService.getUsers().then(setUsers);
}, [userService]
);
}
جمع بندی
الگوهای طراحی در فرانتاند تنها مجموعهای از بهترینها نیستند، بلکه روشی سیستماتیک برای حل مسائل معمول هستند. انتخاب الگوی مناسب به اندازه پروژه، تیم و نیازهای کسبوکار بستگی دارد. مهمترین نکته حفظ ثبات در پروژه و تطبیق الگوها با شرایط خاص هر پروژه است.
نکته طلایی: بهترین الگو، الگویی است که تیم شما آن را درک کند، نگهداری آن آسان باشد و نیازهای پروژه شما را برآورده سازد