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

🆔@IR_javascript
Download Telegram
نمونه‌ای از "نشت" استایل‌ها در Vue 3 با استفاده از Scoped Styles

<!-- A.vue -->
<div class="a" />


<!-- B.vue -->
<div class="b">
<div class="a">
<A /> <!-- اوه، مشکل! -->
</div>
</div>


در این مثال، دو کامپوننت A.vue و B.vue هر دو از کلاسی با نام مشابه a استفاده می‌کنند. این مسئله حتی با وجود استفاده از scoped styles می‌تواند منجر به نشت استایل‌ها شود.
برای مشاهده و تست این مسئله می‌توانید از لینک زیر استفاده کنید

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

🔗https://play.vuejs.org/#eNqdUj1PwzAQ/SvWzSgZYCoFKaAOMAACRi/GuQYXx7Z8Timq+t85O7S0AlWIKfH7sN7d8xqaEKrlgDCBKeloQhKEaQiX0pk++JhEI+bR90JCVTdZKeFcOumm9ShnIR8S9sGqhHyatmYptFVEFxJeJDAkxAGovkCGG1GPfM2CbN59927kI6UPi4K0D9gyUimxzjbtrY8T0UVEx6k2JVaWsgZOILHBzU1XLcg7nrB4JGjfB2Mx3odkvCMJk/G2zClr/fttwVIc8GSL61fUb7/gC1plTMJDRMK45PXsuKRih2mkZ093uOL/Hdn7drCsPkI+Ink75Iyj7GpwLcfe05W0N6Un47pnmq0SOtoOlYNm5aboJXB510dG/457Wp0VH2+Ut1ha//E+/tZ/rtoI1YuI7f+6ZeNhs7D5BMkN50M=
#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
// useUserService.ts

function isAuthenticated() {
return !!user.value;
}


// کامپوننت A

<script>
const { isAuthenticated } = useUserService();
</script>

<template>
<p v-if="isAuthenticated()">سلام</p>
</template>


توابع در Vue رفتار متفاوتی از توابع معمولی دارند.

در این مثال، تابع isAuthenticated() مانند یک computed عمل می‌کند و هر بار که مقدار user تغییر کند یا کامپوننت A دوباره رندر شود، فراخوانی خواهد شد.

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

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


#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
1
اگر صفحه‌ای را از طریق <router-view> نمایش می‌دهید که شامل پارامترهای مسیری (مانند محصولات، کاتالوگ‌ها یا کاربران از طریق شناسه) است و باید در صورت تغییر پارامتر، اطلاعات جدیدی بارگذاری کنید، یعنی باید تغییرات پارامتر را پیگیری کنید، این کار را می‌توان به دو روش انجام داد.

از طریق watch:

watch(
() => route.params.id,
async () => {
userData.value = await fetchUser(to.params.id);
},
{ immediate: true }
);


از طریق هوک روتر onBeforeRouteUpdate:

import { onBeforeRouteUpdate } from 'vue-router'

onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id);
}
})


روش دوم این امکان را فراهم می‌آورد که از onBeforeRouteLeave نیز استفاده کنید، اگر به هر دلیلی نیاز باشد.

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
Media is too big
VIEW IN TELEGRAM
حالت بخار (Vapor Mode) در Vue.js یک استراتژی کامپایل جایگزین است که برای بهبود عملکرد برنامه‌های Vue.js طراحی شده است. این حالت به جای استفاده از Virtual DOM (VDOM)، کامپوننت‌ها را مستقیماً به HTML تبدیل می‌کند
‏vapor mode در vue 3.6 اضافه خواهد شد


#️⃣#tip #vue #dub
👥@IR_javascript_group
🆔@IR_javascript
🔥2🤯1
برای کسانی که نمی‌دانند، در VS Code (با افزونه‌ی Volar) یک فرمان کاربردی وجود دارد که امکان تقسیم یک فایل به دو پنجره بر اساس بخش‌های SFC را فراهم می‌کند.

#️⃣#tool #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
ویدیو دوبله شده در مورد بهینه‌سازی عملکرد Vue [+لینک]

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

زمان بارگذاری و عملکرد اجرایی اپلیکیشن Vue.js خود را به‌طور چشمگیری بهبود دهید.
تکنیک‌های پیشرفته بهینه‌سازی که توسط متخصصان صنعت استفاده می‌شود را اجرا کنید.
مشکلات عملکردی را به‌صورت کارآمد اشکال‌زدایی و برطرف کنید.
تصمیمات معماری آگاهانه‌ای اتخاذ کنید که بر سرعت برنامه تأثیر می‌گذارد.
تجربه‌ای استثنایی برای کاربران از طریق بهینه‌سازی عملکرد ارائه دهید.

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


🔗https://www.aparat.com/v/tdvpkja?playlist=17549421
#️⃣#tip #dub #vue
👥@IR_javascript_group
🆔@IR_javascript
👍4
در CSS**، قابلیت **@scope برای محدود کردن دامنه‌ی اعمال استایل‌ها معرفی شده است:

### 🔹 مثال:
<template> 
<div class="box">
<button class="button">Click me</button>
</div>
</template>

<style>
@scope (.box) {
.button {
color: red;
}
}
</style>

این ویژگی می‌تواند در برخی موارد مفید باشد، اما جایگزین کاملی برای Vue Scoped Styles نیست.

### 🔸 تفاوت با Vue Scoped:
- در Vue، اگر یک کامپوننت را در یک لیست رندر کنید، هر نمونه‌ی کامپوننت یک data-attribute منحصر‌به‌فرد دارد که استایل‌های آن را جدا نگه می‌دارد.
- اما در @scope**، دامنه‌ی استایل‌ها ممکن است **با سایر نمونه‌ها ترکیب شود و یکپارچگی آن حفظ نشود.

با این حال، @scope برای موارد ساده و Vanilla CSS یک قابلیت کاربردی و مفید محسوب می‌شود. 🎨

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
🔥2
اگر یک آرایه دارید که هر عنصر آن شامل یک مقدار computed است، بهتر است یک مقدار computed برای کل آرایه ایجاد کنید تا اینکه برای هر عنصر جداگانه یک مقدار computed داشته باشید.

### نمونه نامناسب
const rows = productRows.map(row => ({ 
...row,
total: computed(() => row.price * row.qty),
}));

### نمونه بهینه
const computedRows = computed(() => 
productRows.map(row => ({
...row,
total: row.price * row.qty,
}))
);

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

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
در هسته‌ی Nuxt قابلیت هیدراسیون تنبل (Lazy Hydration) اضافه شده است—پس دیگر نیازی به ساخت راهکارهای سفارشی نیست! اما بیایید قدم‌به‌قدم بررسی کنیم.

### 🚀 Vue 3.5 و ارتقای SSR
در Vue 3.5 SSR بهبود یافته و حالا کامپوننت‌های غیرهم‌زمان (Async Components) می‌توانند زمان هیدراته شدن خود را از طریق استراتژی hydrate در API `defineAsyncComponent()` کنترل کنند. مثلاً، می‌توان تعیین کرد که یک کامپوننت فقط هنگام نمایش در صفحه هیدراته شود:
import { defineAsyncComponent, hydrateOnVisible } from 'vue';

const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnVisible()
});

### 🔥 Nuxt 3.16 و پشتیبانی از Delayed Hydration
📅 در ۷ مارس ۲۰۲۵ نسخه‌ی Nuxt 3.16 منتشر شد که شامل ویژگی‌های جدید، بهبود عملکرد، ابزارهای توسعه‌ی بهتر و... بود، اما مهم‌ترین قابلیت اضافه‌شده، Delayed Hydration است. توسعه‌دهندگان Nuxt در وبلاگ خود جزئیات این قابلیت را توضیح داده‌اند.

#### مثال: هیدراته شدن کامپوننت هنگام ورود به ویوپورت
<!-- کامپوننت تنها زمانی هیدراته می‌شود که در محدوده‌ی دید کاربر قرار بگیرد -->
<LazyExpensiveComponent hydrate-on-visible />

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

🔹 همچنین می‌توان از hook جدید `hydrated` برای اجرای دستورات خاص پس از هیدراته شدن کامپوننت استفاده کرد:
<LazyComponent hydrate-on-visible @hydrated="onComponentHydrated" />

### 📌 چه خبر؟
🔹 این ویژگی تنظیمات مختلفی دارد که می‌توانید نحوه‌ی کارکرد آن را در مستندات Nuxt مطالعه کنید.
🔹 Nuxt به‌سرعت در حال پیشرفت است و وقت آن رسیده که این قابلیت را در محیط Production تست کنیم.
🔹 شاید وقتش رسیده که کامپوننت‌های سمت سرور (Server Components) را هم امتحان کنیم؟ (قابلیت آزمایشی nuxt-islands – اما این بحث را می‌گذاریم برای بعد 😉)

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
### چرا باید از استفاده کنترل‌نشده از watch پرهیز کرد؟

#### یک. برهم خوردن جریان واکنشی (Reactive Flow)
استفاده از watch گاهی باعث پنهان شدن منطق واکنش‌گرایی می‌شود. برای مثال:

watch(() => userId.value, async (newId) => {
const data = await fetchUserData(newId);
userData.value = data;
});


در ظاهر ساده است، اما وقتی همین کد در چند مؤلفه با داده‌های مشابه یا اثرات جانبی دیگر تکرار شود، کنترل جریان داده‌ها سخت‌تر شده و رفتار برنامه غیرقابل پیش‌بینی می‌شود.

Vue معماری واکنش‌گرای یک‌طرفه (از داده به UI) را ترویج می‌دهد، اما watch اغلب برای تبدیل داده به داده استفاده می‌شود که جریان را پیچیده می‌کند و اشکال‌زدایی را دشوارتر می‌سازد.

#### دو. کاهش خوانایی و دشواری در نگه‌داری

وقتی در یک مؤلفه چندین watch به‌صورت زنجیره‌ای نوشته شوند، ساختار کد به‌شکل کلاف سردرگمی درمی‌آید که فهم آن زمان‌بر است:

watch(() => settings.value.theme, applyTheme);
watch(() => settings.value.language, loadTranslations);
watch(() => settings.value.notifications, updateNotifications);


این نوع پراکندگی منطق در `watch`ها:

- خوانایی کد را پایین می‌آورد
- بازسازی (refactor) و انتقال منطق به composableها را دشوار می‌کند

در مقابل، می‌توان این رفتارها را در یک computed مجتمع یا composable مشخص و مستقل تعریف کرد.

#### سه. تأثیر منفی بر عملکرد

مثلاً اگر یک شیء عمیق را با deep: true مانیتور کنید:

watch(settings, (newVal, oldVal) => {
// مقایسه عمیق کل ساختار
}, { deep: true });


اگر settings شامل اشیاء یا آرایه‌های بزرگ باشد، عملکرد برنامه افت خواهد کرد. از طرفی، بسیاری از کاربردهای watch را می‌توان با computed حل کرد:

const isDarkTheme = computed(() => settings.value.theme === 'dark');


ویژگی‌های محاسبه‌شونده کش می‌شوند، سبک‌تر هستند و اجرای بهتری دارند.

#### چهار. خطر نشت حافظه (Memory Leak)

در مؤلفه‌هایی که با شرط v-if نمایش داده می‌شوند، اگر watch بدون مدیریت درست ایجاد شود:

<template>
<div v-if="showComponent">
<SomeComponent />
</div>
</template>


و در SomeComponent بنویسیم:

watch(async() => someReactiveValue.value, await doSomething);


اگر این watch هنگام نابودی مؤلفه (unmount) لغو نشود، به‌مرور زمان منابع حافظه را اشغال کرده و باعث نشت حافظه می‌شود.

---

### راهکار پیشنهادی

قبل از استفاده از `watch`، ابتدا این پرسش را از خودتان بپرسید:

> «آیا می‌توان این مسئله را با `computed`، `props` یا emit حل کرد؟»

در بسیاری از موارد پاسخ *بله* است، و انتخاب راه‌حل‌های اعلانی (declarative) باعث خوانایی، عملکرد بهتر و مدیریت ساده‌تر خواهد شد.



#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍1
کلون‌کردن عمیق اشیاء واکنش‌پذیر در Vue 3

Vue 3 از *Proxy* برای واکنش‌پذیری استفاده می‌کند، که این مسئله زمانی که بخواهید اشیاء را کلون کنید، مشکلاتی ایجاد می‌کند. روش‌های استاندارد به صورت مورد انتظار عمل نمی‌کنند:

const state = reactive({ user: { name: "Al" } });
// مشکلات:
const badCopy1 = { ...state }; // حفظ ارجاع به Proxy
const badCopy2 = JSON.parse(JSON.stringify(state)); // از دست دادن متدها و Proxy


استفاده از عملگر `...` یا مشابه آن:
اگر شما از عملگر ... برای کپی کردن یک شیء واکنش‌پذیر استفاده کنید، این کپی فقط ارجاع به *Proxy* اصلی را می‌گیرد، نه خود داده‌ها. به عبارت دیگر، تغییرات در شیء جدید می‌تواند بر شیء اصلی تأثیر بگذارد.


استفاده از `JSON.stringify` و `JSON.parse`:
وقتی از JSON.stringify برای تبدیل شیء به یک رشته و سپس از JSON.parse برای بازگشایی آن استفاده می‌کنید، متدهای شیء واکنش‌پذیر از بین می‌روند و همچنین ارتباط با *Proxy* قطع می‌شود. این به این معناست که ویژگی‌های واکنش‌پذیری از دست می‌روند و این کپی دیگر به صورت واکنش‌پذیر نخواهد بود.


### چرا این اتفاق می‌افتد؟
Vue 3 از *Proxy* برای ایجاد واکنش‌پذیری استفاده می‌کند. *Proxy* در واقع یک "نظارت‌کننده" بر روی اشیاء است که هر تغییر در داده‌ها را شبیه‌سازی می‌کند و به Vue اطلاع می‌دهد تا رابط کاربری به‌روز شود. زمانی که شما از روش‌های استاندارد کپی استفاده می‌کنید، این ویژگی‌های *Proxy* نادیده گرفته می‌شوند و به همین دلیل نمی‌توانید از کپی‌های انجام‌شده به‌طور کامل استفاده کنید.

سه روش کاربردی برای این کار:

1. ترکیب toRaw و structuredClone

import { toRaw } from 'vue';
const original = reactive({ data: 123 });
const copy = structuredClone(toRaw(original));


2. کپی‌کردن عمیق دستی

function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const clone = Array.isArray(obj) ? [] : {};
for (const key in obj) {
clone[key] = deepClone(obj[key]);
}
return clone;
}
const copy = reactive(deepClone(toRaw(original)));


3. استفاده از کتابخانه‌های موجود

import { cloneDeep } from 'lodash-es';
const copy = reactive(cloneDeep(toRaw(obj)));


این روش‌ها به شما کمک می‌کنند تا اشیاء واکنش‌پذیر را به طور صحیح کلون کنید بدون اینکه ارجاع به *Proxy* حفظ شود یا متدهای آن از بین بروند.

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍2
در این پست، 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
اگر در یک اپلیکیشن چندین تابع غیرهمزمان (مانند بارگذاری داده‌ها از بک‌اند) به‌طور همزمان اجرا می‌شود و نیاز به نمایش یک انیمیشن پیش‌لودر واحد داریم، می‌توانیم به‌راحتی این کار را از طریق یک تابع 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معماری ماژولار (ماژول‌محور) است.

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

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

را تعریف کرد.

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

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

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

---

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

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

---

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

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

#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
👍5🔥2👎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
کتاب Chibi Vue نوشته‌ی یویچی کیکوچی، راهنمایی جامع و دقیق درباره‌ی ساختار درونی فریم‌ورک Vue ۳ است. این کتاب به‌ویژه برای توسعه‌دهندگانی که به‌صورت عمیق در حال یادگیری Vue هستند، بسیار سودمند است.

در این کتاب می‌خوانید:

تحلیل جامع هسته‌ی Vue، شامل: سامانه‌ی واکنش‌پذیری، کامپایلر قالب‌ها، و سیستم رندرینگ.

پیاده‌سازی عملی و گام‌به‌گام نسخه‌ای ساده‌شده از Vue از ابتدا.

زبان این کتاب انگلیسی است.
🔗https://book.chibivue.land/
#️⃣#tip #vue
👥@IR_javascript_group
🆔@IR_javascript
متوقف کردن یک Watcher

Watcher**هایی که به‌صورت هم‌زمان (synchronous) درون تابع `setup()` یا در قالب `<script setup>` تعریف می‌شوند، به نمونه (instance) کامپوننت مالک وابسته هستند و به‌طور خودکار هنگام **unmount شدن آن کامپوننت متوقف خواهند شد. در اغلب موارد، نیازی نیست نگران متوقف کردن دستی آن‌ها باشید.

نکته‌ی کلیدی در این‌جاست که Watcher باید به‌صورت هم‌زمان ایجاد شود؛ اگر Watcher در یک تابع غیرهم‌زمان (asynchronous) مانند setTimeout ایجاد گردد، دیگر به کامپوننت والد وابسته نخواهد بود و باید به‌صورت دستی متوقف شود تا از نشت حافظه (memory leak) جلوگیری شود. به مثال زیر توجه کنید:
<script setup>
import { watchEffect } from 'vue'

// این Watcher به‌صورت خودکار متوقف خواهد شد
watchEffect(() => {})

// ...اما این یکی نه!
setTimeout(() => {
watchEffect(() => {})
}, ۱۰۰)
</script>

برای متوقف کردن دستی یک Watcher**، می‌توان از تابع بازگشتی استفاده کرد. این روش هم برای `watch` و هم برای `watchEffect` کاربرد دارد:


const unwatch = watchEffect(() => {})

// ...در زمانی دیگر، زمانی که دیگر نیازی نیست
unwatch()


توجه داشته باشید که تنها در موارد بسیار محدود نیاز به ایجاد **Watcher
به‌صورت غیرهم‌زمان وجود دارد، و در هر زمان ممکن، بهتر است از ایجاد هم‌زمان استفاده شود.

اگر نیاز دارید منتظر داده‌ای غیرهم‌زمان بمانید، می‌توانید منطق Watcher خود را مشروط طراحی کنید:

// داده‌ای که به‌صورت غیرهم‌زمان بارگذاری می‌شود
const data = ref(null)

watchEffect(() => {
if (data.value) {
// انجام عملیات زمانی که داده بارگذاری شد
}
})


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