Vue.js: راهنمای جامع از پایه تا پیشرفته - فریمورک مدرن جاوااسکریپت
معرفی
Vue.js یک فریمورک جاوااسکریپتی پیشرو و
چرا Vue.js؟
· یادگیری آسان: سینتکس ساده و مشابه
· انعطافپذیری: قابل استفاده در پروژههای کوچک تا
· کارایی بالا:
· اکوسیستم غنی:
· مستندات عالی: جامعه فعال و پشتیبانی قوی
فصل اول: شروع کار با Vue.js
نصب و راهاندازی
روش ۱: استفاده از
روش ۲: استفاده از
روش ۳: استفاده از
فصل دوم: مفاهیم پایه Vue.js
فصل سوم: کامپوننتها در Vue.js
کامپوننت پایه
کامپوننت
فصل چهارم:
راهاندازی Store
استفاده از
فصل پنجم:
راهاندازی
کامپوننت
نتیجهگیری
مزایای کلیدی
· سینتکس ساده و یادگیری آسان
· عملکرد بالا با
· اکوسیستم کامل (
· مستندات عالی و جامعه فعال
· مناسب برای پروژههای کوچک تا
Vue.js: فریمورک پیشروی جاوااسکریپت
Vue.js یک فریمورک جاوااسکریپتی پیشرو و
progressive است که برای ساخت رابطهای کاربری و اپلیکیشنهای تکصفحهای (SPA) طراحی شده است. Vue.js با معماری reactive و component-based خود، توسعه اپلیکیشنهای مدرن وب را ساده و لذتبخش میکند.
چرا Vue.js؟
· یادگیری آسان: سینتکس ساده و مشابه HTML
· انعطافپذیری: قابل استفاده در پروژههای کوچک تا
enterprise
· کارایی بالا:
Virtual DOM و بهینهسازیهای پیشرفته
· اکوسیستم غنی:
Vue Router, Vuex, Pinia, Vue CLI
· مستندات عالی: جامعه فعال و پشتیبانی قوی
فصل اول: شروع کار با Vue.js
نصب و راهاندازی
روش ۱: استفاده از CDN (برای شروع سریع)
<!DOCTYPE html>
<html>
<head>
<title>My First Vue App</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<h1>{{ message }}</h1>
<button @click="count ">Count: {{ count }}</button>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
message: 'Hello Vue!',
count: 0
}
}
}).mount('#app');
</script>
</body>
</html>
روش ۲: استفاده از Vue CLI (برای پروژههای واقعی)
npm install -g @vue/cli
vue create my-project
cd my-project
npm run serve
روش ۳: استفاده از Vite (روش مدرن)
npm create vue@latest
cd my-project
npm install
npm run dev
فصل دوم: مفاهیم پایه Vue.js
Template Syntax
<div id="app">
<!-- Text Interpolation -->
<p>{{ message }}</p>
<!-- Raw HTML -->
<p v-html="rawHtml"></p>
<!-- Attribute Binding -->
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div> <!-- shorthand -->
<!-- Conditional Rendering -->
<p v-if="isVisible">This is visible</p>
<p v-else-if="type === 'A'">Type A</p>
<p v-else>Type B</p>
<!-- List Rendering -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- Event Handling -->
<button @click="handleClick">Click me</button>
<input @input="onInput" @keyup.enter="submit">
<!-- Two-way Binding -->
<input v-model="username">
<textarea v-model="message"></textarea>
<select v-model="selected">
<option value="A">Option A</option>
<option value="B">Option B</option>
</select>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
message: 'Hello Vue!',
rawHtml: '<span style="color: red">Red Text</span>',
dynamicId: 'my-div',
isVisible: true,
type: 'A',
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
],
username: '',
selected: 'A'
}
},
methods: {
handleClick() {
console.log('Button clicked!');
},
onInput(event) {
console.log('Input value:', event.target.value);
},
submit() {
console.log('Form submitted!');
}
}
}).mount('#app');
</script>
Computed Properties و Watchers
<div id="app">
<input v-model="firstName" placeholder="First name">
<input v-model="lastName" placeholder="Last name">
<p>Full Name: {{ fullName }}</p>
<p>Name Length: {{ nameLength }}</p>
<input v-model="question" placeholder="Ask a question">
<p>Answer: {{ answer }}</p>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
firstName: 'John',
lastName: 'Doe',
question: '',
answer: 'I cannot answer until you ask a question!'
}
},
computed: {
// Computed property - cached based on dependencies
fullName() {
return ${this.firstName} ${this.lastName};
},
nameLength() {
return this.fullName.length;
}
},
watch: {
// Watcher - for async operations
question(newQuestion, oldQuestion) {
if (newQuestion.includes('?')) {
this.getAnswer();
}
}
},
methods: {
async getAnswer() {
this.answer = 'Thinking...';
try {
// Simulate API call
const response = await new Promise(resolve => {
setTimeout(() => {
resolve('This is the answer!');
}, 1000);
});
this.answer = response;
} catch (error) {
this.answer = 'Error! Could not fetch the answer.';
}
}
}
}).mount('#app');
</script>
فصل سوم: کامپوننتها در Vue.js
کامپوننت پایه
<div id="app">
<h1>User Management</h1>
<user-profile
:user="currentUser"
@update-user="handleUserUpdate"
></user>
<button-counter></button>
<button-counter></button>
</div>
<script>
const { createApp } = Vue;
// User Profile Component
const UserProfile = {
template:
<div class="user-profile">
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>User Profile<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>Name: {{ user.name }}<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>Email: {{ user.email }}<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>Age: {{ user.age }}<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<button @click="updateAge">Increase Age<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
,
props: {
user: {
type: Object,
required: true
}
},
emits: ['update-user'],
methods: {
updateAge() {
this.$emit('update-user', {
...this.user,
age: this.user.age 1
});
}
}
};
// Button Counter Component
const ButtonCounter = {
template:
<button @click="count ">
You clicked me {{ count }} times
<span class="p"></</span><span class="nt">button</span><span class="p">></span>
,
data() {
return {
count: 0
}
}
};
// Main App
createApp({
components: {
UserProfile,
ButtonCounter
},
data() {
return {
currentUser: {
name: 'Alice Johnson',
email: 'alice@example.com',
age: 25
}
}
},
methods: {
handleUserUpdate(updatedUser) {
this.currentUser = updatedUser;
}
}
}).mount('#app');
</script>
کامپوننت Single File (SFC)
UserCard.vue:
<template>
<div class="user-card" :class="{ active: isActive }">
<img :src="user.avatar" :alt="user.name" class="avatar">
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p>Member since: {{ joinDate }}</p>
</div>
<button
@click="toggleActive"
:class="['btn', isActive ? 'btn-primary' : 'btn-secondary']"
>
{{ isActive ? 'Deactivate' : 'Activate' }}
</button>
</div>
</template>
<script>
export default {
name: 'UserCard',
props: {
user: {
type: Object,
required: true,
validator(value) {
return value.name && value.email;
}
}
},
data() {
return {
isActive: false
}
},
computed: {
joinDate() {
return new Date(this.user.joinDate).toLocaleDateString();
}
},
methods: {
toggleActive() {
this.isActive = !this.isActive;
this.$emit('status-change', {
userId: this.user.id,
isActive: this.isActive
});
}
},
mounted() {
console.log('UserCard component mounted');
}
}
</script>
<style scoped>
.user-card {
border: 1px solid #ddd;
padding: 1rem;
margin: 1rem 0;
border-radius: 8px;
display: flex;
align-items: center;
gap: 1rem;
}
.user-card.active {
border-color: #42b883;
background-color: #f8fff8;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
}
.user-info {
flex: 1;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background-color: #42b883;
color: white;
}
.btn-secondary {
background-color: #64748b;
color: white;
}
</style>
App.vue:
<template>
<div id="app">
<h1>User Management System</h1>
<div class="filters">
<input v-model="searchQuery" placeholder="Search users...">
<select v-model="statusFilter">
<option value="all">All</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<div class="user-list">
<user-card
v-for="user in filteredUsers"
:key="user.id"
:user="user"
@status-change="handleStatusChange"
/>
</div>
<div class="stats">
<p>Total Users: {{ totalUsers }}</p>
<p>Active Users: {{ activeUsersCount }}</p>
</div>
</div>
</template>
<script>
import UserCard from './components/UserCard.vue';
export default {
name: 'App',
components: {
UserCard
},
data() {
return {
searchQuery: '',
statusFilter: 'all',
users: [
{
id: 1,
name: 'Alice Johnson',
email: 'alice@example.com',
avatar: '/avatars/alice.jpg',
joinDate: '2023-01-15',
isActive: true
},
{
id: 2,
name: 'Bob Smith',
email: 'bob@example.com',
avatar: '/avatars/bob.jpg',
joinDate: '2023-02-20',
isActive: false
},
{
id: 3,
name: 'Carol Davis',
email: 'carol@example.com',
avatar: '/avatars/carol.jpg',
joinDate: '2023-03-10',
isActive: true
}
]
}
},
computed: {
filteredUsers() {
return this.users.filter(user => {
const matchesSearch = user.name.toLowerCase().includes(
this.searchQuery.toLowerCase()
);
const matchesStatus = this.statusFilter === 'all' ||
(this.statusFilter === 'active' && user.isActive) ||
(this.statusFilter === 'inactive' && !user.isActive);
return matchesSearch && matchesStatus;
});
},
totalUsers() {
return this.users.length;
},
activeUsersCount() {
return this.users.filter(user => user.isActive).length;
}
},
methods: {
handleStatusChange({ userId, isActive }) {
const user = this.users.find(u => u.id === userId);
if (user) {
user.isActive = isActive;
}
}
}
}
</script>
<style>
#app {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
font-family: 'Arial', sans-serif;
}
.filters {
margin: 2rem 0;
display: flex;
gap: 1rem;
}
.filters input,
.filters select {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.user-list {
margin: 2rem 0;
}
.stats {
background-color: #f5f5f5;
padding: 1rem;
border-radius: 8px;
margin-top: 2rem;
}
</style>
فصل چهارم: State Management با Pinia
راهاندازی Store
stores/userStore.js:
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
users: [],
currentUser: null,
isLoading: false,
error: null
}),
getters: {
activeUsers: (state) => state.users.filter(user => user.isActive),
userCount: (state) => state.users.length,
activeUserCount: (state) => state.activeUsers.length,
getUserById: (state) => (id) => {
return state.users.find(user => user.id === id);
}
},
actions: {
async fetchUsers() {
this.isLoading = true;
this.error = null;
try {
// Simulate API call
const response = await new Promise(resolve => {
setTimeout(() => {
resolve({
data: [
{
id: 1,
name: 'Alice Johnson',
email: 'alice@example.com',
isActive: true
},
{
id: 2,
name: 'Bob Smith',
email: 'bob@example.com',
isActive: false
}
]
});
}, 1000);
});
this.users = response.data;
} catch (error) {
this.error = 'Failed to fetch users';
console.error('Error fetching users:', error);
} finally {
this.isLoading = false;
}
},
async addUser(userData) {
try {
const newUser = {
id: Date.now(), // Simple ID generation
...userData,
isActive: true
};
this.users.push(newUser);
return newUser;
} catch (error) {
this.error = 'Failed to add user';
throw error;
}
},
async updateUser(userId, updates) {
const userIndex = this.users.findIndex(user => user.id === userId);
if (userIndex !== -1) {
this.users[userIndex] = { ...this.users[userIndex], ...updates };
}
},
async deleteUser(userId) {
this.users = this.users.filter(user => user.id !== userId);
},
setCurrentUser(user) {
this.currentUser = user;
},
clearError() {
this.error = null;
}
}
});
استفاده از Store در کامپوننت
UserManagement.vue:
<template>
<div class="user-management">
<div class="header">
<h2>User Management</h2>
<button
@click="showAddForm = !showAddForm"
class="btn btn-primary"
>
Add New User
</button>
</div>
<!-- Add User Form -->
<div v-if="showAddForm" class="add-user-form">
<h3>Add New User</h3>
<form @submit.prevent="addNewUser">
<input
v-model="newUser.name"
placeholder="Name"
required
>
<input
v-model="newUser.email"
type="email"
placeholder="Email"
required
>
<div class="form-actions">
<button type="submit" :disabled="isLoading">
{{ isLoading ? 'Adding...' : 'Add User' }}
</button>
<button type="button" @click="cancelAdd">Cancel</button>
</div>
</form>
</div>
<!-- Error Message -->
<div v-if="error" class="error-message">
{{ error }}
<button @click="clearError" class="close-btn">×</button>
</div>
<!-- Loading State -->
<div v-if="isLoading" class="loading">
Loading users...
</div>
<!-- Users List -->
<div v-else class="users-list">
<div class="stats">
<p>Total: {{ userCount }} users</p>
<p>Active: {{ activeUserCount }} users</p>
</div>
<div class="user-cards">
<div
v-for="user in users"
:key="user.id"
class="user-card"
:class="{ active: user.isActive }"
>
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p>Status: {{ user.isActive ? 'Active' : 'Inactive' }}</p>
<div class="user-actions">
<button
@click="toggleUserStatus(user.id)"
:class="['btn', user.isActive ? 'btn-warning' : 'btn-success']"
>
{{ user.isActive ? 'Deactivate' : 'Activate' }}
</button>
<button
@click="deleteUser(user.id)"
class="btn btn-danger"
>
Delete
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'pinia';
import { useUserStore } from '@/stores/userStore';
export default {
name: 'UserManagement',
data() {
return {
showAddForm: false,
newUser: {
name: '',
email: ''
}
}
},
computed: {
// Map store state and getters
...mapState(useUserStore, ['isLoading', 'error']),
...mapGetters(useUserStore, ['users', 'userCount', 'activeUserCount'])
},
methods: {
// Map store actions
...mapActions(useUserStore, [
'fetchUsers',
'addUser',
'updateUser',
'deleteUser',
'clearError'
]),
async addNewUser() {
try {
await this.addUser(this.newUser);
this.newUser = { name: '', email: '' };
this.showAddForm = false;
} catch (error) {
console.error('Error adding user:', error);
}
},
async toggleUserStatus(userId) {
const user = this.users.find(u => u.id === userId);
if (user) {
await this.updateUser(userId, { isActive: !user.isActive });
}
},
cancelAdd() {
this.showAddForm = false;
this.newUser = { name: '', email: '' };
}
},
async mounted() {
await this.fetchUsers();
}
}
</script>
<style scoped>
.user-management {
max-width: 800px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.add-user-form {
background: #f5f5f5;
padding: 1rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.add-user-form input {
display: block;
width: 100%;
margin: 0.5rem 0;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.users-list {
margin-top: 2rem;
}
.stats {
background: #e3f2fd;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.user-cards {
display: grid;
gap: 1rem;
}
.user-card {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 8px;
}
.user-card.active {
border-color: #4caf50;
background-color: #f1f8e9;
}
.user-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary { background-color: #2196f3; color: white; }
.btn-success { background-color: #4caf50; color: white; }
.btn-warning { background-color: #ff9800; color: white; }
.btn-danger { background-color: #f44336; color: white; }
.error-message {
background-color: #ffebee;
color: #c62828;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
display: flex;
justify-content: between;
align-items: center;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
</style>
فصل پنجم: Routing با Vue Router
راهاندازی Router
router/index.js:
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
import UserProfile from '@/views/UserProfile.vue';
import UserList from '@/views/UserList.vue';
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: {
title: 'Home - Vue App'
}
},
{
path: '/about',
name: 'About',
component: About,
meta: {
title: 'About Us - Vue App'
}
},
{
path: '/users',
name: 'Users',
component: UserList,
meta: {
title: 'Users - Vue App',
requiresAuth: true
}
},
{
path: '/users/:id',
name: 'UserProfile',
component: UserProfile,
props: true,
meta: {
title: 'User Profile - Vue App',
requiresAuth: true
}
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue'),
meta: {
title: 'Page Not Found - Vue App'
}
}
];
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
} else {
return { top: 0 };
}
}
});
// Global navigation guards
router.beforeEach((to, from, next) => {
// Update document title
document.title = to.meta.title || 'Vue App';
// Check for authentication
if (to.meta.requiresAuth && !isAuthenticated()) {
next({ name: 'Home' });
} else {
next();
}
});
function isAuthenticated() {
// Implement your authentication logic
return localStorage.getItem('isAuthenticated') === 'true';
}
export default router;
کامپوننت Navigation
AppNavigation.vue:
<template>
<nav class="main-nav">
<div class="nav-brand">
<router-link to="/" class="brand-link">
Vue App
</router>
</div>
<ul class="nav-links">
<li>
<router-link
to="/"
class="nav-link"
exact-active-class="active"
>
Home
</router>
</li>
<li>
<router-link
to="/about"
class="nav-link"
active-class="active"
>
About
</router>
</li>
<li>
<router-link
to="/users"
class="nav-link"
active-class="active"
>
Users
</router>
</li>
</ul>
<div class="nav-actions">
<button
v-if="isAuthenticated"
@click="logout"
class="btn btn-outline"
>
Logout
</button>
<button
v-else
@click="login"
class="btn btn-primary"
>
Login
</button>
</div>
</nav>
</template>
<script>
import { mapState } from 'pinia';
import { useAuthStore } from '@/stores/authStore';
export default {
name: 'AppNavigation',
computed: {
...mapState(useAuthStore, ['isAuthenticated'])
},
methods: {
login() {
// Implement login logic
localStorage.setItem('isAuthenticated', 'true');
window.location.reload();
},
logout() {
// Implement logout logic
localStorage.setItem('isAuthenticated', 'false');
window.location.reload();
}
}
}
</script>
<style scoped>
.main-nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background-color: #2c3e50;
color: white;
}
.nav-brand .brand-link {
font-size: 1.5rem;
font-weight: bold;
color: #42b883;
text-decoration: none;
}
.nav-links {
display: flex;
list-style: none;
gap: 2rem;
margin: 0;
padding: 0;
}
.nav-link {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.3s;
}
.nav-link:hover,
.nav-link.active {
background-color: #34495e;
}
.nav-actions {
display: flex;
gap: 1rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background-color: #42b883;
color: white;
}
.btn-outline {
background-color: transparent;
color: white;
border: 1px solid white;
}
</style>
نتیجهگیری
Vue.js یک فریمورک قدرتمند و انعطافپذیر برای ساخت اپلیکیشنهای مدرن وب است. با معماری component-based، سیستم reactive و اکوسیستم غنی، Vue.js توسعه اپلیکیشنهای پیچیده را ساده و کارآمد میکند.
مزایای کلیدی
Vue.js:
· سینتکس ساده و یادگیری آسان
· عملکرد بالا با
Virtual DOM
· اکوسیستم کامل (
Router, State Management, CLI)
· مستندات عالی و جامعه فعال
· مناسب برای پروژههای کوچک تا
enterpris
Vue.js همچنان به عنوان یکی از محبوبترین فریمورکهای جاوااسکریپت در جهان شناخته میشود و انتخاب مناسبی برای پروژههای مختلف است.
نظرات