CSS مبتنی بر کامپوننت در برابر کلاس‌های Utility؛ دو فلسفه‌ی متفاوت

تو درس قبل با BEM آشنا شدیم؛ یه روش برای نام‌گذاری کلاس‌های کامپوننت‌محور. اما BEM فقط یه قرارداد نام‌گذاریه، نه تنها فلسفه‌ی ممکن برای سازماندهی CSS. تو این درس می‌خوایم با یه رویکرد کاملاً متفاوت آشنا بشیم: کلاس‌های Utility (که بهش Atomic CSS هم می‌گن)، و ببینیم چرا خیلی از پروژه‌های مدرن (مثل اونایی که با Tailwind ساخته می‌شن) این رویکرد رو به BEM ترجیح می‌دن، و کجاها هرکدوم منطقی‌تره.

یادآوری سریع: رویکرد کامپوننت‌محور (مثل BEM)

تو این رویکرد، هر کلاس نماینده‌ی یه تیکه‌ی معنایی از UI هست:

.card {
  padding: 16px;
  border-radius: 8px;
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
<div class="card">...</div>

یه کلاس، معنی مشخصی داره («این یه کارته») و پیاده‌سازیش (چه ویژگی‌هایی داره) پشت اون اسم پنهانه.

رویکرد Utility: یه کلاس، یه ویژگی

تو رویکرد utility، به‌جای یه کلاس معنایی بزرگ، از چندین کلاس کوچیک استفاده می‌کنید که هرکدوم دقیقاً یه کار می‌کنن:

.p-4 { padding: 16px; }
.rounded-lg { border-radius: 8px; }
.bg-white { background: white; }
.shadow-md { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); }
<div class="p-4 rounded-lg bg-white shadow-md">...</div>

نتیجه‌ی بصری این دو مثال دقیقاً یکسانه؛ فقط محل تعریف استایل فرق می‌کنه: تو حالت اول، تو فایل CSS؛ تو حالت دوم، مستقیم تو کلاس‌های HTML.

مزیت اصلی Utility: نیازی به فکر کردن روی اسم نیست

یکی از سخت‌ترین بخش‌های کار با BEM (که تو درس قبل هم به‌طور ضمنی بهش اشاره کردیم)، پیدا کردن اسم درست برای هر block و element هست. تو رویکرد utility، این مشکل عملاً از بین می‌ره؛ چون کلاس‌ها از قبل تعریف شدن و فقط ترکیبشون می‌کنید. این باعث می‌شه توسعه‌ی رابط کاربری، خصوصاً تو مراحل اولیه‌ی یه پروژه، خیلی سریع‌تر پیش بره.

مزیت دوم: کاهش حجم CSS با رشد پروژه

تو رویکرد کامپوننت‌محور، هر کامپوننت جدید معمولاً یه بلوک جدید CSS با خودش میاره، حتی اگه خیلی از ویژگی‌هاش (مثل padding یا border-radius) با کامپوننت‌های دیگه مشترک باشه. تو رویکرد utility، چون همون کلاس‌های .p-4 یا .rounded-lg بین همه‌ی کامپوننت‌ها به اشتراک گذاشته می‌شن، حجم کلی CSS پروژه معمولاً کندتر رشد می‌کنه؛ بعد از یه نقطه، اضافه کردن یه کامپوننت جدید ممکنه اصلاً نیازی به CSS جدید نداشته باشه، چون همه‌ی کلاس‌های لازم از قبل وجود دارن.

عیب اصلی Utility: طولانی شدن کلاس‌های HTML

<button class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-400 disabled:opacity-50 transition-colors">
  ذخیره
</button>

این خط، از نظر خوانایی HTML سنگین‌تره؛ و اگه همین دکمه تو ۲۰ جای مختلف سایت تکرار بشه، باید کل این رشته‌ی طولانی رو هر بار کپی کنید (یا با یه کامپوننت جاوااسکریپتی، مثل React، فقط یه بار بنویسیدش و بارها استفاده کنید).

حل تعارض: کامپوننت‌سازی در سطح framework، نه CSS

