جاوااسکریپت | JavaScript
507 subscribers
654 photos
139 videos
3 files
512 links
کانال @IR_javascript حاوی اطلاعات مفید در حوزه برنامه نویس فرانت که بصورت روزانه بروز می‌شود.
در این کانال شما به:
[1] مطالب تازه
[2] تحلیل‌های عمیق
[3] نکات آموزشی
[4] چالش
[5] ابزار و راهنمایی‌های کاربردی
دسترسی خواهید داشت.

🆔@IR_javascript
Download Telegram
در این پست، API جدیدی با نام effectScope() برای پکیج @vue/reactivity معرفی می‌شود. یک نمونه از EffectScope می‌تواند به‌صورت خودکار، افکت‌هایی را که درون یک تابع هم‌زمان اجرا می‌شوند، گردآوری کند تا در زمان مناسب همگی با هم حذف شوند.

در تابع setup() درون یک کامپوننت Vue، افکت‌ها به‌صورت خودکار جمع‌آوری شده و به همان نمونه‌ی کامپوننت متصل می‌شوند. هنگام از بین رفتن کامپوننت، این افکت‌ها نیز به‌طور خودکار حذف می‌شوند. این ویژگی، ساده و شهودی است.

اما هنگامی که این افکت‌ها را خارج از کامپوننت‌ها یا در قالب یک پکیج مستقل استفاده کنیم، کار به این سادگی نیست. برای نمونه، حذف افکت‌های computed و watch به صورت دستی به شکل زیر خواهد بود:
const disposables = []

const counter = ref(۰)
const doubled = computed(() => counter.value * ۲)

disposables.push(() => stop(doubled.effect))

const stopWatch۱ = watchEffect(() => {
console.log(`counter: ${counter.value}`)
})

disposables.push(stopWatch۱)

const stopWatch۲ = watch(doubled, () => {
console.log(doubled.value)
})

disposables.push(stopWatch۲)


و برای حذف آن‌ها:
disposables.forEach((f) => f())
disposables = []


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

اما با وجود EffectScope بصورت زیر میشود
// effect، computed، watch، و watchEffect که درون این scope ساخته می‌شوند، جمع‌آوری خواهند شد

const scope = effectScope()

scope.run(() => {
const doubled = computed(() => counter.value * ۲)

watch(doubled, () => console.log(doubled.value))

watchEffect(() => console.log('تعداد: ', doubled.value))
})

// برای حذف همه افکت‌ها در این scope
scope.stop()





‏### Scopeهای تودرتو

‏Scopeهای تودرتو نیز باید توسط scope والد جمع‌آوری شوند. زمانی که scope والد حذف شود، همه‌ی scopeهای فرزند نیز حذف خواهند شد:

const scope = effectScope()

scope.run(() => {
const doubled = computed(() => counter.value * ۲)

effectScope().run(() => {
watch(doubled, () => console.log(doubled.value))
})

watchEffect(() => console.log('تعداد: ', doubled.value))
})

// حذف تمام افکت‌ها، از جمله موارد در scopeهای تودرتو
scope.stop()


---

‏### Scopeهای جداشده (Detached)

پارامتر detached در effectScope اجازه می‌دهد تا scope به‌صورت جدا از scope والد ایجاد شود. این ویژگی برای سناریوهایی مانند «مقداردهی اولیه تنبل» (Lazy Initialization) مفید است.

let nestedScope

const parentScope = effectScope()

parentScope.run(() => {
const doubled = computed(() => counter.value * ۲)

nestedScope = effectScope(true /* detached */)
nestedScope.run(() => {
watch(doubled, () => console.log(doubled.value))
})

watchEffect(() => console.log('تعداد: ', doubled.value))
})

// فقط scope والد حذف می‌شود
parentScope.stop()

// scope تودرتو را در زمان مناسب حذف می‌کنیم
nestedScope.stop()


---

### تابع onScopeDispose

تابع onScopeDispose عملکردی مشابه onUnmounted دارد، اما برای scope فعلی (نه نمونه کامپوننت). این قابلیت به فانکشن‌های ترکیبی کمک می‌کند تا اثرات جانبی خود را همراه با scope مربوطه پاک‌سازی کنند.

import { onScopeDispose } from 'vue'

const scope = effectScope()

scope.run(() => {
onScopeDispose(() => {
console.log('پاک‌سازی شد!')
})
})

scope.stop() // چاپ: پاک‌سازی شد!


---

### دریافت scope فعلی

‏API جدید getCurrentScope() برای دریافت scope فعلی معرفی شده است:

import { getCurrentScope } from 'vue'

getCurrentScope() // EffectScope | undefined


🔗https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md
#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
### مثال‌ها

#### مثال الف: استفاده‌ی مشترک از composable

