Complete Vue 3 Tutorial
Vue 3 is a progressive JavaScript framework for building user interfaces. This tutorial will guide you from basics to advanced concepts, helping you master Vue 3's core concepts and best practices.
What is Vue 3?
Vue 3 is the latest major version of Vue.js, bringing many important improvements and new features:
- 🚀 Better performance - Smaller bundle size, faster rendering
- 🔧 Composition API - More flexible logic reuse
- 📦 Better TypeScript support - Native TypeScript support
- 🌳 Tree-shaking friendly - Import on demand, reducing bundle size
Environment Setup
Installing Node.js
Ensure your system has Node.js 16+ installed:
node --version
npm --version
Creating a Vue 3 Project
Using Vite to create a new project (recommended):
npm create vue@latest my-vue-app
cd my-vue-app
npm install
npm run dev
Or using Vue CLI:
npm install -g @vue/cli
vue create my-vue-app
cd my-vue-app
npm run serve
Basic Concepts
1. Template Syntax
Vue uses an HTML-based template syntax:
<template>
<div>
<!-- Text interpolation -->
<h1>{{ title }}</h1>
<!-- Attribute binding -->
<img :src="imageUrl" :alt="imageAlt">
<!-- Event listening -->
<button @click="handleClick">Click me</button>
<!-- Conditional rendering -->
<p v-if="isVisible">This text is visible</p>
<!-- List rendering -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
</template>
2. Reactive Data
Using ref
and reactive
to create reactive data:
<script setup>
import { ref, reactive } from 'vue'
// Use ref for primitive types
const count = ref(0)
const message = ref('Hello Vue 3!')
// Use reactive for object types
const user = reactive({
name: 'John',
age: 25,
email: 'john@example.com'
})
// Modifying data
const increment = () => {
count.value++
}
const updateUser = () => {
user.name = 'Jane'
user.age = 30
}
</script>
3. Computed Properties
Using computed
to create computed properties:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// Read-only computed property
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
// Writable computed property
const fullNameWritable = computed({
get() {
return firstName.value + ' ' + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
4. Watchers
Using watch
and watchEffect
to watch for data changes:
<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const user = reactive({ name: 'John', age: 25 })
// Watching a single data source
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// Watching multiple data sources
watch([count, () => user.name], ([newCount, newName], [oldCount, oldName]) => {
console.log('count or user.name changed')
})
// Immediate watcher
watchEffect(() => {
console.log(`Current count: ${count.value}`)
})
</script>
Component Development
1. Component Definition
<!-- UserCard.vue -->
<template>
<div class="user-card">
<img :src="user.avatar" :alt="user.name">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button @click="$emit('follow', user.id)">Follow</button>
</div>
</template>
<script setup>
// Define props
const props = defineProps({
user: {
type: Object,
required: true
}
})
// Define emits
const emit = defineEmits(['follow'])
</script>
<style scoped>
.user-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
text-align: center;
}
</style>
2. Using Components
<template>
<div>
<UserCard
v-for="user in users"
:key="user.id"
:user="user"
@follow="handleFollow"
/>
</div>
</template>
<script setup>
import UserCard from './components/UserCard.vue'
const users = ref([
{ id: 1, name: 'John', email: 'john@example.com', avatar: '/avatar1.svg' },
{ id: 2, name: 'Jane', email: 'jane@example.com', avatar: '/avatar2.svg' }
])
const handleFollow = (userId) => {
console.log(`Following user ${userId}`)
}
</script>
3. Slots
<!-- Card.vue -->
<template>
<div class="card">
<header class="card-header">
<slot name="header"></slot>
</header>
<main class="card-content">
<slot></slot>
</main>
<footer class="card-footer">
<slot name="footer"></slot>
</footer>
</div>
</template>
Using slots:
<template>
<Card>
<template #header>
<h2>Card Title</h2>
</template>
<p>This is the main content of the card</p>
<template #footer>
<button>Confirm</button>
<button>Cancel</button>
</template>
</Card>
</template>
State Management
Using Pinia
Installing Pinia:
npm install pinia
Creating a store:
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isLoggedIn = computed(() => !!user.value)
const login = async (credentials) => {
// Login logic
const response = await api.login(credentials)
user.value = response.data
}
const logout = () => {
user.value = null
}
return {
user,
isLoggedIn,
login,
logout
}
})
Using in components:
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const handleLogin = async () => {
await userStore.login({
username: 'admin',
password: '123456'
})
}
</script>
Routing
Using Vue Router
Installing Vue Router:
npm install vue-router@4
Configuring routes:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User, props: true }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
Using in components:
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view />
</template>
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const goToUser = (userId) => {
router.push(`/user/${userId}`)
}
</script>
Best Practices
1. Component Design Principles
- Single Responsibility: Each component should be responsible for one feature
- Reusability: Design generic component interfaces
- Testability: Write unit tests
2. Performance Optimization
<script setup>
import { defineAsyncComponent } from 'vue'
// Async component
const AsyncComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
// Using shallowRef for large objects
import { shallowRef } from 'vue'
const largeData = shallowRef({})
</script>
3. Code Organization
src/
├── components/ # Common components
│ ├── ui/ # UI components
│ └── business/ # Business components
├── views/ # Page components
├── stores/ # State management
├── composables/ # Composition functions
├── utils/ # Utility functions
└── assets/ # Static assets
Common Questions
Q: When should I use ref and when should I use reactive?
A:
- Use
ref
for primitive types (string, number, boolean) - Use
reactive
for objects and arrays - Use
ref
when you need to reassign the entire object
Q: How do I use TypeScript with Vue 3?
A:
<script setup lang="ts">
interface User {
id: number
name: string
email: string
}
const user = ref<User>({
id: 1,
name: 'John',
email: 'john@example.com'
})
</script>
Q: How do I optimize large list rendering?
A: Use virtual scrolling or pagination to avoid rendering too many DOM elements at once.
Summary
Vue 3 provides a powerful and flexible development experience, with the Composition API and improved performance allowing us to build better applications. By mastering these core concepts and best practices, you'll be able to efficiently develop Vue 3 applications.
Continue learning: