CSS-in-JS دقیقا چیست؟

ترجمه و تالیف : ابوالفضل باغشاهی
تاریخ انتشار : 25 مرداد 99
خواندن در 6 دقیقه
دسته بندی ها : جاوا اسکریپت

این عبارت اشاره به مجموعه‌ای از راهکارها برای حل مشکلات پیچیده توسط css دارد. از آن‌جایی که یک کتابخانه‌ی واحد نیز نیست، ممکن است کتابخانه‌های مختلفی بتوانند این مشکلات را با استفاده از روش‌های مختلف و رویکردهای متفاوت، حل کنند که تمام این‌ها وابسته به نوع پیاده‌سازی هر کدام از آن‌هاست.

البته باید بدانیم که تمام این پیاده‌سازی‌ها در یک نقطه اشتراک دارند؛ حل کردن مشکل با استفاده از API ها به جای روش‌های سنتی و همچنین استفاده از زبان جاوااسکریپت برای تالیف استایل‌ها.

کمبود ماژول‌ها

جاوااسکریپت و css از قدیم الایام، به هیچ‌وجه دارای ماژول‌های واقعی نبوده‌اند. به مرور زمان نیازمندی‌های لازم برای وب اپلیکیشن‌ها تکامل یافت و جاوااسکریپت یک سیستم ماژولی را اضافه کرد. ابتدا این قابلیت به عنوان یک ویژگی اضافی از طریق CommonJS به جاوااسکریپت اضافه شد و سپس به شکلی استاندارد به عنوان یک سیستم ماژولی قابل تجزیه و تحلیل با نام ECMAScript Modules ESM به جاوااسکریپت اضافه شد.

دلیل نیاز به ماژول‌ها در جاوااسکریپت و css احساس نیاز به پنهان کردن جزئیات پیاده‌سازی شده و تنها افشا شدن API های عمومی بوده است. ما نیاز داشتیم که زیرمجموعه‌های مختلف یک اپلیکیشن را صراحتاً از هم جدا کنیم تا در نتیجه تغییر دادن کدها، بیش از پیش قابل پیش‌بینی و قابل اطمینان شود.

هر اپلیکیشنی به این قابلیت نیاز ندارد ولی این ویژگی، نگهداری از اپ‌های متوسط و بزرگ را آسان‌تر می‌کند و همچنین ایجاد تغییرات در آن‌ها را نیز ساده‌تر کرده. هر چه اپ شما کوچک‌تر باشد، پیچیدگی‌های کمتری نیز دارد.

CSS-in-JS نیز بر پایه‌ی اجرای ماژول‌های جاوااسکریپت است.

عدم وجود scoping

ما می‌دانیم که css همیشه دارای یک فضای نام‌گذاری گلوبال (global) بوده است؛ برای مثال یک کلاس ‌می‌تواند به المنتی در هر کجای سند HTML شما، اضافه شود و استایل مورد نظر را به آن بدهد یا حتی شما با استایل دهی به یک تگ در css خود، تمام المنت‌هایی که آن تگ هستند را استایل دهی کنید. css در ابتدا برای استایل دهی به اسناد ساخته شد و در آن زمان بحثی راجع به کامپوننت (component) ها نبوده است. تمام یک صفحه‌ی وب توسط یک فایل css استایل دهی می‌شد و معمولا افراد مختلف و زیادی بر روی یک پروژه کار نمی‌کردند. حال از آن زمان‌ها خیلی گذشته و اپ‌های تحت وب بسیار پیچیده‌تر شده‌اند و همین دلیل باعث شده است که متدولوژی‌های جدید متعددی برای css عرضه شوند.

وبسایت‌های مدرن به اندازه‌ای پیچیده هستند که احساس نیاز به چندین متخصص front-end برای کار بر روی یکی از آن‌ها حس شود. برخی از کامپوننت‌های اپ‌ها ممکن است در مکان‌های مختلفی مورد استفاده‌ی مجدد قرار گیرند و نیاز است که حتما در هرکجایی که قرار بگیرند، به درستی کار کنند.

با توحه به این موارد، نیاز است که یک سیستم اسکوپ بندی مناسب داشته باشیم تا از هرگونه رفتار غیرمنتظره از طرف css (style leaking) دور بمانیم.