نکته‌ی مهمی که خیلی وقت‌ها گم می‌شه: پروژه‌های utility-first مثل Tailwind، معمولاً کنار یه framework جاوااسکریپتی (React، Vue و غیره) استفاده می‌شن. اونجا، تکرار طولانی کلاس‌ها یه بار تو یه کامپوننت جاوااسکریپتی نوشته می‌شه، و بقیه‌ی جاهای پروژه فقط از همون کامپوننت استفاده می‌کنن:

<!-- این کلاس‌های طولانی فقط یه‌بار، داخل تعریف کامپوننت Button نوشته می‌شن -->
<Button variant="primary">ذخیره</Button>
<Button variant="primary">ارسال</Button>

یعنی مشکل تکرار، با ابزار مناسب (کامپوننت جاوااسکریپتی) حل می‌شه، نه با خود CSS. این نکته‌ی کلیدیه که خیلی از مقایسه‌های سطحی BEM و Tailwind نادیده می‌گیرنش.

می‌شه این دو رویکرد رو ترکیب کرد؟

بله، و تو عمل خیلی از پروژه‌ها همین کار رو می‌کنن: یه سری کلاس معنایی و کامپوننت‌محور (مثل .card) برای ساختارهای پیچیده و تکرارشونده، و کلاس‌های utility برای تنظیمات ریز و یک‌باره (مثل یه فاصله‌ی خاص فقط تو یه صفحه):

<div class="card mt-8 md:mt-0">
  <!-- .card مال ساختار اصلیه، mt-8 یه تنظیم جزئی و موقعیتی -->
</div>

این ترکیب، مزیت خوانایی BEM رو برای بخش‌های اصلی حفظ می‌کنه، ولی برای تنظیمات ریز و متغیر، به utility class ها که سریع‌تر نوشته می‌شن تکیه می‌کنه.

کِی رویکرد کامپوننت‌محور بهتره؟

  • وقتی تیم شما ترجیح می‌ده منطق ظاهری تو خود CSS متمرکز باشه، نه پخش‌شده تو HTML
  • وقتی طراحی سایت خیلی سفارشی و غیرتکراریه (کمتر از الگوهای استاندارد استفاده می‌کنه)
  • پروژه‌های کوچیک‌تر که پیچیدگی build tooling یه utility framework ارزششو نداره

کِی رویکرد Utility بهتره؟

  • وقتی سرعت توسعه‌ی اولیه اهمیت بالایی داره
  • وقتی پروژه از یه framework کامپوننتی (React، Vue) استفاده می‌کنه که مشکل تکرار کلاس‌ها رو حل می‌کنه
  • پروژه‌های بزرگ با خیلی صفحه، جایی که جلوگیری از رشد بی‌رویه‌ی CSS مهمه
نکته مهم: هیچ‌کدوم از این دو رویکرد «همیشه درست» نیستن؛ انتخاب بین BEM (یا مشابهش) و utility-first، بیشتر یه تصمیم تیمی و مبتنی بر نوع پروژه‌ست تا یه قانون فنی مطلق. هر دو رویکرد تو پروژه‌های بزرگ واقعی دنیا با موفقیت استفاده می‌شن.

اشتباه رایج: قضاوت utility class ها فقط از روی ظاهر HTML

خیلی از منتقدهای رویکرد utility، فقط با دیدن یه خط طولانی کلاس تو HTML قضاوت می‌کنن که این روش «کثیفه»، بدون در نظر گرفتن این‌که اگه از framework کامپوننتی استفاده بشه، این تکرار عملاً فقط یه‌بار (تو تعریف کامپوننت) نوشته می‌شه، نه هزاران بار تو کل پروژه. قضاوت درست باید کل جریان کار (workflow) رو در نظر بگیره، نه فقط یه اسکرین‌شات از HTML.

جمع‌بندی

رویکرد کامپوننت‌محور (مثل BEM) استایل رو تو CSS متمرکز می‌کنه و کلاس‌های معنایی می‌سازه؛ رویکرد utility، کلاس‌های کوچیک و تک‌منظوره رو مستقیم تو HTML ترکیب می‌کنه. مشکل تکرار در رویکرد utility معمولاً با کامپوننت‌سازی در سطح framework جاوااسکریپتی حل می‌شه، نه خود CSS. خیلی از پروژه‌های واقعی، ترکیبی از هر دو رو به کار می‌برن: کلاس‌های معنایی برای ساختار اصلی، و utility class ها برای تنظیمات جزئی.