دستگاه شما به‌روزرسانی شد: 🔧 دستگاه
Vue.js: راهنمای جامع از پایه تا پیشرفته - فریمورک مدرن جاوااسکریپت

Vue.js: راهنمای جامع از پایه تا پیشرفته - فریمورک مدرن جاوااسکریپت

امیر بصیر 1 ماه پیش 100 بازدید مدت زمان مطالعه 8 دقیقه

مشخصات وبلاگ

آیدینویسندهتاریخ ایجاد
#66امیر بصیر دوشنبه ، 12 آبان 1404
معرفی 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: 
        &lt;div class=&quot;user-profile&quot;&gt;
            <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span>User Profile<span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Name: {{ user.name }}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Email: {{ user.email }}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Age: {{ user.age }}<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
            &lt;button @click=&quot;updateAge&quot;&gt;Increase Age<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</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: 
        &lt;button @click=&quot;count  &quot;&gt;
            You clicked me {{ count }} times
        <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</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 همچنان به عنوان یکی از محبوب‌ترین فریمورک‌های جاوااسکریپت در جهان شناخته می‌شود و انتخاب مناسبی برای پروژه‌های مختلف است.
Vuejsآموزش Vueفریمورک جاوا اسکریپتکامپوننتPiniaVue Routerاپلیکیشن تک صفحه ای

نظرات

برای ارسال نظر باید وارد شوید. ورود یا ثبت نام
هنوز نظری ثبت نشده است.
بازدید روزانه: 10
بازدید هفتگی: 133
بازدید ماهانه: 133
بازدید سالانه: 8052
0%