آموزش جامع Vue.js
فریمورک پیشرو جاوااسکریپت برای ساخت رابطهای کاربری — تدریجی، سریع و لذتبخش برای یادگیری.
📚معرفی و نصب
Vue.js یک فریمورک تدریجی (Progressive Framework) برای ساخت رابط کاربری است. برخلاف React، نیازی به build step یا JSX ندارد و میتوان آن را دقیقاً مثل jQuery با یک تگ <script> در صفحه استفاده کرد — و بعداً در صورت نیاز به پروژهای بزرگ با Vite و Single File Component ارتقا داد.
نصب
<!-- فایل لوکال — قبل از </body> --> <script src="vue.global.min.js"></script> <!-- یا CDN --> <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
ساخت اولین اپ
<div id="app">{{ message }}</div> <script> const { createApp } = Vue; createApp({ data() { return { message: 'سلام از Vue!' }; } }).mount('#app'); </script> // یا با Composition API (توصیهشده در Vue 3) const { createApp, ref } = Vue; createApp({ setup() { const message = ref('سلام از Vue!'); return { message }; } }).mount('#app');
Vue قرار میدهد — دقیقاً مثل jQuery، بدون نیاز به import یا bundler.
⚡Reactivity پایه — ref و reactive
هستهٔ Vue سیستم Reactivity آن است: هر وقت دادهٔ reactive تغییر کند، DOM بهطور خودکار بروزرسانی میشود — بدون نیاز به دستکاری دستی DOM.
| تابع | کاربرد | خواندن/نوشتن |
|---|---|---|
| ref(value) | مقادیر ساده (عدد، رشته، بولین) | x.value |
| reactive(obj) | آبجکتها و آرایهها | obj.prop مستقیم |
| computed(fn) | مقدار مشتقشده و کششده | c.value |
| toRefs(obj) | تبدیل خواص reactive به ref های جدا | — |
const { createApp, ref, reactive } = Vue; createApp({ setup() { // ref — برای مقادیر ساده const count = ref(0); function increment() { count.value++; } // در JS با .value // reactive — برای آبجکت/آرایه const user = reactive({ name: 'علی', age: 25 }); function birthday() { user.age++; } // بدون .value return { count, increment, user, birthday }; } }).mount('#app'); // در تمپلیت: {{ count }} و {{ user.name }} — بدون .value! // Vue خودش در تمپلیت unwrap میکند
🛠Directive های اصلی
| Directive | توضیح |
|---|---|
| v-bind:attr / :attr | بایند کردن attribute (کوتاه: :) |
| v-on:event / @event | گوشدادن به رویداد (کوتاه: @) |
| v-if / v-else-if / v-else | رندر شرطی (حذف/اضافهٔ کامل عنصر) |
| v-show | نمایش شرطی با display:none (همیشه رندر میشود) |
| v-for="item in list" | حلقه روی آرایه/آبجکت |
| v-model | بایند دوطرفهٔ input/select/textarea |
| v-html | درج HTML خام (مراقب XSS باشید) |
<!-- v-bind و v-on --> <img :src="imgUrl" :alt="imgAlt"> <button @click="count++">کلیک</button> <!-- v-if / v-show --> <p v-if="isLoggedIn">خوش آمدید</p> <p v-else>لطفاً وارد شوید</p> <div v-show="visible">گاهی دیده میشود</div> <!-- v-for با key منحصربهفرد --> <li v-for="item in items" :key="item.id"> {{ item.name }} </li> <!-- کلاس/استایل شرطی --> <div :class="{ active: isActive, disabled: !enabled }"></div>
این پاراگراف با v-if کنترل میشود ✅
- {{ i+1 }}. {{ f }} (جدیدترین)
📊Computed و Watch
computed — مقدار مشتقشده و کششده
const { createApp, ref, computed } = Vue; createApp({ setup() { const first = ref('علی'); const last = ref('رضایی'); // فقط وقتی first یا last تغییر کند دوباره محاسبه میشود const fullName = computed(() => first.value + ' ' + last.value); return { first, last, fullName }; } }).mount('#app');
watch — واکنش به تغییرات
const { createApp, ref, watch } = Vue; createApp({ setup() { const question = ref(''); watch(question, (newVal, oldVal) => { console.log('تغییر کرد از', oldVal, 'به', newVal); }); // watch روی چند منبع با آرایه watch([question, /* ref دیگر */], () => { /* ... */ }); return { question }; } });
computed استفاده کنید (کش میشود). اگر باید هنگام تغییر یک مقدار یک عملیات جانبی (side effect مثل درخواست شبکه) انجام دهید، از watch استفاده کنید.
🧩کامپوننتها و Props
کامپوننتها بلوکهای قابل استفادهٔ مجددِ UI هستند. داده از والد به فرزند با props منتقل میشود، و فرزند با emit رویداد به والد خبر میدهد.
const { createApp } = Vue; // تعریف کامپوننت فرزند const StarRating = { props: { modelValue: Number }, // دریافت از والد emits: ['update:modelValue'], // اعلام رویدادهای خروجی template: ` <span v-for="n in 5" :key="n" @click="$emit('update:modelValue', n)" :style="{color: n <= modelValue ? '#f5b301' : '#ddd'}">★</span> ` }; createApp({ components: { StarRating }, setup() { const rating = Vue.ref(3); return { rating }; } }).mount('#app'); // استفاده در تمپلیت: // <star-rating v-model="rating" />
امتیاز فعلی (در والد): {{ rating }} / ۵
⌛چرخهٔ حیات (Lifecycle Hooks)
| Hook | زمان اجرا |
|---|---|
| onMounted | بعد از قرار گرفتن کامپوننت در DOM |
| onUpdated | بعد از هر بروزرسانی DOM ناشی از تغییر داده |
| onUnmounted | بعد از حذف کامپوننت از DOM (پاکسازی) |
| onBeforeMount | درست قبل از رندر اول |
const { createApp, onMounted, onUnmounted } = Vue; const Timer = { template: `<div>زمان سپریشده: {{ seconds }} ثانیه</div>`, setup() { const seconds = Vue.ref(0); let timer; onMounted(() => { timer = setInterval(() => seconds.value++, 1000); console.log('کامپوننت mount شد — تایمر شروع شد'); }); onUnmounted(() => { clearInterval(timer); // جلوگیری از memory leak console.log('کامپوننت unmount شد — تایمر پاک شد'); }); return { seconds }; } };
onMounted راهاندازی میکنید (تایمر، event listener، subscription) باید در onUnmounted پاکسازی شود تا نشتی حافظه رخ ندهد.
📝فرمها و v-model
v-model بایند دوطرفه بین input و داده ایجاد میکند — تغییر در ورودی داده را عوض میکند و برعکس.
<!-- متن --> <input v-model="name"> <!-- چکباکس (بولین) --> <input type="checkbox" v-model="agree"> <!-- select --> <select v-model="city"> <option value="tehran">تهران</option> <option value="shiraz">شیراز</option> </select> <!-- radio --> <input type="radio" value="m" v-model="gender"> <input type="radio" value="f" v-model="gender"> <!-- Modifiers مفید --> <input v-model.trim="name"> <!-- حذف فاصلهٔ اضافه --> <input v-model.number="age"> <!-- تبدیل به عدد --> <input v-model.lazy="q"> <!-- sync روی change نه input -->
⚖️Composition API در برابر Options API
Vue 3 از دو سبک نوشتن پشتیبانی میکند. هر دو معتبرند؛ Composition API برای پروژههای بزرگ و استفادهٔ مجدد منطق (composables) برتری دارد.
| ویژگی | Options API | Composition API |
|---|---|---|
| سازماندهی | بر اساس نوع (data, methods, computed) | بر اساس ویژگی/قابلیت منطقی |
| استفادهٔ مجدد منطق | Mixins (محدودیتدار) | Composables (توابع ساده) |
| یادگیری | سادهتر برای مبتدی | نیاز به آشنایی با ref/reactive |
| TypeScript | پشتیبانی متوسط | پشتیبانی عالی |
همان کامپوننت با دو سبک
// Options API createApp({ data() { return { count: 0 }; }, methods: { increment() { this.count++; } }, computed: { doubled() { return this.count * 2; } } }); // Composition API — همان منطق createApp({ setup() { const count = ref(0); const doubled = computed(() => count.value * 2); function increment() { count.value++; } return { count, doubled, increment }; } });
<script setup> (در پروژههای مبتنی بر build) را توصیه میکند. برای اسکریپتهای ساده و صفحات کوچک، Options API همچنان کاملاً مناسب است.
🔥بهترین شیوهها
۱. همیشه key منحصربهفرد در v-for
// ✗ بد — بدون key، Vue نمیتواند تغییرات را بهینه دنبال کند <li v-for="item in items">{{ item.name }}</li> // ✓ خوب — key پایدار (نه index، مگر لیست هرگز مرتب/فیلتر نشود) <li v-for="item in items" :key="item.id">{{ item.name }}</li>
۲. هرگز v-if و v-for را روی یک عنصر ننویسید
// ✗ بد — اولویت v-if بالاتر است و به item دسترسی ندارد <li v-for="item in items" v-if="item.active"></li> // ✓ خوب — فیلتر با computed، یا wrapper با template <li v-for="item in activeItems" :key="item.id"></li>
۳. Props را در فرزند تغییر ندهید
// ✗ بد — جهش مستقیم props (خطا در حالت strict) props: ['modelValue'], methods: { update() { this.modelValue = 5; } } // ✓ خوب — emit به والد و اجازه بده والد تصمیم بگیرد this.$emit('update:modelValue', 5);
| موضوع | Vue.js | Vanilla JS |
|---|---|---|
| رندر لیست | v-for + :key | innerHTML دستی یا createElement |
| بایند دوطرفه فرم | v-model | event listener + مقداردهی دستی |
| بروزرسانی UI | خودکار (reactive) | دستی با هر تغییر داده |
- نام کامپوننتها را با PascalCase یا kebab-case ثابت نگه دارید
- props را با
props: { title: String }تایپ کنید تا خطاهای زودهنگام دریافت کنید - منطق قابل استفادهٔ مجدد را در composable (تابعی با پیشوند
use) جدا کنید - از
computedبرای مقادیر مشتقشده و ازwatchفقط برای side effect استفاده کنید
🏆پروژه کامل — لیست کارها (Todo App)
ترکیب ref + v-model + v-for + computed + methods در یک اپ واقعی کوچک:
const { createApp, ref, computed } = Vue; createApp({ setup() { const newTodo = ref(''); const todos = ref([ { id: 1, text: 'یادگیری Vue', done: true }, { id: 2, text: 'ساخت پروژه', done: false } ]); function addTodo() { if (!newTodo.value.trim()) return; todos.value.push({ id: Date.now(), text: newTodo.value, done: false }); newTodo.value = ''; } function removeTodo(id) { todos.value = todos.value.filter(t => t.id !== id); } const remaining = computed(() => todos.value.filter(t => !t.done).length); return { newTodo, todos, addTodo, removeTodo, remaining }; } }).mount('#app');
{{ remaining }} کار باقیمانده از {{ todos.length }}
🚀زمین بازی کد (Playground)
کد را همینجا ویرایش کن و نتیجه را زنده ببین. یک مثال را از منوی بالا انتخاب کن، تبهای HTML و JS را تغییر بده، و دکمهٔ ▶ اجرا را بزن (یا فقط تایپ کن تا خودکار اجرا شود). از console.log() برای دیدن خروجی در پایین استفاده کن.