در زیر می‌توانید مثالی از نحوه‌ی کارکرد کتاب‌خانه‌های css-in-js برای تولید انتخاب‌گرها را مشاهده کنید:

const css = styleBlock => {
  const className = someHash(styleBlock);
  const styleEl = document.createElement('style');
  styleEl.textContent = `
    .${className} {
      ${styleBlock}
    }
  `;
  document.head.appendChild(styleEl);
  return className;
};
const className = css(`
  color: red;
  padding: 20px;
`); // 'c23j4'

CSS-in-JS با تولید انتخاب‌گرهای (selectors) انحصاری و یکتا (unique) به اسکوپینگ خودکار می‌پردازد.

وابستگی‌های ضمنی

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

  1. یک قاعده‌ی css دارای چندین انتخاب‌گر یا سلکتور باشد تا بتواند به المنت‌های مختلفی از HTML اعمال شود.
  2. چندین کلاس یا اتریبیوت (attribute) به المنت‌های HTML اضافه شوند تا بتوان قواعد مختلف css را به آن‌ها اختصاص داد.

هیچ‌کدام از دو روش بالا که در css سنتی مورد استفاده قرار می‌گیرند، روش‌های مناسبی نیستند؛ چرا که هر دوی آن‌ها منجر به یک ساختار یک‌پارچه برای کد ما می‌شوند که در آن هر قسمت به قسمت دیگر وابسته است و از معماری بر پایه‌ی کامپوننت‌ها به دور است؛ همچنین تقسیم یک سیستم به زیرمجموعه‌های سیستم مختلف را غیرممکن می‌کند.

در مورد اول، اگر ما انتخاب‌گرهای زیادی را به قواعد CSS خود اضافه کنیم، با قواعدی روبرو خواهیم بود که هرکدام اشاره به اجزای مختلفی در کامپوننت‌های مختلف سیستم ما دارند. در واقع شما قادر به تغییر کامپوننت‌های خود بدون ایجاد در تغییر در آن قاعده که در کامپوننت‌های مختلفی استفاده شده است، نخواهید بود.

در مورد دوم نیز که از کلاس‌ها و اتریبیوت‌های مختلفی در ساختار HTML خود استفاده می‌کنیم، این کار باعث مورد هدف قرار گرفتن المنت‌هایمان توسط قواعد css ای مختلفی می‌شود. این اتفاق نیز باز هم منجر به پیچیده شدن روابط موجود در اپ می‌شود.

در هردوی این موارد ما در حال ساختن وابستگی‌هایی هستیم که درکشان سخت و ایجاد تغییر در آن‌ها زمان‌بر است. در واقع نگهداری از چنین سیستمی در طی مدت طولانی، برای انسان دشوار است.

کاری که CSS-in-JS برای ما انجام می‌دهد، صریح سازی وابستگی‌هاست؛ چرا که متغیرها همیشه به شکلی بصری به کد ما ارجاع داده می‌شوند و به دلیل توانایی ما در تجزیه و تحلیل آماری منبع آن‌ها، قابل ردگیری (traceable) خواهند بود. به این ترتیب استایل دهی ها به شکلی جدا از هم خواهند بود که می‌توانند مورد استفاده‌ی مجدد قرار گیرند.

CSS-in-JS صراحت، قابلیت ردگیری و جداسازی وابستگی (dependency) ها را بهبود می‌بخشد.

کدهای مرده

به دلیل رابطه‌ی ضمنی بین HTML و CSS، معمولا یافتن قواعد بدون استفاده‌ی CSS و اطلاع رسانی به توسعه دهنده‌ی آن‌ها و یا حذف این قسمت‌ها از باندل، کاری بسیار سخت است؛ زیرا اغلب نمی‌دانیم که قواعد در کجا مورد استفاده قرار گرفته‌اند.

با گذشت زمان، کدهای مرده احتمالا کارایی و پرفورمنس سایت شما را کاهش می‌دهند؛ جدای از این بعد از مدتی برنامه‌نویسان نیز از کدهای وب‌سایت شما سر در نمی‌آورند.

با وجود متغیرهای صریح و قابل ردگیری و ماژول‌ها در جاوااسکریپت، راه‌کارهای بهتری برای ایجاد رابطه‌ی صریح بین قواعد CSS و المنت‌های HTML به‌ وجود آمده است.