تابع useMouse() یک نمونه‌ی خوب برای ایجاد اثرات جانبی جهانی است:

function useMouse() {
const x = ref(۰)
const y = ref(۰)

function handler(e) {
x.value = e.x
y.value = e.y
}

window.addEventListener('mousemove', handler)

onUnmounted(() => {
window.removeEventListener('mousemove', handler)
})

return { x, y }
}


اگر useMouse() در چند کامپوننت فراخوانی شود، هرکدام یک listener جدید ایجاد کرده و refs جداگانه خواهند داشت. برای جلوگیری از این کار پرهزینه، می‌توانیم از scope جداشده و onScopeDispose استفاده کنیم:

ابتدا:

- onUnmounted(() => {
+ onScopeDispose(() => {
window.removeEventListener('mousemove', handler)
})


سپس یک تابع کمکی برای مدیریت اشتراک ایجاد می‌کنیم:

function createSharedComposable(composable) {
let subscribers = ۰
let state, scope

const dispose = () => {
if (scope && --subscribers <= ۰) {
scope.stop()
state = scope = null
}
}

return (...args) => {
subscribers++
if (!state) {
scope = effectScope(true)
state = scope.run(() => composable(...args))
}
onScopeDispose(dispose)
return state
}
}


اکنون:

const useSharedMouse = createSharedComposable(useMouse)


این نسخه از useMouse تنها یک بار listener را اضافه می‌کند و در صورت عدم نیاز، آن را حذف می‌نماید.

---

#### مثال ب: Scopeهای موقتی (Ephemeral)

export default {
setup() {
const enabled = ref(false)
let mouseState, mouseScope

const dispose = () => {
mouseScope && mouseScope.stop()
mouseState = null
}

watch(
enabled,
() => {
if (enabled.value) {
mouseScope = effectScope()
mouseState = mouseScope.run(() => useMouse())
} else {
dispose()
}
},
{ immediate: true }
)

onScopeDispose(dispose)
},
}


در اینجا scopeها به‌صورت موقتی ساخته و حذف می‌شوند. onScopeDispose اطمینان حاصل می‌کند که useMouse به‌درستی پاک‌سازی شود، درحالی‌که onUnmounted در این فرآیند فراخوانی نمی‌شود.

---

### تأثیر در هسته Vue

در حال حاضر، در @vue/runtime-dom توابع computed بازنویسی می‌شوند تا به نمونه‌ی کامپوننت متصل شوند. بنابراین موارد زیر معادل نیستند:

// متفاوت
import { computed } from '@vue/reactivity'
import { computed } from 'vue'


با پیاده‌سازی این RFC، دیگر نیازی به بازنویسی computed نخواهد بود و vue می‌تواند مستقیماً از نسخه‌ی @vue/reactivity استفاده کند.


🔗https://github.com/vuejs/rfcs/blob/master/active-rfcs/0041-reactivity-effect-scope.md
#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
درک تفاوت‌ها: File در برابر Blob
گاهی اوقات در کد به موقعیت‌هایی برمی‌خوریم که به‌دلیل درک نادرست از تفاوت میان Blob و File**، ابهاماتی به‌وجود می‌آید. سپس این پرسش‌ها مطرح می‌شوند:
«چرا نام فایل منتقل نمی‌شود؟»
«چرا تاریخ وجود ندارد؟»
یا «چطور می‌توان این را به‌درستی با FormData ارسال کرد؟»
زمان آن رسیده که موضوع را روشن کنیم!

### Blob چیست؟
**Blob
ساختاری داده‌ای است که یک شیء تغییرناپذیر شامل داده‌های دودویی را نمایش می‌دهد.

const blob = new Blob(["Hello, world!"], { type: "text/plain" });


نکات مهم:
- می‌تواند هر نوع داده‌ای را در خود جای دهد: تصویر، متن، ویدیو و ...
- اطلاعاتی درباره‌ی منبع فایل در خود ندارد؛
- فاقد نام (name) و تاریخ آخرین تغییر (lastModified) است؛
- اما می‌توان به اندازه (size) و نوع (type) آن دسترسی داشت.

کاربردهای رایج Blob:
- تولید فایل‌ها به‌صورت «لحظه‌ای» (مثلاً تولید فایل CSV یا JSON از رابط کاربری)
- ایجاد URL با استفاده از URL.createObjectURL()
- نگهداری موقت داده‌ها در حافظه

جمع‌بندی:
Blob روشی عمومی و انعطاف‌پذیر برای نمایش هرگونه داده‌ی «خام» در یک وب‌اپلیکیشن است.
در بسیاری از عملیات مربوط به محتوای دودویی در وب، پایه و اساس را Blob تشکیل می‌دهد.

---

### File چیست؟
File در واقع نسخه‌ی توسعه‌یافته‌ی Blob به همراه فراداده (metadata) است.

const file = new File(["Hello, world!"], "greeting.txt", {
type: "text/plain",
lastModified: Date.now()
});


نکات مهم:
- از Blob ارث‌بری می‌کند؛ یعنی تمام قابلیت‌های Blob را داراست؛
- افزوده‌هایی دارد مانند: name و lastModified;
- شیء File همان چیزی است که هنگام انتخاب فایل توسط کاربر از طریق <input type="file"> به‌دست می‌آید.

کاربردهای رایج File:
- مدیریت فایل‌هایی که کاربر در اختیار برنامه قرار می‌دهد؛
- خواندن محتوای فایل با FileReader
- ارسال فایل به سرور
- پیش‌نمایش فایل در مرورگر

---

### نتیجه‌گیری نهایی:
🔹 اگر فقط به یک جریان داده‌ی دودویی نیاز دارید: Blob را انتخاب کنید.
🔹 اگر نیاز به شیئی دارید که همانند یک فایل رفتار کند: File انتخاب بهتری است.

و همین!

#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
👍2
مهاجرت مایکروسافت به موتور کرومیوم یک تصمیم بسیار هوشمندانه بود.

در آغوش بگیر، توسعه بده، و حذف کن — راهبردی که هنوز هم کار می‌کند.
#️⃣#tool
👥@IR_javascript_group
🆔@IR_javascript
عبارت «در آغوش بگیر، گسترش بده و نابود کن» (Embrace, Extend, and Extinguish – EEE) عبارتی است که، بنا بر گزارش وزارت دادگستری ایالات متحده، مایکروسافت در داخل شرکت برای توصیف راهبرد خود به کار می‌برد. این استراتژی به این صورت عمل می‌کرد که ابتدا وارد حوزه‌هایی با استانداردهای رایج و پرکاربرد می‌شد، سپس آن استانداردها را با قابلیت‌های اختصاصی خود گسترش می‌داد، و در نهایت از این تفاوت‌ها برای به حاشیه راندن و تضعیف جدی رقبا استفاده می‌کرد.


فرآیند «در آغوش‌گیری و گسترش» مایکروسافت در حوزه توسعه نرم‌افزار به‌وضوح قابل مشاهده است:
از Visual Studio Code و تملک GitHub و npmjs.org گرفته تا معرفی TypeScript، و اکنون Copilot (محصولی بر پایه هوش مصنوعی OpenAI).

#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
👍2
روندهای تاریخی در آمار استفاده از کتابخانه‌های جاوااسکریپت در وب‌سایت‌ها
این گزارش، روندهای تاریخی مربوط به میزان استفاده از برترین کتابخانه‌های جاوااسکریپت را از آوریل سال دوهزار و بیست‌وچهار تا به امروز بررسی می‌کند.

🔗https://w3techs.com/technologies/history_overview/javascript_library/all
#️⃣#tool
👥@IR_javascript_group
🆔@IR_javascript
دسترس‌پذیری وب‌سایت و Screen Reader
این موضوعی‌ست که انگار همه با آن آشنایی دارند، اما کمتر کسی آن را واقعاً درک می‌کند.

در توسعه‌ی فرانت‌اند، معمولاً تمرکز ما بر ظاهر، عملکرد و تعامل‌پذیری است. اما دسترس‌پذیری چطور؟ معمولاً به آن می‌گوییم: «باشه، بعداً اضافه‌اش می‌کنیم.» تصور می‌شود که این فقط مربوط به موقعیت‌های خاص و کاربران «خاص» است. اما واقعیت این است که دسترس‌پذیری برای آدم‌های عادی‌ست**—تنها با ابزارها و شرایط متفاوتی برای تعامل با رابط کاربری.

Screen reader ظاهر را نمی‌بیند؛ **ساختار را می‌خواند.
او «نمی‌بیند» که یک دکمه زیباست، بلکه تنها می‌فهمد این عنصر چیست (دکمه، بلوک، عنوان و غیره)، کجای ساختار DOM قرار گرفته (که در اولویت خواندن اثر دارد)، و آیا می‌توان با آن تعامل داشت یا نه (مثل دکمه‌ها، فیلدهای ورودی و موارد دیگر).

براساس همین اطلاعات، Screen reader تصمیم می‌گیرد که آیا لازم است این عنصر را برای کاربر بخواند یا نه.

---

### از پایه شروع کنیم:
چه چیزهایی لازم است تا چنین شیوه‌ی تعامل با رابط، در سایت قابل استفاده باشد؟

#### استفاده از تگ‌های معنایی درست
تگ‌هایی مانند: button`، `nav`، `main`، `label`، `form
این‌ها صرفاً «استانداردهای رایج» نیستند؛ بلکه زبانی هستند که اسکرین‌ریدرها و APIهای دسترس‌پذیری از طریق آن با رابط کاربر ارتباط برقرار می‌کنند.

#### 🖼 متن جایگزین برای تصاویر
اگر تصویر حاوی معناست، همیشه ویژگی alt را با توضیح مناسب قرار بده.
اگر تصویر صرفاً تزئینی است، alt="" را تنظیم کن تا screen reader آن را نادیده بگیرد.

#### ⌨️ فوکوس درست و ناوبری با کیبورد
با کلید Tab در وب‌سایت حرکت کن.
آیا ترتیب منطقی دارد؟
آیا تمام عناصر قابل فوکوس هستند؟
آیا «تله»‌ای وجود دارد که کاربر در آن گیر کند؟
اگر کاربر نتواند به یک دکمه برسد یا در ترتیب بین عناصر گم شود، این خود یک مانع است.

#### 🧠 استفاده هوشمندانه از ویژگی‌های aria-*
هر جا امکان استفاده از تگ های معنایی بومی HTML وجود دارد، از آن استفاده کن.
ویژگی‌هایی مانند aria-label`، `aria-hidden`، `aria-live و ... زمانی کاربرد دارند که HTML نتواند نیاز مورد نظر را برآورده کند.
اما اگر بدون دلیل موجه، از آن‌ها استفاده کنی، فقط کد را سنگین‌تر و تجربه‌ی کاربر را مختل خواهی کرد.

---

### نکته‌ی کلیدی:
دسترس‌پذیری «مخصوص دیگران» نیست.
این نه یک پیچیدگی‌ست، نه باری اضافی؛ بلکه بخشی از همان توجهی‌ست که به طراحی واکنش‌گرا، مسیر ناوبری واضح یا سرعت بارگذاری می‌دهی.
دسترس‌پذیری، فقط یک روش متفاوت و جایگزین برای تعامل با رابط کاربری توست.

و جالب اینجاست که دسترس‌پذیری معمولاً از ساده‌ترین جا آغاز می‌شود:
نه با افزونه‌های پیشرفته یا ترفندهای پیچیده، بلکه با کدنویسی تمیز، شفاف، و دغدغه‌مندی برای کاربران واقعی.



#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
1
کوئری‌های کانتینری (Container Queries) ابزاری در CSS هستند که این امکان را فراهم می‌کنند تا عناصر بر اساس ابعاد کانتینر والد خود استایل‌دهی شوند، نه صرفاً بر اساس اندازه‌ی کل پنجره‌ی مرورگر.

در نسخه‌ی کروم صد و سی و سه**، قابلیت جدیدی با عنوان **وضعیت‌های اسکرول (Scroll States) برای کوئری‌های کانتینری معرفی شده است که کنترل بیشتری بر رفتار عناصر در هنگام اسکرول فراهم می‌کند.

---

### وضعیت‌های اسکرول (Scroll States) چیست؟

سه وضعیت جدید برای ویژگی scroll-state تعریف شده‌اند:
scrollable**، **stuck و snapped
این وضعیت‌ها می‌توانند در داخل قانون @container مورد استفاده قرار گیرند تا بسته به وضعیت اسکرول کانتینر، استایل‌های خاصی اعمال شوند.

---

### نمونه‌ کد:

.stuck-top {
container-type: scroll-state;
position: sticky;
top: 0px;

nav {
@container scroll-state(stuck: top) {
background: Highlight;
color: HighlightText;
}
}
}


---

### موارد کاربرد:

- نمایش وضعیت اسکرول
به‌صورت بصری به کاربر نشان دهید که هنوز محتوای بیشتری در پایین وجود دارد.

- تیترهای چسبنده (Sticky Headers)
به‌طور خودکار، تیترها هنگام اسکرول در جای خود ثابت شوند.

- تغییر رفتار ناوبری
مثلاً نمایش دکمه‌ی «بازگشت به بالا» تنها در صورتی که کاربر اسکرول کرده باشد.

- بهینه‌سازی رابط کاربری
پنهان‌سازی عناصر کم‌اهمیت هنگام اسکرول برای افزایش تمرکز و فضای قابل مشاهده.

---

برای اطلاعات بیشتر، به مستندات رسمی CSS Container Queries مراجعه کنید.
این قابلیت جدید، گامی مهم به‌سوی طراحی رابط‌های کاربری هوشمندتر و پاسخ‌گوتر به شرایط واقعی نمایش است.

#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
👍2👏1
اگر در یک اپلیکیشن چندین تابع غیرهمزمان (مانند بارگذاری داده‌ها از بک‌اند) به‌طور همزمان اجرا می‌شود و نیاز به نمایش یک انیمیشن پیش‌لودر واحد داریم، می‌توانیم به‌راحتی این کار را از طریق یک تابع composable پیاده‌سازی کنیم:

---

### AppLoader.vue

<script setup>
import { useAppLoader } from "@/app/composables/useAppLoader";
const { loading } = useAppLoader();
</script>

<template>
<div class="loader" :class="{ active: loading }">
<div class="loaderBar" />
</div>
</template>


### useAppLoader.ts

import { computed, reactive, ref } from "vue";

const loaderSet = reactive(new Set<string>());
const loading = computed(() => loaderSet.size > 0);

export function useAppLoader(id) {
function startLoading() {
loaderSet.add(id);
}
function stopLoading() {
loaderSet.delete(id);
}

return { loading, startLoading, stopLoading };
}


### SomeComponent

import { useId } from "Vue";
import { useAppLoader } from "@/app/composables/useAppLoader";

const { startLoading, stopLoading } = useAppLoader(useId());

startLoading();
product.value = await api.products.product(props.productId);
stopLoading();


---

این مثال از useId استفاده می‌کند که در Vue 3.5 معرفی شده است.
برای نسخه‌های قدیمی‌تر Vue، می‌توان از هر تابعی برای تولید یک شناسه‌ی منحصر به فرد استفاده کرد.

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
راحت ترین معماری برای یک برنامه مبتنی بر Vue 3، معماری ماژولار (ماژول‌محور) است.

در این رویکرد، ساختار برنامه از همان ابتدا به ماژول‌های منطقی و مستقل با وابستگی حداقلی تقسیم می‌شود. برای مثال، در یک فروشگاه آنلاین، می‌توان ماژول‌هایی مانند:

- ماژول کاتالوگ محصولات
- ماژول صفحه محصول
- ماژول حساب کاربری / پنل شخصی کاربر

را تعریف کرد.

علاوه بر این‌ها، یک ماژول پوسته (App Shell) نیز وجود دارد؛ بخشی که ساختار کلی برنامه را شامل می‌شود، نظیر: هدر، فوتر، منوی کناری و ناحیه اصلی نمایش محتوا. بهتر است منابع عمومی و پرکاربرد مانند:

- متدهای API
- کتابخانه بین‌المللی‌سازی useI18n
- کامپوننت‌های پایه‌ای نظیر BaseButton
- توابع کمکی مانند stringHelpers

در این ماژول قرار گیرند.

---

هر ماژول، پوشه‌ها و ساختار مستقل خود را دارد که معمولاً شامل بخش‌های زیر است:

- components (کامپوننت‌ها)
- composables (توابع ترکیبی)
- assets (منابع گرافیکی و استاتیک)
- utils (توابع کمکی)
- و در صورت نیاز: api`، `routes`، `views`، `layouts

---

این جداسازی با وابستگی کم، این امکان را فراهم می‌کند که هر ماژول به‌صورت **نسبتاً مستقل توسعه یابد**؛ که این موضوع در عمل موجب بهبود عملکرد تیم توسعه، سهولت نگهداری کد، و افزایش احتمال موفقیت پروژه می‌شود.

(ادامه دارد ... .)

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍5🔥2👎1
### ساختار تخت (Flat)

زمانی که قصد راه‌اندازی یک پروژه کوچک Vue مانند یک «اثبات مفهوم» (Proof of Concept) را دارید، می‌توانید ساختاری ساده و تخت برای پوشه‌ها انتخاب کنید تا از پیچیدگی‌های غیرضروری اجتناب شود:

/src
|-- /components
| |-- BaseButton.vue
| |-- BaseCard.vue
| |-- PokemonList.vue
| |-- PokemonCard.vue
|-- /composables
| |-- usePokemon.js
|-- /utils
| |-- validators.js
|-- /layout
| |-- DefaultLayout.vue
| |-- AdminLayout.vue
|-- /plugins
| |-- translate.js
|-- /views
| |-- Home.vue
| |-- PokemonDetail.vue
|-- /router
| |-- index.js
|-- /store
| |-- index.js
|-- /assets
| |-- /images
| |-- /styles
|-- /tests
| |-- ...
|-- App.vue
|-- main.js


---

### طراحی اتمی (Atomic Design)

برای اپلیکیشن‌های بزرگ Vue، بهره‌گیری از متدولوژی «طراحی اتمی» می‌تواند بسیار سودمند باشد. این روش، کامپوننت‌ها را به‌صورت سلسله‌مراتبی از ساده‌ترین تا پیچیده‌ترین دسته‌بندی می‌کند:

- اتم‌ها: عناصر پایه مانند دکمه‌ها و آیکون‌ها
- مولکول‌ها: گروهی از اتم‌ها، مانند ورودی جستجو
- ارگانیسم‌ها: کامپوننت‌های پیچیده‌تری مانند هدر یا کارت‌ها
- قالب‌ها (Templates): ساختار بصری صفحات
- صفحات (Pages): نمایش نهایی اطلاعات با داده‌های واقعی

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

/src
|-- /components
| |-- /atoms
| | |-- AtomButton.vue
| | |-- AtomIcon.vue
| |-- /molecules
| | |-- MoleculeSearchInput.vue
| | |-- MoleculePokemonThumbnail.vue
| |-- /organisms
| | |-- OrganismPokemonCard.vue
| | |-- OrganismHeader.vue
| |-- /templates
| | |-- TemplatePokemonList.vue
| | |-- TemplatePokemonDetail.vue
|-- /pages
| |-- PageHome.vue
| |-- PagePokemonDetail.vue
...


---

### طراحی ماژولار (Modular Design)

با گسترش پروژه، استفاده از معماری ماژولار توصیه می‌شود. در این رویکرد، هر قابلیت یا حوزه (Domain) به‌صورت یک واحد مستقل تعریف می‌شود که نگهداری و توسعه آن آسان‌تر بوده و زمینه‌ساز انتقال به معماری‌های میکروسرویسی نیز هست.

/src
|-- /core
| |-- /components
| | |-- BaseButton.vue
| |-- /models
| |-- /store
| |-- /services
| |-- /views
|-- /modules
| |-- /pokemon
| | |-- /components
| | |-- /models
| | |-- /store
| | |-- /services
| | |-- /views
| | |-- /tests
| |-- /search
| |-- ...
...


---

### طراحی مبتنی بر ویژگی‌ها (Feature-Sliced Design)

این روش برای پروژه‌های بزرگ و طولانی‌مدت طراحی شده است تا توسعه و نگهداری آن‌ها را ساده‌تر و ساختارمندتر کند. در این مدل، اپلیکیشن به لایه‌هایی با مسئولیت‌های مشخص تقسیم می‌شود:

- app: تنظیمات کلی، استایل‌ها و Providerها
- pages: صفحات کامل شامل داده‌ها
- widgets: بلاک‌های مستقل رابط کاربری
- features: منطق عملکردی قابل استفاده مجدد
- entities: مدل‌های اصلی کسب‌وکار مانند کاربر یا محصول
- shared: کامپوننت‌ها و ابزارهای عمومی مانند UIKit یا API

/src
|-- /app
|-- /pages
|-- /widgets
|-- /features
|-- /entities
|-- /shared
...


---

### میکروفرانت‌اندها (Microfrontends)

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

- پوسته برنامه: کنترل ساختار کلی و مسیرها
- رابط‌های خردشده: هر بخش از برنامه به‌طور مستقل پیاده‌سازی شده و می‌تواند با فناوری متفاوتی توسعه یابد.

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


🔗https://vue-faq.org/ru/development/architectural-patterns.html?t=7
#️⃣#tip
👥@IR_javascript_group
🆔@IR_javascript
👍2
در انتخاب کتابخانه‌ی رابط کاربری (UI Library) مناسب برای پروژه‌های Vue 3 یا Nuxt 3، باید عواملی مانند قابلیت توسعه، مستندات، پشتیبانی از TailwindCSS، پشتیبانی از SSR (در Nuxt)، و تجربه کاربری را در نظر بگیرید
کتالوگ کتابخانه‌های رابط کاربری برای vue , nuxt
🔗 https://ui-libs.vercel.app/
#️⃣#tool
👥@IR_javascript_group
🆔@IR_javascript
👏3👍1
در این مطلب می‌خواهیم درباره دو هوک کاربردی در Vue به نام‌های onRenderTracked() و onRenderTriggered() صحبت کنیم — ابزارهایی که معمولاً کمتر مورد استفاده قرار می‌گیرند، در حالی که بسیار مفید هستند!

### این دو چه کاری انجام می‌دهند؟

‏- `onRenderTracked`: نشان می‌دهد که Vue هنگام رندر شدن، چه چیزهایی را دنبال و رصد می‌کند (یعنی چه چیزی را "ردگیری" می‌کند).
‏- `onRenderTriggered`: مشخص می‌کند که چه چیزی تغییر کرده و باعث اجرای مجدد رندر شده است.

### چه زمانی باید از آن‌ها استفاده کرد؟

- زمانی که یک کامپوننت بیش از حد و بدون دلیل واضحی رندر می‌شود.
- وقتی قصد بهینه‌سازی عملکرد (Performance Debugging) را دارید.
- زمانی که مشغول بررسی کدی هستید که توسط فرد دیگری نوشته شده یا خودتان مدتی پیش نوشته‌اید و حالا علت رفتارهای آن را نمی‌دانید.

### نمونه‌ای از استفاده:

<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue';

const count = ref(۰);
const name = ref('Vue');

onRenderTracked((e) => {
console.log('[tracked]', e);
});

onRenderTriggered((e) => {
console.log('[triggered]', e);
});
</script>

<template>
<div>
<p>شمارنده: {{ count }}</p>
<p>نام: {{ name }}</p>
<button @click="count++">افزایش</button>
</div>
</template>


زمانی که روی دکمه کلیک می‌کنید، در کنسول پیغامی مشابه زیر نمایش داده می‌شود:

[triggered] { target: ..., key: "count", type: "set" }


### چه چیزی در این هوک‌ها به شما داده می‌شود؟

Vue یک شیء با فیلدهای زیر به شما تحویل می‌دهد:

‏- effect: ارجاع به افکت واکنشی (یعنی رندر مربوطه)
‏- target: شیئی که تحت نظر قرار گرفته یا باعث اجرای مجدد شده (مانند ref یا reactive)
‏- key: فیلد مشخصی که رصد شده (مثل count یا name)
‏- type: نوع عملیات (مانند get`، `set`، `add`، `delete`، `clear)
‏- oldValue و newValue (در onRenderTriggered) — مقادیر پیش و پس از تغییر

### چه کاربردهای مفیدی دارند؟

#### شناسایی رندرهای غیرضروری
فرض کنید کامپوننت شما هنگام تغییر یک متغیر رندر می‌شود، در حالی که آن متغیر اصلاً در قالب (template) استفاده نشده. در این حالت می‌توانید با استفاده از onRenderTriggered و مشاهده خروجی کنسول، بفهمید که علت رندر چیست. اگر کلید مورد نظر در قالب استفاده نشده باشد، احتمالاً باید کد را بازنگری کنید.

#### لاگ‌گیری وابستگی‌ها
می‌توانید اطلاعات ردیابی‌شده را در قالب جدول چاپ کنید تا راحت‌تر متوجه شوید کامپوننت شما چه چیزهایی را دنبال می‌کند:

onRenderTracked((e) => {
console.table({
key: e.key,
type: e.type,
target: e.target
});
});


#### تحلیل عملکرد
می‌توانید شمارنده‌ای برای تعداد دفعات اجرای onRenderTriggered قرار دهید. اگر با یک کلیک، ده‌ها بار این تابع اجرا شود، احتمال وجود یک مشکل در ساختار کامپوننت وجود دارد.

---

### خلاصه:
‏- onRenderTracked = ببین Vue هنگام رندر چه چیزی را ردیابی می‌کند.
‏- onRenderTriggered = ببین چه چیزی تغییر کرده و باعث رندر مجدد شده.
- این ابزارها برای دیباگ و بهینه‌سازی بسیار کاربردی هستند.
- فقط در حالت توسعه (dev mode) استفاده شوند.



#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍3
کامپوننت KeepAlive در Vue یک کامپوننت پوششی خاص است که اجازه می‌دهد یک کامپوننت پس از حذف شدن از DOM همچنان در حافظه باقی بماند. به‌عبارت دیگر، Vue آن را از بین نمی‌برد، بلکه به‌آرامی کنار می‌گذارد تا در صورت نیاز دوباره مورد استفاده قرار گیرد. عملکرد آن شبیه به display: none است، با این تفاوت که تمام داده‌ها، واکنش‌پذیری و وضعیت داخلی کامپوننت حفظ می‌شود.

### کاربرد رایج:
فرض کنید در برنامه خود از زبانه‌ها (Tabs) یا مسیرها (Routes) استفاده می‌کنید و می‌خواهید هنگام جابه‌جایی بین زبانه‌ها، اطلاعات واردشده، موقعیت اسکرول و سایر وضعیت‌ها حفظ شوند:

<KeepAlive>
<component :is="currentTabComponent" />
</KeepAlive>


### اگر بخواهم کنترل کنم چه چیزی کش شود، چه کنم؟

از نسخه دو ممیز یک ممیز صفر به بعد، KeepAlive دارای دو پراپرتی include و exclude است که می‌توانید نام کامپوننت‌ها را برای کش شدن یا نشدن، در آن‌ها مشخص کنید.

#### نمونه‌ها:

رشته‌ای از نام‌ها (با ویرگول جدا شده):

<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>


عبارت منظم (Regex):

<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>


آرایه‌ای از نام‌ها:

<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>


### همچنین می‌توانید از prop به نام max استفاده کنید تا مشخص کنید حداکثر چند کامپوننت در حافظه نگهداری شود:

<KeepAlive max="۲">
<component :is="currentTabComponent" />
</KeepAlive>


---

### چه چیزهایی در Vue ۳ جدید است؟

در گذشته، یک کامپوننت یا کش می‌شد یا نمی‌شد — همین! اما در Vue ۳، امکانات بیشتری برای کنترل رفتار فراهم شده است.

### رفتار جدید: هوک‌های onActivated و onDeactivated

وقتی یک کامپوننت در KeepAlive قرار می‌گیرد، برخلاف حالت عادی، Vue دیگر unmounted را اجرا نمی‌کند. در عوض، این دو هوک اجرا می‌شوند:

onActivated(() => {
console.log('من دوباره فعال شدم!');
});

onDeactivated(() => {
console.log('وقت استراحته!');
});


این یعنی شما می‌توانید:
- تایمرها را موقتاً متوقف کنید
- از منابع سنگین جدا شوید (unsubscribe)
- انیمیشن‌ها را متوقف کرده یا به حالت تعلیق درآورید

### چه زمانی بهتر است استفاده نشود؟

- زمانی که کامپوننت "ارزان" است و رندر شدن آن زمان زیادی نمی‌برد
- زمانی که حفظ وضعیت کامپوننت برایتان اهمیتی ندارد
- زمانی که می‌خواهید کامپوننت با هر بار نمایش، کاملاً از نو ساخته شود

---

### خلاصه‌ی مطالب:

‏- KeepAlive کامپوننت‌ها را در حافظه نگه می‌دارد
- از onActivated و onDeactivated به‌جای unmounted استفاده می‌کند
- امکان فیلتر کردن کامپوننت‌ها با include و exclude وجود دارد
- می‌توان با max تعداد کامپوننت‌های ذخیره‌شده را محدود کرد
- فقط با یک فرزند (یا تگ <component>) به‌درستی کار می‌کند



#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
2
گاهی اوقات هنگام نوشتن یک کامپوننت، انواع ویژگی‌ها مانند id`، `class`، `style و یا ویژگی‌هایی با پیشوند data-* را به آن ارسال می‌کنید، اما این ویژگی‌ها در خروجی نهایی ناپدید می‌شوند. چرا؟ چون Vue به‌صورت پیش‌فرض این ویژگی‌ها را به‌طور خودکار روی تمام المنت‌ها اعمال نمی‌کند. ولی موضوع به همین سادگی هم نیست — ظرافت‌هایی دارد.

### دقیقاً چه اتفاقی می‌افتد؟

Vue تمامی ویژگی‌هایی را که صراحتاً به‌عنوان props تعریف نکرده‌اید**، جمع‌آوری کرده و آن‌ها را روی **المان ریشه‌ای (Root Element) کامپوننت شما قرار می‌دهد.

#### مثال:
<MyButton class="big red" id="super-btn" />


و درون MyButton.vue چنین چیزی نوشته‌اید:

<template>
<button>کلیک کن</button>
</template>


در این صورت، ویژگی‌های class="big red" و id="super-btn" به دکمه اضافه می‌شوند.
ساده و کاربردی، نه؟

---

### اگر از ساختار سفارشی استفاده کرده باشم چه می‌شود؟

اگر نخواهید Vue به‌طور خودکار این ویژگی‌ها را به هر جایی منتقل کند و ترجیح دهید خودتان تعیین کنید این ویژگی‌ها به کدام عنصر بروند**، در این صورت می‌توانید از `v-bind="$attrs"` استفاده کنید.

#### نمونه:

<template>
<div>
<button>دکمه شماره یک</button>
<button v-bind="$attrs">دکمه شماره دو</button>
</div>
</template>



در اینجا فقط دکمه دوم ویژگی‌هایی مثل `id` و `class` را دریافت می‌کند.

---

### اگر اصلاً نخواهید Vue چیزی منتقل کند چه؟

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


<template>
<div class="wrapper">
<button v-bind="$attrs">دکمه</button>
</div>
</template>

<script setup>
defineOptions({
inheritAttrs: false
})
</script>



در این حالت، **هیچ ویژگی‌ای به عنصر ریشه‌ای (div) اضافه نمی‌شود
و تنها به دکمه منتقل خواهد شد. این کار زمانی بسیار مفید است که بخواهید استایل‌ها یا رویدادها دقیقاً روی یک عنصر تعاملی مثل دکمه اعمال شوند.

---

### نکات مهم:

- هر چیزی که prop نباشد، در $attrs قرار می‌گیرد.
- به‌صورت پیش‌فرض، تمام این ویژگی‌ها به عنصر ریشه‌ای کامپوننت افزوده می‌شوند.
- اگر می‌خواهید کنترل بیشتری داشته باشید، از inheritAttrs: false و v-bind="$attrs" استفاده کنید.
- می‌توان $attrs را هم در بخش setup و هم در template به‌کار برد.
- اگر نام یک ویژگی با نام prop یکی باشد، آن ویژگی دیگر وارد $attrs نمی‌شود.

این ویژگی‌ها به شما کمک می‌کنند کنترل دقیق‌تری بر ساختار، استایل و رفتار کامپوننت‌های خود داشته باشید — خصوصاً در کامپوننت‌های قابل استفاده مجدد و پیچیده.

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍2