ساخت Components قابل استفاده مجدد در Tailwind CSS
بهنام نوبخت · ۱۴۰۵/۷/۱۳
یکی از مزیتهای Tailwind CSS این است که میتوانیم ظاهر عناصر را مستقیماً با Utility Classها ایجاد کنیم؛ اما در پروژههای واقعی ممکن است یک مشکل ایجاد شود: تکرار کد. فرض کنید در یک فروشگاه چندین Button، Card، Badge و Input داریم که ظاهر مشابهی دارند. اگر برای هر عنصر تمام Classها را دوباره بنویسیم، کد HTML طولانی و نگهداری آن دشوار میشود. راهحل، ایجاد Componentهای قابل استفاده مجدد است. در این مقاله یاد میگیریم مفهوم Component چیست، چرا باید از آن استفاده کنیم، چگونه الگوهای تکرارشونده را شناسایی کنیم و چگونه Button، Card، Badge و سایر عناصر را به Component تبدیل کنیم. همچنین با مفهوم Variant آشنا میشویم تا بتوانیم یک Component را با ظاهرهای مختلف استفاده کنیم.
Component چیست؟
Component یک بخش مستقل و قابل استفاده مجدد از رابط کاربری است.
برای مثال در یک سایت ممکن است چندین Button داشته باشیم:
Primary Button
Secondary Button
Danger Buttonیا چندین Card:
Product Card
Blog Card
Service Cardبه جای اینکه هر بار ساختار و Style را از ابتدا بنویسیم، میتوانیم یک الگوی قابل استفاده مجدد ایجاد کنیم.
به صورت ساده:
Component
↓
Reusable UI
↓
Multiple Placesچرا Component مهم است؟
فرض کنید این Button را در ۱۰ قسمت پروژه استفاده کردهایم:
<button class="rounded-lg bg-blue-600 px-5 py-3 text-white">
خرید
</button>حالا تصمیم میگیریم رنگ Button را تغییر دهیم.
اگر Classها در ۱۰ قسمت مختلف تکرار شده باشند، باید هر ۱۰ قسمت را بررسی کنیم.
اما اگر Button یک Component باشد:
Button Component
↓
10 usagesمیتوانیم Style آن را در یک محل مدیریت کنیم.
Componentها فقط برای کوتاه کردن کد نیستند؛ هدف اصلی آنها ایجاد ساختار منظم، قابل نگهداری و قابل توسعه است.
Component در Tailwind چگونه ایجاد میشود؟
Tailwind CSS خودش یک Framework برای Componentهای JavaScript نیست.
Tailwind بیشتر روی Styling تمرکز دارد.
Component میتواند با تکنولوژیهای مختلف ساخته شود:
HTML
React
Vue
Angular
Next.js
ASP.NET Razor
Blazorبنابراین باید بین این دو مفهوم تفاوت قائل شویم:
Tailwind
→ Styling
Framework
→ Component Structureدر این مقاله ابتدا مفهوم Component را با HTML و الگوهای ساده یاد میگیریم و سپس ساختار آن را در Frameworkها بررسی میکنیم.
اولین Component — Button
فرض کنید Button اصلی پروژه ما این Style را دارد:
<button
class="rounded-lg bg-blue-600 px-5 py-3
font-semibold text-white
hover:bg-blue-700"
>
خرید
</button>اگر این Button چندین بار استفاده شود، Classها نیز تکرار میشوند.
به جای آن میتوانیم یک Component به نام:
Buttonداشته باشیم.
و فقط محتوای آن را تغییر دهیم:
Button
├── خرید
├── ثبت نام
├── ورود
└── ادامهButton Variant
گاهی همه Buttonها یکسان نیستند.
مثلاً:
Primary
Secondary
Danger
Successبه این حالتها معمولاً Variant گفته میشود.
مثلاً:
Button
├── Primary
├── Secondary
└── Dangerطراحی Button Variant با HTML
برای درک مفهوم، میتوانیم سه Button داشته باشیم:
<button class="rounded-lg bg-blue-600 px-5 py-3 text-white hover:bg-blue-700">
Primary
</button>
<button class="rounded-lg bg-gray-200 px-5 py-3 text-gray-900 hover:bg-gray-300">
Secondary
</button>
<button class="rounded-lg bg-red-600 px-5 py-3 text-white hover:bg-red-700">
Delete
</button>میبینیم که بخش زیادی از Classها تکراری است.
ساختار مشترک:
rounded-lg
px-5
py-3
font-semiboldو بخش متفاوت:
Primary
→ Blue
Secondary
→ Gray
Danger
→ Redاین دقیقاً جایی است که Component و Variant ارزش خودشان را نشان میدهند.
Componentهای HTML
در پروژههای ساده میتوانیم الگوهای مشترک را در فایلهای جداگانه قرار دهیم.
مثلاً:
components/
button.html
card.html
badge.htmlسپس هر Component میتواند ساختار مشخصی داشته باشد.
در پروژههای واقعی، نحوه استفاده از این Componentها به تکنولوژی پروژه بستگی دارد.
Component در React
اگر از React یا Next.js استفاده کنیم، یک Component میتواند یک Function باشد.
مثلاً:
function Button() {
return (
<button className="rounded-lg bg-blue-600 px-5 py-3 text-white">
Button
</button>
);
}حالا:
<Button />همان Button را نمایش میدهد.
ارسال متن به Component
میتوانیم متن Button را نیز قابل تغییر کنیم.
function Button({ children }) {
return (
<button className="rounded-lg bg-blue-600 px-5 py-3 text-white">
{children}
</button>
);
}حالا:
<Button>
خرید
</Button>
<Button>
ثبت نام
</Button>یک Component مشترک داریم اما محتوای آن متفاوت است.
Button با Variant
میتوانیم Variant نیز ایجاد کنیم:
function Button({ variant = "primary", children }) {
const styles = {
primary: "bg-blue-600 hover:bg-blue-700 text-white",
secondary: "bg-gray-200 hover:bg-gray-300 text-gray-900",
danger: "bg-red-600 hover:bg-red-700 text-white"
};
return (
<button
className={`rounded-lg px-5 py-3 font-semibold ${styles[variant]}`}
>
{children}
</button>
);
}استفاده:
<Button variant="primary">
خرید
</Button>
<Button variant="secondary">
انصراف
</Button>
<Button variant="danger">
حذف
</Button>در اینجا یک Component داریم که چند ظاهر مختلف دارد.
ساختار ذهنی Variant
Button
│
├── Primary
│
├── Secondary
│
└── Dangerاین الگو در پروژههای بزرگ بسیار رایج است.
Card Component
حالا یک Card ایجاد کنیم.
نسخه تکراری:
<div class="rounded-xl border bg-white p-6 shadow-sm">
<h3 class="text-lg font-bold">
Product
</h3>
<p class="mt-2 text-gray-600">
Product description.
</p>
</div>اگر این Card در چند قسمت استفاده شود، بهتر است Component شود.
ساخت Card با React
function Card({ title, description }) {
return (
<div className="rounded-xl border bg-white p-6 shadow-sm">
<h3 className="text-lg font-bold">
{title}
</h3>
<p className="mt-2 text-gray-600">
{description}
</p>
</div>
);
}حالا:
<Card
title="Product 1"
description="Product description"
/>
<Card
title="Product 2"
description="Another product description"
/>ساختار مشترک باقی میماند، اما اطلاعات تغییر میکند.
Card با Hover
میتوانیم Stateهایی که در مقاله قبل یاد گرفتیم را نیز داخل Component قرار دهیم:
function Card({ title, description }) {
return (
<div
className="group rounded-xl border bg-white p-6 shadow-sm
transition-all duration-300
hover:-translate-y-1
hover:shadow-lg"
>
<h3 className="text-lg font-bold group-hover:text-blue-600">
{title}
</h3>
<p className="mt-2 text-gray-600">
{description}
</p>
</div>
);
}اکنون هر Card رفتار یکسانی دارد.
Badge Component
Badgeها نیز معمولاً چندین بار در پروژه استفاده میشوند.
مثلاً:
<span class="rounded-full bg-green-100 px-3 py-1 text-sm text-green-700">
Available
</span>میتوانیم Badgeهای مختلف داشته باشیم:
Success
Warning
Danger
InfoBadge با Variant
function Badge({ variant = "success", children }) {
const styles = {
success: "bg-green-100 text-green-700",
warning: "bg-yellow-100 text-yellow-700",
danger: "bg-red-100 text-red-700",
info: "bg-blue-100 text-blue-700"
};
return (
<span
className={`rounded-full px-3 py-1 text-sm ${styles[variant]}`}
>
{children}
</span>
);
}استفاده:
<Badge variant="success">
Available
</Badge>
<Badge variant="warning">
Pending
</Badge>
<Badge variant="danger">
Failed
</Badge>Input Component
Input نیز گزینه خوبی برای Component شدن است.
function Input({ label, placeholder }) {
return (
<div>
<label className="mb-2 block font-medium">
{label}
</label>
<input
type="text"
placeholder={placeholder}
className="w-full rounded-lg border border-gray-300
px-4 py-3
focus:border-blue-500
focus:ring-2
focus:ring-blue-200"
/>
</div>
);
}حالا:
<Input
label="نام"
placeholder="نام خود را وارد کنید"
/>
<Input
label="ایمیل"
placeholder="example@email.com"
/>ساختار یکسان است و فقط اطلاعات تغییر میکند.
چرا Component بهتر از Copy/Paste است؟
فرض کنید ۲۰ Product Card داریم.
روش اول:
Copy
Copy
Copy
Copy
Copy
...مشکل:
کد زیاد
نگهداری سخت
احتمال خطا
تغییرات دشوار
روش دوم:
ProductCard
↓
20 usagesمزایا:
یک محل برای Style
تغییر سریع
ساختار منظم
استفاده مجدد
کاهش تکرار
Component فقط برای Style نیست
Component میتواند علاوه بر ظاهر، منطق مربوط به همان بخش را نیز در خود داشته باشد.
مثلاً:
ProductCard
├── Image
├── Title
├── Price
├── Badge
└── Buttonاین Component میتواند اطلاعات Product را دریافت کند و آنها را نمایش دهد.
ساخت ProductCard
مثال:
function ProductCard({ product }) {
return (
<div
className="overflow-hidden rounded-xl border bg-white
shadow-sm transition-all duration-300
hover:-translate-y-1 hover:shadow-lg"
>
<img
src={product.image}
alt={product.title}
className="h-56 w-full object-cover"
/>
<div className="p-5">
<h3 className="text-lg font-bold">
{product.title}
</h3>
<p className="mt-2 text-gray-600">
{product.description}
</p>
<div className="mt-5 flex items-center justify-between">
<span className="font-bold">
{product.price}
</span>
<button
className="rounded-lg bg-blue-600
px-4 py-2 text-white
hover:bg-blue-700"
>
خرید
</button>
</div>
</div>
</div>
);
}حالا اطلاعات میتواند از یک Object بیاید:
const product = {
image: "product.jpg",
title: "Modern Product",
description: "Product description",
price: "$120"
};و:
<ProductCard product={product} />Component Composition
گاهی یک Component از چند Component کوچکتر ساخته میشود.
مثلاً:
ProductCard
│
├── ProductImage
├── ProductInfo
│ ├── ProductTitle
│ ├── ProductDescription
│ └── ProductPrice
│
└── Buttonاین مفهوم را Composition مینامیم.
به جای اینکه یک Component بسیار بزرگ بسازیم، میتوانیم آن را به بخشهای کوچکتر تقسیم کنیم.
چه زمانی یک Component بسازیم؟
هر بخش صفحه الزاماً نیاز به Component جداگانه ندارد.
معمولاً زمانی Component ایجاد میکنیم که:
یک بخش چند بار استفاده میشود.
ساختار آن مشخص و مستقل است.
منطق خاصی دارد.
تغییرات آن باید در یک محل مدیریت شود.
میخواهیم کد خواناتر شود.
مثلاً:
Button
Card
Input
Modal
Navbar
Footer
Badge
Alertمعمولاً Componentهای مناسبی هستند.
Component بیش از حد کوچک
گاهی ممکن است بیش از حد Component ایجاد کنیم.
مثلاً:
SmallText
SmallIcon
SmallWrapper
SmallTitleبرای هر عنصر کوچک یک Component بسازیم.
این کار میتواند پروژه را پیچیده کند.
بنابراین هدف این نیست که همه چیز Component باشد.
هدف این است که بخشهای منطقی و قابل استفاده مجدد را جدا کنیم.
Component و Responsive Design
Componentها میتوانند Responsive نیز باشند.
مثلاً:
function ProductCard() {
return (
<div
className="w-full
rounded-xl border p-5
md:w-1/2
lg:w-1/3"
>
Product
</div>
);
}هر بار که Component استفاده شود، همان Responsive behavior را خواهد داشت.
Component و Dark Mode
همچنین میتوانیم Dark Mode را داخل Component قرار دهیم:
function Card({ title }) {
return (
<div
className="rounded-xl border
bg-white text-gray-900
dark:border-gray-700
dark:bg-gray-800
dark:text-white"
>
<h3 className="font-bold">
{title}
</h3>
</div>
);
}اکنون تمام Cardهای پروژه از همین Theme پشتیبانی میکنند.
Component و State
Stateهایی که در مقاله قبل یاد گرفتیم نیز میتوانند داخل Component قرار بگیرند.
مثلاً:
function Button() {
return (
<button
className="rounded-lg bg-blue-600 px-5 py-3 text-white
transition-colors duration-300
hover:bg-blue-700
focus:ring-2
focus:ring-blue-300
active:bg-blue-800"
>
Submit
</button>
);
}حالا تمام Buttonهای پروژه میتوانند رفتار مشابه داشته باشند.
یک ساختار پیشنهادی برای پروژه
در یک پروژه Frontend میتوانیم ساختاری مانند زیر داشته باشیم:
src/
│
├── components/
│ ├── Button
│ ├── Card
│ ├── Badge
│ ├── Input
│ └── Navbar
│
├── pages/
│
├── layouts/
│
└── styles/ساختار دقیق به Framework و معماری پروژه بستگی دارد، اما ایده اصلی این است که Componentهای قابل استفاده مجدد در یک محل مشخص قرار بگیرند.
Tailwind و Component Classها
در Tailwind میتوان گاهی مجموعهای از Utilityها را به یک Class سفارشی تبدیل کرد.
برای مثال در CSS میتوان از @apply در شرایط و نسخههای پشتیبانیشده استفاده کرد:
.btn-primary {
@apply rounded-lg bg-blue-600 px-5 py-3 text-white;
}سپس:
<button class="btn-primary">
خرید
</button>اما بهتر است @apply را برای هر عنصر بهصورت بیدلیل استفاده نکنیم.
در بسیاری از پروژهها، Componentهای Framework مانند React یا Vue محل مناسبتری برای تعریف الگوهای قابل استفاده مجدد هستند.
اگر یک UI Component واقعی دارید، معمولاً بهتر است آن را در سطح Component مدیریت کنید؛ نه اینکه تمام پروژه را به مجموعهای از Classهای CSS سفارشی تبدیل کنید.
یک مثال کامل
فرض کنید یک صفحه محصولات داریم.
ساختار:
Products Page
│
├── Navbar
│
├── ProductGrid
│ ├── ProductCard
│ ├── ProductCard
│ └── ProductCard
│
└── Footerو هر ProductCard:
ProductCard
│
├── Image
├── Title
├── Description
├── Price
├── Badge
└── Buttonاین ساختار بسیار قابل توسعهتر از یک فایل HTML بسیار بزرگ است.
اشتباهات رایج
۱. Copy/Paste زیاد
اگر یک بخش چندین بار تکرار شده، بررسی کنید آیا میتواند Component شود.
۲. Componentهای بسیار بزرگ
یک Component با صدها خط کد معمولاً مدیریت دشوارتری دارد.
۳. Componentهای بیش از حد کوچک
برای هر <span> یا <div> نباید الزاماً Component ایجاد کنیم.
۴. مخلوط کردن مسئولیتها
بهتر است هر Component مسئولیت مشخصی داشته باشد.
۵. Variantهای بیش از حد
اگر یک Button دارای دهها Variant باشد، ممکن است طراحی Component نیاز به بازنگری داشته باشد.
تمرین عملی
یک فروشگاه کوچک با Componentهای زیر طراحی کنید:
Navbar
Button
Badge
ProductCard
ProductGrid
FooterButton
حداقل سه Variant داشته باشد:
Primary
Secondary
DangerBadge
حداقل سه Variant:
Success
Warning
DangerProductCard
شامل:
Image
Title
Description
Price
Badge
ButtonProductGrid
Responsive باشد:
Mobile
1 column
Tablet
2 columns
Desktop
3 columnsDark Mode
تمام Componentها باید از Dark Mode پشتیبانی کنند.
States
Buttonها و Cardها باید hover و focus مناسب داشته باشند.
در پایان بررسی کنید آیا برای تغییر ظاهر Button یا Card لازم است چندین فایل را ویرایش کنید یا فقط یک Component را.
جمعبندی
در این مقاله با مفهوم Reusable Component آشنا شدیم.
یاد گرفتیم:
Component چیست.
چرا Componentها اهمیت دارند.
چگونه Button را به Component تبدیل کنیم.
مفهوم Variant چیست.
چگونه Buttonهای مختلف ایجاد کنیم.
چگونه Card و Badge بسازیم.
چگونه Input را به Component تبدیل کنیم.
چگونه ProductCard طراحی کنیم.
مفهوم Composition را بررسی کردیم.
Componentها را با Responsive Design ترکیب کردیم.
Dark Mode را داخل Component قرار دادیم.
Stateهایی مانند Hover و Focus را به Componentها اضافه کردیم.
درباره استفاده صحیح از
@applyصحبت کردیم.با ساختار پیشنهادی Componentها در پروژه آشنا شدیم.
مفهوم اصلی این مقاله را میتوان اینگونه خلاصه کرد:
Reusable Component
↓
Consistent UI
↓
Less Repetition
↓
Easier Maintenance
↓
Scalable Projectدر یک پروژه واقعی، ترکیب Tailwind CSS + Component Architecture به ما کمک میکند رابط کاربری را هم سریعتر توسعه دهیم و هم ساختار منظمتری داشته باشیم.
نظرات
هنوز نظری ثبت نشده است.