CSS-in-JS به حذف کدهای مرده کمک می‌کند.

اولویت‌بندی غیرقابل پیش‌بینی فایل‌های CSS

اگر یک اپلیکیشن تک صفحه‌ای (SPA) بسازید و باندل css را برای هر صفحه جدا کنید، احتمالا با مشکل مواجه خواهید شد. در این مواقع ترتیب تزریق CSS هایی که وابسته به اکشن‌های کاربران است، باعث بروز استایل‌هایی غیرقابل پیش‌بینی در المنت‌های HTML می‌شود.

تصور کنید که شما ابتدا صفحه‌ی اول را لود می‌کنید و سپس بدون لود کامل سند، به صفحه‌ی دوم می‌روید. از نظر فنی شما ابتدا css اول را لود کرده‌اید و سپس css دوم را. اگر قصد ما این بوده که سلکتورهای css دوم، سلکتورهای css اول را بازنویسی (override) کنند، در این مثال به مشکلی بر نخواهیم خورد.

اما اگر کاربر بعدی ابتدا مستقیما به صفحه‌ی دوم برود و سپس به صفحه‌ی اول برود، css دوم ابتدا لود می‌شود و سپس css اول. این اتفاق باعث می‌شود که css اول، الویت بالاتری نسبت به css دوم داشته باشد. حال این‌جا احتمال بروز مشکلات، زیاد می‌شود و حل آن‌ها نیز زمان‌بر خواهد بود.

برای حل این مشکل باید HTML و CSS های مرتبط به هم را همیشه نزدیک یک‌دیگر داشت تا بدانیم دقیقا چه CSS ای مربوط به هر بخش HTML است.

CSS-in-JS باعث عدم غیرقابل قطعی بودن اولویت فایل‌های CSS می‌شود.

روابط یک به چند

ایده‌ي جداسازی وظایف که باعث جدا شدن HTML از CSS شده است، با این واقعیت که از ابتدا CSS برای جدا بودن از HTML طراحی نشده بوده،‌ مغایرت دارد. CSS یک فرض ضمنی راجع به ساختار HTML دارد. برای مثال flexbox ها فرض می‌کنند که کانتینرهای (containers) پوزیشن دهی، فرزند مستقیم المنتی هستند که فلکس باکس بر روی آن اعمال شده است.

وقتی یک قاعده‌ی ‌CSS بر روی المنت‌های مختلفی از صفحه HTML شما اعمال شود، به سادگی می‌توان این رابطه را یک رابطه‌ی یک به چند دانست؛ پس اگر در آینده بخواهید این قاعده‌ی CSS را تغییر دهید، احتمالا باید تمامی المنت‌های HTML مربوط به آن را نیز تغییر دهید.

CSS-in-JS کمک می‌کند که این رابطه تبدیل به یک رابطه‌ی یک به یک شود. این درحالیست که سعی در حفظ قابلیت اشتراک‌گذاری پراپرتی‌ها را نیز دارد. در حال حاضر تمام API های CSS-in-JS رابطه‌ی یک به یک را اعمال نمی‌کنند؛ زیرا بسیاری از کتابخانه‌ها از استفاده‌ی مجدد از CSS پشتیبانی می‌کنند.

مهم نیست که شما از چه مفهومی استفاده می‌کنید، مهم این است که بهترین روش برای به اشتراک‌گذاری CSS، اشتراک‌گذاری HTML و اطمینان حاصل کردن از رندر شدن CSS های مربوط به آن به شکل خودکار است.

CSS-in-JS به جفت‌سازی HTML و CSS های مربوط به هم، با یک‌دیگر تاکید دارد.

سلکتورهای متعالی

جالب است که بدانید برخی افراد CSS را زیادی قدرتمند می‌دانند و برخی نیز CSS-in-JS را.

واقعیت این است که هر دوی آن‌ها قدرتمند هستند ولی هر کدام در زمینه‌هایی مختلف. سلکتورهای CSS از این جهت بیش از اندازه قدرتمندند که می‌توانند هر المنتی را در هر کجای سند HTML مورد هدف قرار دهند. این مشکل بزرگیست که برای حل آن سعی کرده‌ایم که به عنوان مثال برای هر کامپوننت یا بخش HTML ای، CSS جداگانه بنویسیم.

CSS-in-JS این قدرت بیش از حد را با اسکوپ بندی کردن انتخاب‌گرها یا همان سلکتورها، محدود کرده است. البته که این راه‌حل کاملی برای این مشکل نیست؛ چرا که سلکتورها ممکن است بتوانند به تمام فرزاندان آن کامپوننت دسترسی داشته باشند. این روش جهش رو به جلویی برای نوشتن CSS محدود شده، بوده است. اکثر کتاب‌خانه‌های CSS-in-JS از حالت آبشاری استفاده می‌کنند؛ نه به دلیل امنیت بالای آن بلکه به خاطر کاربردی بودن آن و نبود هیچ جایگزین مناسب برای آن.

از سوی دیگر، جاوااسکریپت زبانی قدرتمندتر از CSS است و آن هم به دلیل رساتر بودن سینتکس آن و وجود امکان پیاده‌سازی الگوهای مختلف توسط آن است. پیاده‌سازی منطق‌های UX بدون وجود شرط‌ها، فانکشن‌ها و متغیرها معمولا بسیار دشوار است. یک سینتکس کاملا اعلانی (declarative) زمانی خوب کار می‌کند که runtime به شکل خیلی مناسبی برای هر use case (مورد استفاده) اختصاصی شده باشد. اما CSS زبانی است که برای انجام وظایف مختلف و گسترده‌ای طراحی شده است.

CSS-in-JS باعث روشن‌گری بیش‌تر توسعه‌دهندگان و تشویق آن‌ها به استفاده از الگوهای بهتر و با قابلیت نگه‌داری آسان‌تر از آبشاری (cascading)، می‌شود.

استایل دهی بر پایه‌ی State (حالت)

یکی از پرقدرت‌ترین ویژگی‌هایی که CSS-in-JS در اختیار ما قرار می‌دهد،‌ قابلیت استایلینگ state-base است. از نظر تکنیکی اغلب این مورد به عنوان یک فانکشن جاوااسکریپت که یک آبجکت که state است را دریافت می‌کند و پراپرتی‌های مناسب css ای آن را بر می‌گرداند. در نتیجه قواعد css کاملا بر اساس استیت‌ها تولید می‌شوند و غیرقابل پیش‌بینی بودن آن‌ها به حداقل می‌رسد. این روش در مقایسه با روش‌ سنتی (که در آن‌ها یک اتریبیوت کالس برای هر المنت HTML داشتیم که شامل نام کلاس‌ها می‌شد)، چند نکته‌ی مثبت دارد:

  1. منطقی که مسئول CSS نهایی است به state دسترسی دارد و می‌تواند در کنار بقیه‌ی استایل‌ها قرار گرفته باشد.
  2. منطقی که HTML را تولید می‌کند، با استفاده از منطق الصاق کلاس‌ها (classes concatenation)، کم‌تر به هم‌ریخته می‌شود.

CSS-in-JS به توسعه‌دهندگان، API ای را ارائه می‌دهد که استایل‌های بر اساس state را با روشی بهتر از کلاس‌‌های شرطی توصیف کنند.

نتیجه‌گیری

من امیدوارم که توانسته‌باشم یک دید کلی از مفهومی که پشت پرده‌ی CSS-in-JS پنهان است را به شما نشان داده‌باشم. قصد من قضاوت راجع به یک تکنولوژی‌ یا ارائه‌ی لیستی از ویژگی‌های آن‌ نبوده است. همچنین قصد نداشتم که به شما القا کنم که CSS-in-JS تنها راه حلیست که در آینده باید از آن استفاده شود. تنها قصد من این بود که شما با مشکلاتی که توسط CSS-in-JS قابل حل هستند آشنا شوید و بدانید که دقیقا به چه علت باید از یک ابزار استفاده کنید یا حتی دقیق‌تر بدانید که چرا از یک ابزار سال‌هاست که استفاده می‌کنید!

منبع

گردآوری و تالیف ابوالفضل باغشاهی
آفلاین
user-avatar

Front-End

دیدگاه‌ها و پرسش‌ها

برای ارسال نظر لازم است ابتدا وارد سایت شوید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید