Data Loaders در Vue Router
دیتا لودرها کار با دادههای ناهمگام (مثل واکشی API) را در Vue Router ساده و یکپارچه میکنند. با آنها میتوانید به شکل مؤثر دادهها را قبل از رندر کامپوننت آماده کنید و همزمان از کتابخانههایی مثل Pinia Colada یا Apollo بهره ببرید.
لودر بهطور خودکار هنگام تغییر مسیر اجرا میشود و دادهها قبل از رندر کامپوننت آماده هستند. همچنین میتوانید همان لودر را در چند کامپوننت دوباره استفاده کنید و دادهها بهصورت اشتراکی مدیریت شوند.
🔗https://uvr.esm.is/data-loaders/
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
دیتا لودرها کار با دادههای ناهمگام (مثل واکشی API) را در Vue Router ساده و یکپارچه میکنند. با آنها میتوانید به شکل مؤثر دادهها را قبل از رندر کامپوننت آماده کنید و همزمان از کتابخانههایی مثل Pinia Colada یا Apollo بهره ببرید.
// src/pages/users/[id].vue
<script lang="ts">
import { defineBasicLoader } from 'unplugin-vue-router/data-loaders/basic'
import { getUserById } from '../api'
export const useUserData = defineBasicLoader('/users/[id]', async (route) => {
return getUserById(route.params.id)
})
</script>
لودر بهطور خودکار هنگام تغییر مسیر اجرا میشود و دادهها قبل از رندر کامپوننت آماده هستند. همچنین میتوانید همان لودر را در چند کامپوننت دوباره استفاده کنید و دادهها بهصورت اشتراکی مدیریت شوند.
🔗https://uvr.esm.is/data-loaders/
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
🧐 چرا نباید منطق کسبوکار را در کامپوننتهای UI نوشت
اغلب یک الگوی تکراری را میبینم: یک کامپوننت کوچک با چند شرط ساده که پس از چند قابلیت کوچک به موجودی عظیم تبدیل میشود 🤯 — هم رابط را نمایش میدهد، هم تصمیم میگیرد، هم با API ارتباط برقرار میکند و کمی «جادو» هم انجام میدهد. در نتیجه، بهجای یک UI تمیز، مجموعهای از منطق پنهان خواهیم داشت که نگهداری آن بعداً بسیار دشوار است.
---
🧩 چگونه این اتفاق میافتد؟
در ابتدا همه چیز منطقی به نظر میرسد: زمانبندی محدود، فیتچر کوچک — سادهتر است که پردازش را مستقیم در کامپوننت قرار دهیم. سپس «بعداً بیرون میکشیم» هیچگاه اتفاق نمیافتد و کامپوننت رشد میکند:
➖ فراخوانی API و تبدیل پاسخها
➖ تصمیمگیری درباره گامهای فرایند
➖ شامل شاخههای قوانین کسبوکار
➖ منبعی برای کپیپیست هنگام استفاده مجدد
---
🧩 مشکل چیست؟
☑️ ادغام نقشها — کامپوننت باید رابط را نمایش دهد، نه مدیریت فرایند را. وقتی UI شروع به «تفکر» میکند، سیستم بسیار شکننده میشود.
☑️ از بین رفتن قابلیت استفاده مجدد — نیاز به همان منطق در جای دیگر باعث کپیپیست، ناهماهنگی و باگها میشود.
☑️ ریسک تغییرات — تغییر نمایش، فرایند را خراب میکند؛ تغییر قانون، UI را بهطور غیرمنتظره میشکند.
☑️ پیچیدگی تست
---
🧩 چرا با این حال منطق را در کامپوننتها مینویسند؟
⏺️ سریعتر در شروع پروژه
⏺️ «برای یک قابلیت کوچک» بهنظر میرسد نیازی به جداکردن نیست
⏺️ تیم خسته است یا استاندارد معماری وجود ندارد
اما معمولاً همان «قابلیت کوچک» رشد میکند و بدهی فنی انباشته میشود 😥
---
🧩 چگونه بهتر کد را سازماندهی کنیم؟
✔️ کامپوننتهای UI — مسئول نمایش و واکنش به تعاملات کاربر
✔️ منطق کسبوکار — در سرویسها، استورها یا ماژولها
✔️ یکپارچهسازی — کامپوننتها سرویسها یا اکشنها را فراخوانی کرده و دادهها/وضعیتهای آماده را دریافت میکنند
مثال جریان داده:
---
🧩 چگونه خود را بررسی کنیم؟
اگر قالب (template) را از کامپوننت حذف کنیم و کد باقیمانده هنوز معنای مستقلی داشته باشد — یعنی منطق کسبوکار به کامپوننت نفوذ کرده است. این نشانهای برای تفکر درباره ریفکتورینگ است.
💡 نتیجهگیری:
UI = لایه نمایش
منطق کسبوکار = لایه تصمیمگیری
ادغام آنها ممکن است در ابتدا راحت باشد، اما در بلندمدت تقریباً همیشه منجر به افزایش پیچیدگی، شکنندگی سیستم و بدهی فنی میشود 🙈
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
اغلب یک الگوی تکراری را میبینم: یک کامپوننت کوچک با چند شرط ساده که پس از چند قابلیت کوچک به موجودی عظیم تبدیل میشود 🤯 — هم رابط را نمایش میدهد، هم تصمیم میگیرد، هم با API ارتباط برقرار میکند و کمی «جادو» هم انجام میدهد. در نتیجه، بهجای یک UI تمیز، مجموعهای از منطق پنهان خواهیم داشت که نگهداری آن بعداً بسیار دشوار است.
---
🧩 چگونه این اتفاق میافتد؟
در ابتدا همه چیز منطقی به نظر میرسد: زمانبندی محدود، فیتچر کوچک — سادهتر است که پردازش را مستقیم در کامپوننت قرار دهیم. سپس «بعداً بیرون میکشیم» هیچگاه اتفاق نمیافتد و کامپوننت رشد میکند:
➖ فراخوانی API و تبدیل پاسخها
➖ تصمیمگیری درباره گامهای فرایند
➖ شامل شاخههای قوانین کسبوکار
➖ منبعی برای کپیپیست هنگام استفاده مجدد
---
🧩 مشکل چیست؟
☑️ ادغام نقشها — کامپوننت باید رابط را نمایش دهد، نه مدیریت فرایند را. وقتی UI شروع به «تفکر» میکند، سیستم بسیار شکننده میشود.
☑️ از بین رفتن قابلیت استفاده مجدد — نیاز به همان منطق در جای دیگر باعث کپیپیست، ناهماهنگی و باگها میشود.
☑️ ریسک تغییرات — تغییر نمایش، فرایند را خراب میکند؛ تغییر قانون، UI را بهطور غیرمنتظره میشکند.
☑️ پیچیدگی تست
---
🧩 چرا با این حال منطق را در کامپوننتها مینویسند؟
⏺️ سریعتر در شروع پروژه
⏺️ «برای یک قابلیت کوچک» بهنظر میرسد نیازی به جداکردن نیست
⏺️ تیم خسته است یا استاندارد معماری وجود ندارد
اما معمولاً همان «قابلیت کوچک» رشد میکند و بدهی فنی انباشته میشود 😥
---
🧩 چگونه بهتر کد را سازماندهی کنیم؟
✔️ کامپوننتهای UI — مسئول نمایش و واکنش به تعاملات کاربر
✔️ منطق کسبوکار — در سرویسها، استورها یا ماژولها
✔️ یکپارچهسازی — کامپوننتها سرویسها یا اکشنها را فراخوانی کرده و دادهها/وضعیتهای آماده را دریافت میکنند
مثال جریان داده:
کامپوننت → اکشن / use-case → سرویس → مخزن/API → سرویس → use-case → کامپوننت (وضعیت بهروز شده)---
🧩 چگونه خود را بررسی کنیم؟
اگر قالب (template) را از کامپوننت حذف کنیم و کد باقیمانده هنوز معنای مستقلی داشته باشد — یعنی منطق کسبوکار به کامپوننت نفوذ کرده است. این نشانهای برای تفکر درباره ریفکتورینگ است.
💡 نتیجهگیری:
UI = لایه نمایش
منطق کسبوکار = لایه تصمیمگیری
ادغام آنها ممکن است در ابتدا راحت باشد، اما در بلندمدت تقریباً همیشه منجر به افزایش پیچیدگی، شکنندگی سیستم و بدهی فنی میشود 🙈
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
❤3👍1
خاصبودگی در CSS (معادل Specificity) به معنای «وزن» یا «قدرت» یک سلکتور است.
هرچه خاصبودگی یک سلکتور بیشتر باشد، در صورت تعارض با سایر قوانین، احتمال برنده شدن آن بیشتر است.
بهصورت خلاصه، ترتیب قدرت به این شکل است:
استایل درونخطی > شناسه (ID) > کلاس و شبهکلاس > عناصر و شبهعناصر
خاصبودگی معمولاً به شکل یک چهارتایی نمایش داده میشود، مانند:
(صفر، صفر، صفر، یک)
این اعداد بهترتیب نشاندهندهٔ تعداد استایلهای درونخطی، شناسهها، کلاسها/شبهکلاسها و عناصر هستند.
بنابراین «خاصبودگی» یعنی میزان اولویت یک قانون CSS هنگام بروز تداخل.
#️⃣#tip #css
👥@IR_javascript_group
🆔@IR_javascript
هرچه خاصبودگی یک سلکتور بیشتر باشد، در صورت تعارض با سایر قوانین، احتمال برنده شدن آن بیشتر است.
بهصورت خلاصه، ترتیب قدرت به این شکل است:
استایل درونخطی > شناسه (ID) > کلاس و شبهکلاس > عناصر و شبهعناصر
خاصبودگی معمولاً به شکل یک چهارتایی نمایش داده میشود، مانند:
(صفر، صفر، صفر، یک)
این اعداد بهترتیب نشاندهندهٔ تعداد استایلهای درونخطی، شناسهها، کلاسها/شبهکلاسها و عناصر هستند.
بنابراین «خاصبودگی» یعنی میزان اولویت یک قانون CSS هنگام بروز تداخل.
#myId#myId#myId span {
/* 0-3-0-1 */
}
.myClass.myClass.myClass span {
/* 0-0-3-1 */
}#️⃣#tip #css
👥@IR_javascript_group
🆔@IR_javascript
«میخواهم برای تگ p داخل header استایل تعریف کنم، اما نمیخواهم خاصبودگی را افزایش دهم» 😎
گاهی سادهترین کار — تعیین استایل برای p داخل header — به رقابتی بیپایان میان سلکتورها تبدیل میشود. شما مینویسید header p { … }، فرد دیگری .text { … } اضافه میکند، شما سلکتورها را پیچیدهتر میکنید و پروژه بهآرامی در وزنهای «جهنمی» CSS فرو میرود. 😞 خوشبختانه یک راهکار ظریف و بسیار کاربردی وجود دارد: استفاده از :where()
🧩 :where() چه میکند:
:where() یک شبهکلاس در CSS است که فهرستی از سلکتورها را میپذیرد و بدون توجه به محتوای درون آن، همواره خاصبودگی صفر دارد.
💡 یعنی حتی اگر درون آن #id .class element قرار دهید، «وزن» نهایی همچنان صفر خواهد بود — برای کل سازهی :where().
🔍 یادآوری کوتاهی درباره خاصبودگی:
خاصبودگی — وزن سلکتور است. بهصورت ساده: استایل درونخطی بیشتر از شناسه، شناسه بیشتر از کلاس یا شبهکلاس، و آنها بیشتر از عناصر یا شبهعناصر وزن دارند.
الگوریتمهای CSS هنگام بروز تعارض از آن برای تعیین برنده استفاده میکنند:
⏺️ p → خاصبودگی (صفر، صفر، صفر، یک)
⏺️ :where(header) p → :where(header) صفر میدهد، p یک میدهد → در مجموع (صفر، صفر، صفر، یک)
⏺️ header p → header بهعلاوه p → (صفر، صفر، صفر، دو) — این سلکتور از قبل سنگینتر است
➡️ p و :where(header) p خاصبودگی یکسانی دارند — در صورت تعارض، قانونی که دیرتر تعریف شده باشد برنده خواهد شد.
در نتیجه، از :where() برای تعریف قواعد پیشفرض وابسته به کانتکست، بدون افزایش خاصبودگی استفاده کنید: ریست یا نرمالسازی، تایپوگرافی پایه، پیشفرضها درون کانتینرهای لایهبندی. این کار به شما اجازه میدهد کانتکست موردنظر را تعریف کنید، بدون آنکه آبشار را بر هم بزنید یا سلکتورها را سنگینتر کنید. 👍
#️⃣#tip #css
👥@IR_javascript_group
🆔@IR_javascript
گاهی سادهترین کار — تعیین استایل برای p داخل header — به رقابتی بیپایان میان سلکتورها تبدیل میشود. شما مینویسید header p { … }، فرد دیگری .text { … } اضافه میکند، شما سلکتورها را پیچیدهتر میکنید و پروژه بهآرامی در وزنهای «جهنمی» CSS فرو میرود. 😞 خوشبختانه یک راهکار ظریف و بسیار کاربردی وجود دارد: استفاده از :where()
🧩 :where() چه میکند:
:where() یک شبهکلاس در CSS است که فهرستی از سلکتورها را میپذیرد و بدون توجه به محتوای درون آن، همواره خاصبودگی صفر دارد.
💡 یعنی حتی اگر درون آن #id .class element قرار دهید، «وزن» نهایی همچنان صفر خواهد بود — برای کل سازهی :where().
🔍 یادآوری کوتاهی درباره خاصبودگی:
خاصبودگی — وزن سلکتور است. بهصورت ساده: استایل درونخطی بیشتر از شناسه، شناسه بیشتر از کلاس یا شبهکلاس، و آنها بیشتر از عناصر یا شبهعناصر وزن دارند.
الگوریتمهای CSS هنگام بروز تعارض از آن برای تعیین برنده استفاده میکنند:
⏺️ p → خاصبودگی (صفر، صفر، صفر، یک)
⏺️ :where(header) p → :where(header) صفر میدهد، p یک میدهد → در مجموع (صفر، صفر، صفر، یک)
⏺️ header p → header بهعلاوه p → (صفر، صفر، صفر، دو) — این سلکتور از قبل سنگینتر است
➡️ p و :where(header) p خاصبودگی یکسانی دارند — در صورت تعارض، قانونی که دیرتر تعریف شده باشد برنده خواهد شد.
در نتیجه، از :where() برای تعریف قواعد پیشفرض وابسته به کانتکست، بدون افزایش خاصبودگی استفاده کنید: ریست یا نرمالسازی، تایپوگرافی پایه، پیشفرضها درون کانتینرهای لایهبندی. این کار به شما اجازه میدهد کانتکست موردنظر را تعریف کنید، بدون آنکه آبشار را بر هم بزنید یا سلکتورها را سنگینتر کنید. 👍
#️⃣#tip #css
👥@IR_javascript_group
🆔@IR_javascript
آشنایی با معماری مبتنی بر Feature 🤩
معماری Feature-based کد را بر اساس قابلیتهای کسبوکاری سازماندهی میکند، نه لایههای فنی. به همین دلیل تغییرات موضعی میمانند، تیمها مستقلتر کار میکنند و پایگاه کد همزمان با رشد محصول، قابلفهم باقی میماند.
این معماری بر ایدهٔ بومیسازی کانتکست بنا شده است: هر فیچر بهصورت یک ماژول مستقل طراحی میشود که تمام اجزای موردنیاز برای عملکرد خود را درونش دارد — رابط کاربری، وضعیت، منطق کسبوکار، ارتباط با API و تستها. در نتیجه، توسعهدهنده برای اعمال یک تغییر لازم نیست میان دهها پوشه جابهجا شود، زیرا تمام کد مرتبط در یک محل قرار دارد و بهصورت یک واحد منسجم خوانده میشود. 👍
این رویکرد بهویژه همراه با رشد تیم بهخوبی مقیاسپذیر است. زمانی که فیچرها ایزوله باشند، چند توسعهدهنده یا چند تیم میتوانند بهطور موازی روی بخشهای مختلف محصول کار کنند، بیآنکه مزاحم یکدیگر شوند. همچنین با گذر زمان میتوان هر فیچر را بدون ریفکتورینگهای دردناک به یک پکیج یا سرویس مستقل تبدیل کرد. پیامد مثبت دیگر، سادهتر شدن تستنویسی و ریفکتورینگ است؛ زیرا مرز مسئولیتها بهروشنی مشخص است و رفتار هر فیچر را میتوان بهصورت ایزوله بررسی کرد.
❗️❗️❗️❗️
با این حال، معماری Feature-based بهخودیخود کار نمیکند — به قواعد مشخص نیاز دارد. مهمترین اصل، تعریف مرزهای شفاف و قراردادهای عمومی است. فیچرها نباید مستقیماً به جزئیات داخلی یکدیگر ایمپورت داشته باشند و تعامل میان آنها باید صرفاً از طریق API عمومیِ بهصراحت تعریفشده انجام شود. کد مشترک به بخش shared منتقل میشود، اما این بخش باید محدود و پایدار بماند؛ اگر shared بدون کنترل رشد کند، بهسرعت به یک مونولیت پنهان تبدیل میشود و ایزولاسیون فیچرها را از بین میبرد.
👩💻 نمونهای حداقلی از ساختار یک فیچر در فرانتاند:
فایل index.ts نقشی کلیدی دارد: بهصورت شفاف مشخص میکند که چه بخشهایی از فیچر مجاز به استفاده از بیرون هستند و سایر قسمتها بهعنوان جزئیات داخلی پیادهسازی باقی میمانند. همچنین فایل README با توضیح کوتاه درباره هدف فیچر و API عمومی آن، ورود توسعهدهندگان جدید را آسانتر میکند و به حفظ مرزهای معماری کمک مینماید. 😁
‼️ درک محدودیتهای این رویکرد نیز ضروری است. برای پروژههای کوچک، معماری Feature-based ممکن است بیشازحد پیچیده باشد، زیرا به انضباط، مستندسازی و اتوماسیون نیاز دارد. اما برای محصولاتی که در حال رشد هستند، به ابزاری برای مدیریت پیچیدگی تبدیل میشود: تغییرات موضعی و قابلپیشبینی میشوند، پایگاه کد خواناتر میشود و تیمها استقلال بیشتری پیدا میکنند.
در نهایت، معماری Feature-based صرفاً درباره پوشهها نیست، بلکه درباره تفکر مبتنی بر فیچر، مرزهای شفاف و کنترل وابستگیهاست. با تعریف قواعد درست و اتوماسیون مناسب، این رویکرد به سیستم اجازه میدهد رشد کند، بدون آنکه شفافیت و سرعت توسعه را از دست بدهد. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
معماری Feature-based کد را بر اساس قابلیتهای کسبوکاری سازماندهی میکند، نه لایههای فنی. به همین دلیل تغییرات موضعی میمانند، تیمها مستقلتر کار میکنند و پایگاه کد همزمان با رشد محصول، قابلفهم باقی میماند.
این معماری بر ایدهٔ بومیسازی کانتکست بنا شده است: هر فیچر بهصورت یک ماژول مستقل طراحی میشود که تمام اجزای موردنیاز برای عملکرد خود را درونش دارد — رابط کاربری، وضعیت، منطق کسبوکار، ارتباط با API و تستها. در نتیجه، توسعهدهنده برای اعمال یک تغییر لازم نیست میان دهها پوشه جابهجا شود، زیرا تمام کد مرتبط در یک محل قرار دارد و بهصورت یک واحد منسجم خوانده میشود. 👍
این رویکرد بهویژه همراه با رشد تیم بهخوبی مقیاسپذیر است. زمانی که فیچرها ایزوله باشند، چند توسعهدهنده یا چند تیم میتوانند بهطور موازی روی بخشهای مختلف محصول کار کنند، بیآنکه مزاحم یکدیگر شوند. همچنین با گذر زمان میتوان هر فیچر را بدون ریفکتورینگهای دردناک به یک پکیج یا سرویس مستقل تبدیل کرد. پیامد مثبت دیگر، سادهتر شدن تستنویسی و ریفکتورینگ است؛ زیرا مرز مسئولیتها بهروشنی مشخص است و رفتار هر فیچر را میتوان بهصورت ایزوله بررسی کرد.
❗️❗️❗️❗️
با این حال، معماری Feature-based بهخودیخود کار نمیکند — به قواعد مشخص نیاز دارد. مهمترین اصل، تعریف مرزهای شفاف و قراردادهای عمومی است. فیچرها نباید مستقیماً به جزئیات داخلی یکدیگر ایمپورت داشته باشند و تعامل میان آنها باید صرفاً از طریق API عمومیِ بهصراحت تعریفشده انجام شود. کد مشترک به بخش shared منتقل میشود، اما این بخش باید محدود و پایدار بماند؛ اگر shared بدون کنترل رشد کند، بهسرعت به یک مونولیت پنهان تبدیل میشود و ایزولاسیون فیچرها را از بین میبرد.
👩💻 نمونهای حداقلی از ساختار یک فیچر در فرانتاند:
src/
features/
Cart/
components/
hooks/
api.ts
index.ts // API
README.md
shared/
ui/
utils/
فایل index.ts نقشی کلیدی دارد: بهصورت شفاف مشخص میکند که چه بخشهایی از فیچر مجاز به استفاده از بیرون هستند و سایر قسمتها بهعنوان جزئیات داخلی پیادهسازی باقی میمانند. همچنین فایل README با توضیح کوتاه درباره هدف فیچر و API عمومی آن، ورود توسعهدهندگان جدید را آسانتر میکند و به حفظ مرزهای معماری کمک مینماید. 😁
‼️ درک محدودیتهای این رویکرد نیز ضروری است. برای پروژههای کوچک، معماری Feature-based ممکن است بیشازحد پیچیده باشد، زیرا به انضباط، مستندسازی و اتوماسیون نیاز دارد. اما برای محصولاتی که در حال رشد هستند، به ابزاری برای مدیریت پیچیدگی تبدیل میشود: تغییرات موضعی و قابلپیشبینی میشوند، پایگاه کد خواناتر میشود و تیمها استقلال بیشتری پیدا میکنند.
در نهایت، معماری Feature-based صرفاً درباره پوشهها نیست، بلکه درباره تفکر مبتنی بر فیچر، مرزهای شفاف و کنترل وابستگیهاست. با تعریف قواعد درست و اتوماسیون مناسب، این رویکرد به سیستم اجازه میدهد رشد کند، بدون آنکه شفافیت و سرعت توسعه را از دست بدهد. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
👍3
آشنایی با CSR (Client-Side Rendering) 👨🏫
Client-Side Rendering یا CSR رویکردی در رندرینگ است که در آن سرور تنها یک اسکلت حداقلی HTML ارسال میکند و کل رابط کاربری و منطق اصلی در مرورگر کاربر و با استفاده از JavaScript ساخته میشود. مرورگر اسکریپتها را دانلود میکند، کد را اجرا میکند و ساختار DOM را در سمت کاربر شکل میدهد.
مهمترین مزیت CSR، تعاملپذیری پس از مقداردهی اولیه است. رابط کاربری روان و پاسخگو عمل میکند: پیادهسازی ویجتهای پیچیده، شخصیسازی پویا و ناوبری بدون بارگذاری مجدد صفحه بهسادگی امکانپذیر است.
با این حال، این انعطاف هزینه دارد — بار اصلی ساخت رابط کاربری بر دوش دستگاه کاربر قرار میگیرد. 😐
تا زمانی که مرورگر در حال دانلود، تجزیه و اجرای JavaScript است، صفحه ممکن است تنها بخشی از محتوا را نمایش دهد یا حتی کاملاً «سفید» باقی بماند. حتی اگر رابط کاربری قابل مشاهده باشد، اغلب هنوز تعاملپذیری کامل ندارد: کلیکها و ورودیها با تأخیر پردازش میشوند. این مسئله بهویژه در دستگاههای ضعیفتر و شبکههای کندتر محسوس است. ☹️
همچنین باید تأثیر CSR بر ایندکسگذاری و پیشنمایش را در نظر گرفت. اگر بخش قابلتوجهی از محتوا تنها در سمت کاربر تولید شود، موتورهای جستوجو و شبکههای اجتماعی برای تحلیل صحیح صفحه و تولید اسنیپتها با دشواری بیشتری مواجه میشوند.
در نتیجه، CSR بیش از همه برای اپلیکیشنهای با تعاملپذیری بالا مناسب است — مانند داشبوردها، پنلهای مدیریتی و سرویسهای داخلی که کاربر زمان زیادی در آنها سپری میکند و پس از بارگذاری اولیه، روانی عملکرد برایش اهمیت دارد. 👍
برای صفحات محتوایی و بازاریابی معمولاً رویکردهای دیگری انتخاب میشوند که در ادامه به آنها خواهیم پرداخت. 😉
برای کاهش معایب CSR معمولاً از این راهکارها استفاده میشود:
⏺️ تقسیم باندلها،
⏺️ بارگذاری تنبل کد،
⏺️ و البته مدیریت دقیق حجم و زمان اجرای JavaScript.
چنین بهینهسازیهایی کمک میکند انعطافپذیری رندرینگ سمت کاربر حفظ شود، بدون آنکه تجربه کاربری قربانی شود.
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
Client-Side Rendering یا CSR رویکردی در رندرینگ است که در آن سرور تنها یک اسکلت حداقلی HTML ارسال میکند و کل رابط کاربری و منطق اصلی در مرورگر کاربر و با استفاده از JavaScript ساخته میشود. مرورگر اسکریپتها را دانلود میکند، کد را اجرا میکند و ساختار DOM را در سمت کاربر شکل میدهد.
مهمترین مزیت CSR، تعاملپذیری پس از مقداردهی اولیه است. رابط کاربری روان و پاسخگو عمل میکند: پیادهسازی ویجتهای پیچیده، شخصیسازی پویا و ناوبری بدون بارگذاری مجدد صفحه بهسادگی امکانپذیر است.
با این حال، این انعطاف هزینه دارد — بار اصلی ساخت رابط کاربری بر دوش دستگاه کاربر قرار میگیرد. 😐
تا زمانی که مرورگر در حال دانلود، تجزیه و اجرای JavaScript است، صفحه ممکن است تنها بخشی از محتوا را نمایش دهد یا حتی کاملاً «سفید» باقی بماند. حتی اگر رابط کاربری قابل مشاهده باشد، اغلب هنوز تعاملپذیری کامل ندارد: کلیکها و ورودیها با تأخیر پردازش میشوند. این مسئله بهویژه در دستگاههای ضعیفتر و شبکههای کندتر محسوس است. ☹️
همچنین باید تأثیر CSR بر ایندکسگذاری و پیشنمایش را در نظر گرفت. اگر بخش قابلتوجهی از محتوا تنها در سمت کاربر تولید شود، موتورهای جستوجو و شبکههای اجتماعی برای تحلیل صحیح صفحه و تولید اسنیپتها با دشواری بیشتری مواجه میشوند.
در نتیجه، CSR بیش از همه برای اپلیکیشنهای با تعاملپذیری بالا مناسب است — مانند داشبوردها، پنلهای مدیریتی و سرویسهای داخلی که کاربر زمان زیادی در آنها سپری میکند و پس از بارگذاری اولیه، روانی عملکرد برایش اهمیت دارد. 👍
برای صفحات محتوایی و بازاریابی معمولاً رویکردهای دیگری انتخاب میشوند که در ادامه به آنها خواهیم پرداخت. 😉
برای کاهش معایب CSR معمولاً از این راهکارها استفاده میشود:
⏺️ تقسیم باندلها،
⏺️ بارگذاری تنبل کد،
⏺️ و البته مدیریت دقیق حجم و زمان اجرای JavaScript.
چنین بهینهسازیهایی کمک میکند انعطافپذیری رندرینگ سمت کاربر حفظ شود، بدون آنکه تجربه کاربری قربانی شود.
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
👍1
INP: پاسخگویی صفحه چگونه اندازهگیری میشود 🤨
گاهی یک سایت سریع بارگذاری میشود و محتوا تقریباً بلافاصله ظاهر میشود، اما استفاده از آن چندان لذتبخش نیست. کلیک با تأخیر اجرا میشود، فیلد ورودی دیر واکنش نشان میدهد و رابط کاربری انگار «در حال فکر کردن» است. از نظر فنی همهچیز بارگذاری شده، اما حس پاسخگویی چندان مطلوب نیست. 😥
چنین وضعیتهایی را INP (Interaction to Next Paint) توصیف میکند.
INP معیاری است که نشان میدهد رابط کاربری با چه سرعتی به اعمال کاربر واکنش نشان میدهد. این معیار فاصله زمانی بین یک اقدام کاربر (کلیک، لمس یا ورود داده) و لحظهای را اندازهگیری میکند که مرورگر رندر فریم بعدی — که نتیجه آن اقدام را نمایش میدهد — به پایان میرساند. به بیان ساده، مدتزمان بین عمل کاربر و بازخورد بصری.
نکته مهم این است که INP یک تعامل خاص را اندازهگیری نمیکند، بلکه صدک هفتاد و پنجم تمام تعاملات در طول چرخه حیات صفحه را در نظر میگیرد. یعنی این معیار نشان میدهد کاربر در اغلب مواقع چه تجربهای دارد، نه بهترین یا بدترین نمونه منفرد.
از سال دو هزار و بیست و چهار، INP به مجموعه Core Web Vitals اضافه شده و جایگزین FID شده است. دلیل آن روشن است: FID تنها اولین تعامل را در نظر میگرفت، در حالیکه INP همه تعاملات کاربر را پوشش میدهد و تصویر کاملتری از پاسخگویی واقعی صفحه ارائه میدهد. ‼️
در عمل، INP بالا تقریباً همیشه ناشی از اشغال بودن رشته اصلی مرورگر است. اگر کاربر با رابط تعامل کند، اما مرورگر مشغول اجرای وظایف طولانی باشد، پردازش رویدادها به تعویق میافتد و بازخورد بصری با تأخیر نمایش داده میشود.
عوامل رایج در تضعیف INP عبارتاند از:
⏺️ وظایف طولانی در رشته اصلی؛
⏺️ هندلرهای رویداد پیچیده؛
⏺️ محاسبات همزمان هنگام کلیک یا ورود داده؛
⏺️ حجم زیاد JavaScript شخص ثالث؛
⏺️ بهروزرسانیهای سنگین DOM.
بهبود INP معمولاً به راهکارهای پیچیده نیاز ندارد — در بیشتر موارد کافی است وظایف طولانی را کوتاه و خرد کنید، منطق هندلرهای رویداد را سادهتر سازید، محاسبات سنگین را به Web Worker منتقل کنید، بارگذاری کدهای غیرضروری را به تعویق بیندازید و تأثیر اسکریپتهای شخص ثالث را کاهش دهید. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
گاهی یک سایت سریع بارگذاری میشود و محتوا تقریباً بلافاصله ظاهر میشود، اما استفاده از آن چندان لذتبخش نیست. کلیک با تأخیر اجرا میشود، فیلد ورودی دیر واکنش نشان میدهد و رابط کاربری انگار «در حال فکر کردن» است. از نظر فنی همهچیز بارگذاری شده، اما حس پاسخگویی چندان مطلوب نیست. 😥
چنین وضعیتهایی را INP (Interaction to Next Paint) توصیف میکند.
INP معیاری است که نشان میدهد رابط کاربری با چه سرعتی به اعمال کاربر واکنش نشان میدهد. این معیار فاصله زمانی بین یک اقدام کاربر (کلیک، لمس یا ورود داده) و لحظهای را اندازهگیری میکند که مرورگر رندر فریم بعدی — که نتیجه آن اقدام را نمایش میدهد — به پایان میرساند. به بیان ساده، مدتزمان بین عمل کاربر و بازخورد بصری.
نکته مهم این است که INP یک تعامل خاص را اندازهگیری نمیکند، بلکه صدک هفتاد و پنجم تمام تعاملات در طول چرخه حیات صفحه را در نظر میگیرد. یعنی این معیار نشان میدهد کاربر در اغلب مواقع چه تجربهای دارد، نه بهترین یا بدترین نمونه منفرد.
از سال دو هزار و بیست و چهار، INP به مجموعه Core Web Vitals اضافه شده و جایگزین FID شده است. دلیل آن روشن است: FID تنها اولین تعامل را در نظر میگرفت، در حالیکه INP همه تعاملات کاربر را پوشش میدهد و تصویر کاملتری از پاسخگویی واقعی صفحه ارائه میدهد. ‼️
در عمل، INP بالا تقریباً همیشه ناشی از اشغال بودن رشته اصلی مرورگر است. اگر کاربر با رابط تعامل کند، اما مرورگر مشغول اجرای وظایف طولانی باشد، پردازش رویدادها به تعویق میافتد و بازخورد بصری با تأخیر نمایش داده میشود.
عوامل رایج در تضعیف INP عبارتاند از:
⏺️ وظایف طولانی در رشته اصلی؛
⏺️ هندلرهای رویداد پیچیده؛
⏺️ محاسبات همزمان هنگام کلیک یا ورود داده؛
⏺️ حجم زیاد JavaScript شخص ثالث؛
⏺️ بهروزرسانیهای سنگین DOM.
بهبود INP معمولاً به راهکارهای پیچیده نیاز ندارد — در بیشتر موارد کافی است وظایف طولانی را کوتاه و خرد کنید، منطق هندلرهای رویداد را سادهتر سازید، محاسبات سنگین را به Web Worker منتقل کنید، بارگذاری کدهای غیرضروری را به تعویق بیندازید و تأثیر اسکریپتهای شخص ثالث را کاهش دهید. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
رابط برنامهنویسی «Compression Streams» — فشردهسازی بومی gzip/deflate مستقیم در مرورگر 🤔
اخیراً دوباره یاد Compression Streams API افتادم — و این دقیقاً از همان مواردی است که پلتفرم خودش از قبل قابلیتی را دارد که قبلاً برایش مجبور بودیم یک کتابخانهٔ جداگانه اضافه کنیم. 🤭
این یک API داخلی و جریانی برای فشردهسازی و بازفشردهسازی دادهها در قالبهای gzip و deflate است. این قابلیت بر پایهٔ Streams کار میکند؛ یعنی دادهها بهصورت بخشبخش پردازش میشوند، بدون اینکه لازم باشد کل محتوا یکجا در حافظه بارگذاری شود.
اگر بخواهیم خلاصه بگوییم: حالا میتوان دادهها را مستقیم در سمت کلاینت فشرده و بازفشرده کرد — بدون هیچ وابستگی خارجی.
🧩 چه نکاتی را باید به خاطر سپرد؟
✔️ قالبها: gzip و deflate
✔️ کلاسها: CompressionStream و DecompressionStream
✔️ بر پایهٔ Streams کار میکند — دادهها بهصورت قطعهای پردازش میشوند، بدون بارگذاری کامل در حافظه
✔️ از طریق pipeThrough() استفاده میشود — و بهراحتی در هر زنجیرهٔ استریم جا میگیرد
ReadableStream → CompressionStream → WritableStream — و همهٔ اینها بهصورت «در لحظه» انجام میشود.
🧩 مثال: فشردهسازی با gzip
🧩 کجا کاربرد دارد؟
⏺️ فشردهسازی دادهها پیش از ارسال به سرور؛
⏺️ ذخیرهسازی یا پردازش فایلهای بزرگ JSON / CSV / لاگها؛
⏺️ کاهش وابستگی به کتابخانهها — بهویژه هنگام کار با فایلهای حجیم
از معایب آن: فقط gzip و deflate پشتیبانی میشوند (نه آرشیوهای ZIP)، امکان تنظیم سطح فشردهسازی وجود ندارد، و در مرورگرهای قدیمی ممکن است این API در دسترس نباشد. 😕
در نهایت، این یک قابلیت ساده و بیسروصدای پلتفرم است — و برای بیشتر کارهای معمول فشردهسازی کاملاً کافی است. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
اخیراً دوباره یاد Compression Streams API افتادم — و این دقیقاً از همان مواردی است که پلتفرم خودش از قبل قابلیتی را دارد که قبلاً برایش مجبور بودیم یک کتابخانهٔ جداگانه اضافه کنیم. 🤭
این یک API داخلی و جریانی برای فشردهسازی و بازفشردهسازی دادهها در قالبهای gzip و deflate است. این قابلیت بر پایهٔ Streams کار میکند؛ یعنی دادهها بهصورت بخشبخش پردازش میشوند، بدون اینکه لازم باشد کل محتوا یکجا در حافظه بارگذاری شود.
اگر بخواهیم خلاصه بگوییم: حالا میتوان دادهها را مستقیم در سمت کلاینت فشرده و بازفشرده کرد — بدون هیچ وابستگی خارجی.
🧩 چه نکاتی را باید به خاطر سپرد؟
✔️ قالبها: gzip و deflate
✔️ کلاسها: CompressionStream و DecompressionStream
✔️ بر پایهٔ Streams کار میکند — دادهها بهصورت قطعهای پردازش میشوند، بدون بارگذاری کامل در حافظه
✔️ از طریق pipeThrough() استفاده میشود — و بهراحتی در هر زنجیرهٔ استریم جا میگیرد
ReadableStream → CompressionStream → WritableStream — و همهٔ اینها بهصورت «در لحظه» انجام میشود.
🧩 مثال: فشردهسازی با gzip
async function compress(text) {
const encoder = new TextEncoder();
const stream = new Blob([encoder.encode(text)]).stream();
const compressed = stream
.pipeThrough(new CompressionStream("gzip"));
return await new Response(compressed).blob();
}🧩 کجا کاربرد دارد؟
⏺️ فشردهسازی دادهها پیش از ارسال به سرور؛
⏺️ ذخیرهسازی یا پردازش فایلهای بزرگ JSON / CSV / لاگها؛
⏺️ کاهش وابستگی به کتابخانهها — بهویژه هنگام کار با فایلهای حجیم
از معایب آن: فقط gzip و deflate پشتیبانی میشوند (نه آرشیوهای ZIP)، امکان تنظیم سطح فشردهسازی وجود ندارد، و در مرورگرهای قدیمی ممکن است این API در دسترس نباشد. 😕
در نهایت، این یک قابلیت ساده و بیسروصدای پلتفرم است — و برای بیشتر کارهای معمول فشردهسازی کاملاً کافی است. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
چطور محتوای «پنهان، اما قابلجستوجو» بسازیم؟ 🔍
تصور کنید یک صفحهٔ طولانی با بخش پرسشهای متداول، آکاردئونها و قسمتهای جمعشونده دارید. کاربر Ctrl/Cmd+F را میزند، یک کلمه وارد میکند و... مرورگر هیچ چیزی نشان نمیدهد، چون متن موردنظر داخل یک آکاردئون بسته پنهان شده است. آزاردهنده است، نه؟ 😔
برای چنین موقعیتهایی، ویژگی hidden="until-found" وجود دارد — یک صفت که اجازه میدهد محتوا از نظر بصری مخفی باشد، اما همچنان برای جستوجو در صفحه قابلدسترسی بماند. اگر مرورگر تطابقی پیدا کند، بهصورت خودکار بخش مربوطه را باز میکند و امکان هماهنگسازی رابط کاربری از طریق رویداد beforematch را فراهم میسازد.
در اصل، این یک بهبود تدریجی است:
✔️ اگر مرورگر از این قابلیت پشتیبانی کند → جستوجو بهصورت «جادویی» عمل میکند؛
✔️ اگر پشتیبانی نکند → رابط کاربری همچنان کاملاً قابل استفاده باقی میماند.
مزیت اصلی این رویکرد، داشتن یک رابط جمعوجور بدون از دست دادن قابلیت پیدا شدن محتواست. کاربر همچنان میتواند با جستوجوی معمولی، سریع بخش موردنظر را پیدا کند، حتی اگر داخل آکاردئون پنهان شده باشد.
اگر مرورگر نتواند محتوا را خودکار باز کند، اتفاق بدی نمیافتد. کافی است پشتیبانی از رویداد beforematch را بررسی کنید و یک سناریوی جایگزین فعال کنید — مثلاً همهٔ بخشها را باز کنید یا از جستوجوی سفارشی استفاده کنید.
‼️ نکاتی که باید به خاطر داشت
⏺️ استفاده از beforematch برای هماهنگ کردن آکاردئون و اسکرول به محل پیدا شده بسیار کاربردی است؛
⏺️ hidden="until-found" همان display: none نیست: رفتار APIهای مربوط به چیدمان (مثل getBoundingClientRect، content-visibility، contain) ممکن است متفاوت باشد، پس بهتر است حتماً تست شود؛
⏺️ همهٔ مرورگرها از این قابلیت پشتیبانی نمیکنند، بنابراین برای مرورگرهای بدون پشتیبانی باید از قبل یک fallback در نظر گرفت: باز کردن صریح بخشهای مهم یا ارائهٔ جستوجوی جایگزین. 😁
🔗https://codepen.io/web-dot-dev/pen/Rwxveab
🔗https://developer.chrome.com/docs/css-ui/hidden-until-found
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
تصور کنید یک صفحهٔ طولانی با بخش پرسشهای متداول، آکاردئونها و قسمتهای جمعشونده دارید. کاربر Ctrl/Cmd+F را میزند، یک کلمه وارد میکند و... مرورگر هیچ چیزی نشان نمیدهد، چون متن موردنظر داخل یک آکاردئون بسته پنهان شده است. آزاردهنده است، نه؟ 😔
برای چنین موقعیتهایی، ویژگی hidden="until-found" وجود دارد — یک صفت که اجازه میدهد محتوا از نظر بصری مخفی باشد، اما همچنان برای جستوجو در صفحه قابلدسترسی بماند. اگر مرورگر تطابقی پیدا کند، بهصورت خودکار بخش مربوطه را باز میکند و امکان هماهنگسازی رابط کاربری از طریق رویداد beforematch را فراهم میسازد.
در اصل، این یک بهبود تدریجی است:
✔️ اگر مرورگر از این قابلیت پشتیبانی کند → جستوجو بهصورت «جادویی» عمل میکند؛
✔️ اگر پشتیبانی نکند → رابط کاربری همچنان کاملاً قابل استفاده باقی میماند.
مزیت اصلی این رویکرد، داشتن یک رابط جمعوجور بدون از دست دادن قابلیت پیدا شدن محتواست. کاربر همچنان میتواند با جستوجوی معمولی، سریع بخش موردنظر را پیدا کند، حتی اگر داخل آکاردئون پنهان شده باشد.
اگر مرورگر نتواند محتوا را خودکار باز کند، اتفاق بدی نمیافتد. کافی است پشتیبانی از رویداد beforematch را بررسی کنید و یک سناریوی جایگزین فعال کنید — مثلاً همهٔ بخشها را باز کنید یا از جستوجوی سفارشی استفاده کنید.
if (!('onbeforematch' in document)) {
// منطق جایگزین (fallback)
}‼️ نکاتی که باید به خاطر داشت
⏺️ استفاده از beforematch برای هماهنگ کردن آکاردئون و اسکرول به محل پیدا شده بسیار کاربردی است؛
⏺️ hidden="until-found" همان display: none نیست: رفتار APIهای مربوط به چیدمان (مثل getBoundingClientRect، content-visibility، contain) ممکن است متفاوت باشد، پس بهتر است حتماً تست شود؛
⏺️ همهٔ مرورگرها از این قابلیت پشتیبانی نمیکنند، بنابراین برای مرورگرهای بدون پشتیبانی باید از قبل یک fallback در نظر گرفت: باز کردن صریح بخشهای مهم یا ارائهٔ جستوجوی جایگزین. 😁
🔗https://codepen.io/web-dot-dev/pen/Rwxveab
🔗https://developer.chrome.com/docs/css-ui/hidden-until-found
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
codepen.io
hidden="until-found" demo showing styling of element nested inside container.
...
انسجام و پیوستگی: چگونه کدی بنویسیم که از تغییر دادنش نترسیم 🧩
پروژه را مثل یک خانه تصور کنید. اتاقها همان ماژولها و کامپوننتها هستند. در یک خانهٔ خوب، هر اتاق کاربرد مشخصی دارد: آشپزخانه برای آشپزی، اتاق خواب برای استراحت، حمام برای بهداشت. اما اگر در یک اتاق هم اجاق باشد، هم تخت، هم ماشین لباسشویی، زندگی سخت و آشفته میشود — این یعنی انسجام پایین: اجزای داخل یک ماژول مشغول کارهای متفاوتی هستند. 😏
درها و راهروها همان رابطها و APIهایی هستند که ماژولها را به هم وصل میکنند. درهای خوب ساده و قابل پیشبینیاند: میتوانید محتوای یک اتاق را تغییر دهید بدون اینکه بقیهٔ خانه تحت تأثیر قرار بگیرد. اما درهای بد یعنی نبودِ مرزهای درست، جایی که مجبور میشوید «از دیوار رد شوید»: هر تغییر، زنجیرهای از اصلاحات را به دنبال میآورد. این همان پیوستگی بالا است.
خانهٔ ایدهآل یعنی اتاقهای مشخص و درهای مرتب. در کدنویسی هم یعنی: هر ماژول یک وظیفه انجام دهد و تا حد ممکن دربارهٔ بقیهٔ سیستم کم بداند. چنین کدی راحتتر تست میشود، آسانتر تغییر میکند و بهتر رشد میکند.
☑️ به خاطر بسپاریم:
انسجام (Cohesion) — یعنی اینکه اجزای داخل یک ماژول/کلاس/سرویس چقدر روی یک هدف مشترک متمرکز هستند.
انسجام بالا = ماژول یک کار مشخص انجام میدهد و آن را خوب انجام میدهد.
پیوستگی (Coupling) — یعنی اینکه یک ماژول چقدر به دیگر ماژولها وابسته است.
پیوستگی پایین = میتوان یک ماژول را تغییر داد بدون اینکه کل سیستم دچار موج اصلاحات شود.
ایدهآل: انسجام بالا + پیوستگی پایین.
🧩 مزیت اصلی چیست؟
☑️ نگهداری کد ارزانتر و سریعتر میشود؛
☑️ تغییرات محدود و موضعی میمانند — با اثرات جانبی کمتر؛
☑️ معماری انعطافپذیر باقی میماند: میتوان پیادهسازیها را عوض کرد بدون اینکه سیستم بشکند؛
☑️ و البته تست کردن سادهتر است — ماژولی که فقط یک مسئولیت دارد، راحتتر موک میشود و پوشش تست بهتری میگیرد.
یک مثال کوچک در تصویر بالا 👆
و البته، معماری خوب یک ایدئولوژی نیست، بلکه یک ابزار است: زمان را ذخیره میکند، نگهداری را سادهتر میکند و تغییرات را امنتر. اما همیشه ممکن است استثناهایی وجود داشته باشد: گاهی لازم است برای یک راهحل فوری یا یک نمونهٔ اولیه، کمی از پاکیزگی کد صرفنظر کنیم. مهم این است که این مصالحهها آگاهانه و موقتی باشند، نه اینکه تبدیل به عادت شوند. 😊
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
پروژه را مثل یک خانه تصور کنید. اتاقها همان ماژولها و کامپوننتها هستند. در یک خانهٔ خوب، هر اتاق کاربرد مشخصی دارد: آشپزخانه برای آشپزی، اتاق خواب برای استراحت، حمام برای بهداشت. اما اگر در یک اتاق هم اجاق باشد، هم تخت، هم ماشین لباسشویی، زندگی سخت و آشفته میشود — این یعنی انسجام پایین: اجزای داخل یک ماژول مشغول کارهای متفاوتی هستند. 😏
درها و راهروها همان رابطها و APIهایی هستند که ماژولها را به هم وصل میکنند. درهای خوب ساده و قابل پیشبینیاند: میتوانید محتوای یک اتاق را تغییر دهید بدون اینکه بقیهٔ خانه تحت تأثیر قرار بگیرد. اما درهای بد یعنی نبودِ مرزهای درست، جایی که مجبور میشوید «از دیوار رد شوید»: هر تغییر، زنجیرهای از اصلاحات را به دنبال میآورد. این همان پیوستگی بالا است.
خانهٔ ایدهآل یعنی اتاقهای مشخص و درهای مرتب. در کدنویسی هم یعنی: هر ماژول یک وظیفه انجام دهد و تا حد ممکن دربارهٔ بقیهٔ سیستم کم بداند. چنین کدی راحتتر تست میشود، آسانتر تغییر میکند و بهتر رشد میکند.
☑️ به خاطر بسپاریم:
انسجام (Cohesion) — یعنی اینکه اجزای داخل یک ماژول/کلاس/سرویس چقدر روی یک هدف مشترک متمرکز هستند.
انسجام بالا = ماژول یک کار مشخص انجام میدهد و آن را خوب انجام میدهد.
پیوستگی (Coupling) — یعنی اینکه یک ماژول چقدر به دیگر ماژولها وابسته است.
پیوستگی پایین = میتوان یک ماژول را تغییر داد بدون اینکه کل سیستم دچار موج اصلاحات شود.
ایدهآل: انسجام بالا + پیوستگی پایین.
🧩 مزیت اصلی چیست؟
☑️ نگهداری کد ارزانتر و سریعتر میشود؛
☑️ تغییرات محدود و موضعی میمانند — با اثرات جانبی کمتر؛
☑️ معماری انعطافپذیر باقی میماند: میتوان پیادهسازیها را عوض کرد بدون اینکه سیستم بشکند؛
☑️ و البته تست کردن سادهتر است — ماژولی که فقط یک مسئولیت دارد، راحتتر موک میشود و پوشش تست بهتری میگیرد.
یک مثال کوچک در تصویر بالا 👆
و البته، معماری خوب یک ایدئولوژی نیست، بلکه یک ابزار است: زمان را ذخیره میکند، نگهداری را سادهتر میکند و تغییرات را امنتر. اما همیشه ممکن است استثناهایی وجود داشته باشد: گاهی لازم است برای یک راهحل فوری یا یک نمونهٔ اولیه، کمی از پاکیزگی کد صرفنظر کنیم. مهم این است که این مصالحهها آگاهانه و موقتی باشند، نه اینکه تبدیل به عادت شوند. 😊
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
جاوااسکریپت | JavaScript
Video
امروزه استفاده از
اما تصور کنید اگر میتوانستید به عناصر چسبنده به والد دسترسی پیدا کنید و با آنها افکتها و رفتارهای جالب ایجاد کنید، چه؟
خبر خوب اینکه این امکان اکنون وجود دارد! اخیراً قابلیت اسکرول-کوئریها به عنوان بخشی از کانتینر-کوئریها معرفی شده است. همانطور که از نامشان پیداست: [Container scroll-state queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_conditional_rules/Container_scroll-state_queries).
مثلاً میخواهید یک تصویر در تصویر (PIP) بسازید، بهطوری که خواندن مقاله متوقف نشود؟
یا برعکس، تبلیغات در صفحه قطع نشود؟ :)
کاملاً امکانپذیر است: [نمونه عملی در CodePen](https://codepen.io/alinaki/pen/WbvMOPB)
نکته جالب اینکه میتوان چند
برای مرورگرهایی که هنوز از اسکرول-کوئریها پشتیبانی نمیکنند، میتوانید همیشه از یک IntersectionObserver استفاده کنید (که در نمونه نیز وجود دارد).
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
position: sticky کاملاً رایج و معمول شده است.اما تصور کنید اگر میتوانستید به عناصر چسبنده به والد دسترسی پیدا کنید و با آنها افکتها و رفتارهای جالب ایجاد کنید، چه؟
خبر خوب اینکه این امکان اکنون وجود دارد! اخیراً قابلیت اسکرول-کوئریها به عنوان بخشی از کانتینر-کوئریها معرفی شده است. همانطور که از نامشان پیداست: [Container scroll-state queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_conditional_rules/Container_scroll-state_queries).
مثلاً میخواهید یک تصویر در تصویر (PIP) بسازید، بهطوری که خواندن مقاله متوقف نشود؟
یا برعکس، تبلیغات در صفحه قطع نشود؟ :)
کاملاً امکانپذیر است: [نمونه عملی در CodePen](https://codepen.io/alinaki/pen/WbvMOPB)
@container scroll-state(stuck: top) {
.pip {
width: 200px;
transform: translate(-50%, 0%)
translate(calc(50vw - (50% + 1rem)), calc(100vh - (100% + 1rem)));
}
}نکته جالب اینکه میتوان چند
translate را با هم ترکیب کرد تا نتایج ریاضیاتی و حرکتی جالبی ایجاد شود.برای مرورگرهایی که هنوز از اسکرول-کوئریها پشتیبانی نمیکنند، میتوانید همیشه از یک IntersectionObserver استفاده کنید (که در نمونه نیز وجود دارد).
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
MDN Web Docs
Using container scroll-state queries - CSS | MDN
Container scroll-state queries are a type of container query. Rather than selectively applying styles to descendant elements based on the container's size, scroll-state queries allow you to selectively apply styles to descendant elements based on the container's…
👍1
This media is not supported in your browser
VIEW IN TELEGRAM
🖤 سالروز وفات حضرت خدیجه کبری (س)
⚫️ رسول الله صلی الله علیه و آله:
◎ خدا زنی بهتر از خدیجه به من نداد. هنگامی که مردم تکذیبم میکردند، او مرا تصدیق کرد و هنگامی که مردم مرا تحریم کردند، با ثروتش کمکم کرد. و خدا از او فرزندانی به من داد در حالی که از دیگر زنانم به من فرزندی نداد.
#️⃣#event
👥@IR_javascript_group
🆔@IR_javascript
⚫️ رسول الله صلی الله علیه و آله:
◎ خدا زنی بهتر از خدیجه به من نداد. هنگامی که مردم تکذیبم میکردند، او مرا تصدیق کرد و هنگامی که مردم مرا تحریم کردند، با ثروتش کمکم کرد. و خدا از او فرزندانی به من داد در حالی که از دیگر زنانم به من فرزندی نداد.
#️⃣#event
👥@IR_javascript_group
🆔@IR_javascript
❤10👎5
ریپازیتوریهای مونو (Monorepo) ویژگی ناخوشایندی دارند: به مرور زمان رشد میکنند—و ... .
با گذشت زمان، ساختار فایلها دیگر چندان گویا نیست: تعداد پوشهها زیاد است، عمق تو در توی پوشهها زیاد است و تشخیص اینکه کدام بخش کد زنده است و کدام غیرفعال، دشوار میشود.
Git Truck این مشکل را با بصریسازی ریپازیتوری حل میکند. این ابزار یک «نقشه» تعاملی از پروژه بر اساس تاریخچه Git میسازد: اندازه فایلها و پوشهها، فعالیت تغییرات، سهم هر نویسنده و همچنین نحوه تغییر ساختار در طول زمان، همگی قابل مشاهده هستند.
این ابزار به صورت محلی اجرا میشود، بدون نیاز به فضای ابری یا یکپارچگی با سرویسهای دیگر، و مرورگر را با نمایش تصویری ریپازیتوری باز میکند. ابزار بسیار مفیدی است زمانی که نیاز دارید سریع با یک مونو ریپازیتوری بزرگ آشنا شوید یا دید کلی از کد داشته باشید، نه فقط به صورت تکپوشهای.
🔗 https://github.com/standardgalactic/git-truck
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
با گذشت زمان، ساختار فایلها دیگر چندان گویا نیست: تعداد پوشهها زیاد است، عمق تو در توی پوشهها زیاد است و تشخیص اینکه کدام بخش کد زنده است و کدام غیرفعال، دشوار میشود.
Git Truck این مشکل را با بصریسازی ریپازیتوری حل میکند. این ابزار یک «نقشه» تعاملی از پروژه بر اساس تاریخچه Git میسازد: اندازه فایلها و پوشهها، فعالیت تغییرات، سهم هر نویسنده و همچنین نحوه تغییر ساختار در طول زمان، همگی قابل مشاهده هستند.
این ابزار به صورت محلی اجرا میشود، بدون نیاز به فضای ابری یا یکپارچگی با سرویسهای دیگر، و مرورگر را با نمایش تصویری ریپازیتوری باز میکند. ابزار بسیار مفیدی است زمانی که نیاز دارید سریع با یک مونو ریپازیتوری بزرگ آشنا شوید یا دید کلی از کد داشته باشید، نه فقط به صورت تکپوشهای.
🔗 https://github.com/standardgalactic/git-truck
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
This media is not supported in your browser
VIEW IN TELEGRAM
اگر شما هم به استفاده از
خب، چطور عنصری که به رویدادهای ماوس پاسخ نمیدهد را انتخاب کنیم؟
همه چیز ساده است: کلید Shift را نگه داشته و انتخاب کنید. ([راهنما](https://devtoolstips.org/tips/en/select-pointer-events-none-elements/))
این روش در Chrome، Edge و Firefox کار میکند.
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
pointer-events: none روی عناصر علاقهمند شدهاید — شاید برای جلوگیری از کپی شدن یا بهبود UX — احتمالاً دیباگ کردن این عناصر در DevTools میتواند تبدیل به کابوس شود.خب، چطور عنصری که به رویدادهای ماوس پاسخ نمیدهد را انتخاب کنیم؟
همه چیز ساده است: کلید Shift را نگه داشته و انتخاب کنید. ([راهنما](https://devtoolstips.org/tips/en/select-pointer-events-none-elements/))
این روش در Chrome، Edge و Firefox کار میکند.
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
متوجه شدیم که فایلهای ignore تنها به
یک مثال خوب
در مقاله، مجموعهای از فایلهای
*
*
*
*
*
*
*
*
*
*
*
*
*
و غیره…
نتیجهگیری ساده است: نام یکسان، رفتار یکسان را تضمین نمیکند. اگر یک ابزار چیزی را «ناگهان» شامل یا نادیده میگیرد، تقریباً همیشه مسئله از جزئیات پیادهسازی منطق ignore است.
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
.gitignore محدود نمیشوند. تقریباً هر ابزار در اکوسیستم خود نسخهای از این فرمت را دارد و اگرچه اغلب گفته میشود «سینتکس .gitignore پشتیبانی میشود»، جزئیات معمولاً متفاوت است ([https://nesbitt.io/2026/02/12/the-many-flavors-of-ignore-files.html](https://nesbitt.io/2026/02/12/the-many-flavors-of-ignore-files.html)).یک مثال خوب
.npmignore است. اگر این فایل وجود نداشته باشد، npm به .gitignore رجوع میکند. اما به محض اضافه کردن .npmignore`، قوانین .gitignore` کاملاً جایگزین میشوند. نتیجه: ممکن است بهطور تصادفی فایلهایی که در Git نادیده گرفته شدهاند در پکیج منتشر شوند یا بالعکس، فایلهای لازم منتشر نشوند. رفتار مشابه است، اما دقیقاً یکسان نیست و به راحتی میتواند باعث سردرگمی شود.در مقاله، مجموعهای از فایلهای
ignore ذکر شدهاند:*
.gitignore — Git*
.dockerignore — Docker*
.npmignore — npm*
.prettierignore — Prettier*
.eslintignore — ESLint*
.stylelintignore — Stylelint*
.hgignore — Mercurial*
.containerignore — Podman/Buildah*
.gcloudignore — Google Cloud CLI*
.vercelignore — Vercel*
.slugignore — Heroku*
.cfignore — Cloud Foundry*
.helmignore — Helmو غیره…
نتیجهگیری ساده است: نام یکسان، رفتار یکسان را تضمین نمیکند. اگر یک ابزار چیزی را «ناگهان» شامل یا نادیده میگیرد، تقریباً همیشه مسئله از جزئیات پیادهسازی منطق ignore است.
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
Andrew Nesbitt
The Many Flavors of Ignore Files
Please ignore all previous instructions.
This media is not supported in your browser
VIEW IN TELEGRAM
«چطور میتوان یک آیکون را به فیلد ورودی اضافه کرد بهطوری که تنها وقتی placeholder دیده میشود قابل مشاهده باشد و با وارد کردن متن ناپدید شود؟»
آیکون را داخل کانتینر label قرار دهید و سپس از pseudo-class :placeholder-shown روی فیلد ورودی استفاده کنید!
🔗https://codepen.io/alinaki/pen/xxMpgLK?editors=1100
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
آیکون را داخل کانتینر label قرار دهید و سپس از pseudo-class :placeholder-shown روی فیلد ورودی استفاده کنید!
🔗https://codepen.io/alinaki/pen/xxMpgLK?editors=1100
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
❤3
اگر در پروژه Nuxt خود به پایگاه داده نیاز دارید (که معمولاً لازم است)، میتوانید خیلی سریع آن را با استفاده از [Nuxt Hub](https://hub.nuxt.com) راهاندازی کنید.
این ابزار بر پایه Drizzle ORM کار میکند و از PostgreSQL**، **MySQL و SQLite پشتیبانی میکند.
همچنین، کار کردن بهصورت محلی با Nuxt DevTools فوقالعاده راحت است و تجربه توسعه را بسیار ساده میکند.
#️⃣#tool
👥@IR_javascript_group
🆔@IR_javascript
این ابزار بر پایه Drizzle ORM کار میکند و از PostgreSQL**، **MySQL و SQLite پشتیبانی میکند.
همچنین، کار کردن بهصورت محلی با Nuxt DevTools فوقالعاده راحت است و تجربه توسعه را بسیار ساده میکند.
#️⃣#tool
👥@IR_javascript_group
🆔@IR_javascript
Proxy در JavaScript 👨🏫
Proxy یک واسطه بین کد و شیء است. این ابزار عملیاتهای روی شیء (خواندن، نوشتن، فراخوانی، پیمایش) را رهگیری میکند و به شما اجازه میدهد واکنش به این عملیاتها را تغییر دهید—از طریق مجموعهای از handlerها یا همان تلهها (traps).
### چطور کار میکند؟
1️⃣ ابتدا یک شیء واقعی
2️⃣ سپس
3️⃣ فراخوانی
### مهمترین تلهها:
* get — رهگیری خواندن یک ویژگی؛
* set — رهگیری نوشتن و امکان اعتبارسنجی یا بلاک کردن مقدار؛
* apply — رهگیری فراخوانی توابع؛
* ownKeys / getOwnPropertyDescriptor — تأثیر روی
### مثال عملی:
این Proxy شیء را «هوشمند» میکند:
* هنگام خواندن ویژگیهای موجود نبود، مقدار پیشفرض برمیگرداند؛
* هنگام نوشتن، بررسی میکند که
### نکات مهم:
✔️ ویژگیهای
✔️ برای ارجاع صحیح از
✔️ از Proxy در حلقههای داغ (حلقههایی که خیلی زیاد اجرا میشوند) اجتناب کنید—ممکن است کارایی کاهش یابد؛
### جمعبندی:
Proxy یک نقطه کنترل مرکزی برای تمام تعاملات با شیء فراهم میکند، اما استفاده از آن باید آگاهانه باشد. کاربردهای اصلی شامل:
* واکنشپذیری (reactivity)
* اعتبارسنجی
* ثبت لاگ (logging)
* ویژگیهای مجازی
در اکثر کارهای روزمره، استفاده از Proxy ممکن است اضافه باشد، اما دانستن نحوه عملکرد آن مهم است: بهتر است ابزار را بشناسید و استفاده نکنید تا اینکه در کد با آن مواجه شوید و نفهمید چه اتفاقی میافتد. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
Proxy یک واسطه بین کد و شیء است. این ابزار عملیاتهای روی شیء (خواندن، نوشتن، فراخوانی، پیمایش) را رهگیری میکند و به شما اجازه میدهد واکنش به این عملیاتها را تغییر دهید—از طریق مجموعهای از handlerها یا همان تلهها (traps).
const p = new Proxy(target, handler);
### چطور کار میکند؟
1️⃣ ابتدا یک شیء واقعی
target میسازید؛2️⃣ سپس
handler را تعریف میکنید—شیئی با توابع رهگیریکننده مانند get, set, apply و غیره؛3️⃣ فراخوانی
new Proxy(target, handler) یک پروکسی برمیگرداند: هر عملی روی شیء ابتدا تله مربوطه را فراخوانی میکند و تصمیم میگیرد که عمل استاندارد انجام شود (با استفاده از Reflect) یا تغییر کند؛### مهمترین تلهها:
* get — رهگیری خواندن یک ویژگی؛
* set — رهگیری نوشتن و امکان اعتبارسنجی یا بلاک کردن مقدار؛
* apply — رهگیری فراخوانی توابع؛
* ownKeys / getOwnPropertyDescriptor — تأثیر روی
Object.keys و سریالسازی؛### مثال عملی:
const obj = { name: "Katya" };
const p = new Proxy(obj, {
get(t, prop){
return prop in t ? Reflect.get(t, prop) : "—not found—";
},
set(t, prop, val){
if(prop === "age" && typeof val !== "number")
throw TypeError("age must be number");
return Reflect.set(t, prop, val);
}
});این Proxy شیء را «هوشمند» میکند:
* هنگام خواندن ویژگیهای موجود نبود، مقدار پیشفرض برمیگرداند؛
* هنگام نوشتن، بررسی میکند که
age حتماً عدد باشد؛### نکات مهم:
✔️ ویژگیهای
non-configurable را پنهان نکنید و به شیء غیرقابل گسترش فیلد اضافه نکنید—در غیر این صورت TypeError خواهید گرفت؛const obj = {};
Object.defineProperty(obj, "fixed", {
value: 42,
configurable: false // غیرقابل تغییر
});
// این کار باعث خطا میشود
delete obj.fixed; // TypeError✔️ برای ارجاع صحیح از
Reflect.* (https://t.me/IR_javascript/1328) استفاده کنید (بهویژه برای getterها و پروتوتایپها)؛✔️ از Proxy در حلقههای داغ (حلقههایی که خیلی زیاد اجرا میشوند) اجتناب کنید—ممکن است کارایی کاهش یابد؛
### جمعبندی:
Proxy یک نقطه کنترل مرکزی برای تمام تعاملات با شیء فراهم میکند، اما استفاده از آن باید آگاهانه باشد. کاربردهای اصلی شامل:
* واکنشپذیری (reactivity)
* اعتبارسنجی
* ثبت لاگ (logging)
* ویژگیهای مجازی
در اکثر کارهای روزمره، استفاده از Proxy ممکن است اضافه باشد، اما دانستن نحوه عملکرد آن مهم است: بهتر است ابزار را بشناسید و استفاده نکنید تا اینکه در کد با آن مواجه شوید و نفهمید چه اتفاقی میافتد. 👍
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
Telegram
جاوااسکریپت | JavaScript
**Proxy و Reflect: چطور کار با آبجکتها راحتتر میشود**
در JavaScript ابزارهای قدرتمندی وجود دارند که گاهی کمتر به چشم میآیند. Proxy و Reflect دقیقاً از همین دست ابزارها هستند که اغلب پشت صحنه باقی میمانند. اما اگر بدانید چطور از آنها درست استفاده کنید،…
در JavaScript ابزارهای قدرتمندی وجود دارند که گاهی کمتر به چشم میآیند. Proxy و Reflect دقیقاً از همین دست ابزارها هستند که اغلب پشت صحنه باقی میمانند. اما اگر بدانید چطور از آنها درست استفاده کنید،…
وقتی height: 100% روند چیدمان رو به هم ریخت 🙈
داشتم یه گرید ساده میساختم: یه کانتینر با display: grid، ردیفها با grid-template-rows: repeat(n, auto) و توی یه سلول، یه عنصری بود که براش height: 100% گذاشتم تا «قشنگ کش بیاد». تو کروم همهچیز عالی بود، ولی تو سافاری چیدمان به هم ریخت. ☹️
اولش فکر کردم: «خب، دیگه باگ مرورگره». ولی اگه یه کم عمیقتر بشیم، میبینیم که خبری از ماورا نیست: همهچیز به یه قانون CSS برمیگرده.
چه اتفاقی افتاد؟
height: 100% یعنی ۱۰۰٪ ارتفاع والد. و اینجا یه نکته کلیدی هست: ارتفاع درصدی فقط وقتی کار میکنه که والد یه ارتفاع مشخصی داشته باشه—منظور ارتفاعیه که بشه بدون در نظر گرفتن محتوا حسابش کرد.
خب، ما اینجا چی داریم؟ یه ردیف auto تو گرید که ارتفاعش دقیقاً موقع چیدمان و بر اساس محتوا تعیین میشه، پس از قبل ارتفاعش معلوم نیست. نتیجه میشه یه چرخه:
✔️ ردیف منتظر میمونه تا اندازه بچهاش معلوم بشه،
✔️ بچه میخواد ۱۰۰٪ ارتفاع ردیف رو بگیره،
✔️ ولی ردیف هنوز ارتفاع خودش رو نمیدونه—و همهچیز قفل میکنه.
➡️ درصد اصلاً نمیدونه باید از چی حساب کنه.
چرا تو مرورگرهای مختلف نتیجه فرق میکنه؟
موتورهای مختلف ممکن است این جور چرخههای اندازهگیری رو جور متفاوتی حل کنن.
توی بعضیها نتیجه «بهنظر» نرمال میاد، توی بعضی دیگه مشکل خودش رو نشون میده.
خلاصه اینکه، این باگ یه مرورگر خاص نیست. این تفاوت توی پیادهسازی الگوریتم محاسبه اندازهها تو موقعیتهای وابستهست.
خب، چطور این مشکل رو حل کنیم؟ باید به درصد یه تکیهگاه بدیم—یعنی ارتفاع والد رو مشخص و معین کنیم. 👍
#️⃣#tip #css
👥@IR_javascript_group
🆔@IR_javascript
داشتم یه گرید ساده میساختم: یه کانتینر با display: grid، ردیفها با grid-template-rows: repeat(n, auto) و توی یه سلول، یه عنصری بود که براش height: 100% گذاشتم تا «قشنگ کش بیاد». تو کروم همهچیز عالی بود، ولی تو سافاری چیدمان به هم ریخت. ☹️
اولش فکر کردم: «خب، دیگه باگ مرورگره». ولی اگه یه کم عمیقتر بشیم، میبینیم که خبری از ماورا نیست: همهچیز به یه قانون CSS برمیگرده.
چه اتفاقی افتاد؟
height: 100% یعنی ۱۰۰٪ ارتفاع والد. و اینجا یه نکته کلیدی هست: ارتفاع درصدی فقط وقتی کار میکنه که والد یه ارتفاع مشخصی داشته باشه—منظور ارتفاعیه که بشه بدون در نظر گرفتن محتوا حسابش کرد.
خب، ما اینجا چی داریم؟ یه ردیف auto تو گرید که ارتفاعش دقیقاً موقع چیدمان و بر اساس محتوا تعیین میشه، پس از قبل ارتفاعش معلوم نیست. نتیجه میشه یه چرخه:
✔️ ردیف منتظر میمونه تا اندازه بچهاش معلوم بشه،
✔️ بچه میخواد ۱۰۰٪ ارتفاع ردیف رو بگیره،
✔️ ولی ردیف هنوز ارتفاع خودش رو نمیدونه—و همهچیز قفل میکنه.
➡️ درصد اصلاً نمیدونه باید از چی حساب کنه.
چرا تو مرورگرهای مختلف نتیجه فرق میکنه؟
موتورهای مختلف ممکن است این جور چرخههای اندازهگیری رو جور متفاوتی حل کنن.
توی بعضیها نتیجه «بهنظر» نرمال میاد، توی بعضی دیگه مشکل خودش رو نشون میده.
خلاصه اینکه، این باگ یه مرورگر خاص نیست. این تفاوت توی پیادهسازی الگوریتم محاسبه اندازهها تو موقعیتهای وابستهست.
خب، چطور این مشکل رو حل کنیم؟ باید به درصد یه تکیهگاه بدیم—یعنی ارتفاع والد رو مشخص و معین کنیم. 👍
#️⃣#tip #css
👥@IR_javascript_group
🆔@IR_javascript