Vue.js Cheat Sheet
Complete reference guide for Vue.js with interactive examples and live playground links
Basic Syntax
Hello World
Basic Vue component structure
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue!'
}
}
}
</script>
<!-- Vue 3 Composition API -->
<script setup>
import { ref } from 'vue'
const message = ref('Hello, Vue 3!')
</script>
Directives
Common Vue directives
<template>
<!-- Conditional Rendering -->
<div v-if="show">Shown if show is true</div>
<div v-else-if="maybe">Shown if maybe is true</div>
<div v-else>Shown otherwise</div>
<!-- Display/hide element -->
<div v-show="isVisible">Toggles visibility</div>
<!-- List Rendering -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- Event Handling -->
<button v-on:click="handleClick">Click me</button>
<button @click="handleClick">Click me (shorthand)</button>
<!-- Two-way Binding -->
<input v-model="message" placeholder="Edit me">
<!-- Attribute Binding -->
<img v-bind:src="imageUrl" alt="Image">
<img :src="imageUrl" alt="Image (shorthand)">
<!-- Class Binding -->
<div :class="{ active: isActive, 'text-danger': hasError }">
Dynamic Classes
</div>
<!-- Style Binding -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
Dynamic Styles
</div>
</template>
Computed Properties & Watchers
Using computed properties and watchers
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe',
count: 0
}
},
computed: {
// Computed property (getter only)
fullName() {
return this.firstName + ' ' + this.lastName
},
// Getter and setter
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
},
watch: {
// Simple watcher
count(newValue, oldValue) {
console.log(`Count changed from ${oldValue} to ${newValue}`)
},
// Deep watcher
user: {
handler(newValue) {
console.log('User object changed', newValue)
},
deep: true
},
// Immediate watcher (fires on creation)
searchQuery: {
handler(newValue) {
this.fetchResults(newValue)
},
immediate: true
}
}
}
</script>
<!-- Vue 3 Composition API -->
<script setup>
import { ref, computed, watch } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// Computed property
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
// Watch single ref
watch(firstName, (newValue, oldValue) => {
console.log(`First name changed from ${oldValue} to ${newValue}`)
})
// Watch multiple sources
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log('Name changed')
})
</script>
Lifecycle Hooks
Vue component lifecycle hooks
<script>
export default {
// Creation hooks
beforeCreate() {
// Instance has been initialized, before data observation
},
created() {
// Instance created, observed data, events, computed props established
// Can access 'this' but DOM not yet mounted
},
// Mounting hooks
beforeMount() {
// Called right before mounting
},
mounted() {
// DOM mounted, can access this.$el
},
// Update hooks
beforeUpdate() {
// Called when data changes, before DOM re-render
},
updated() {
// Called after re-render happens
},
// Destruction hooks
beforeUnmount() { // Vue 3 (beforeDestroy in Vue 2)
// Called right before instance is destroyed
},
unmounted() { // Vue 3 (destroyed in Vue 2)
// Component removed from DOM
// Good for cleanup
}
}
</script>
<!-- Vue 3 Composition API -->
<script setup>
import { onMounted, onUpdated, onUnmounted, onBeforeMount,
onBeforeUpdate, onBeforeUnmount } from 'vue'
onBeforeMount(() => {
console.log('Before mount')
})
onMounted(() => {
console.log('Component is mounted')
})
onBeforeUpdate(() => {
console.log('Before update')
})
onUpdated(() => {
console.log('Component updated')
})
onBeforeUnmount(() => {
console.log('Before unmount')
})
onUnmounted(() => {
console.log('Component unmounted')
})
</script>
Components
Component Registration
Registering and using Vue components
// Global component registration (main.js)
import { createApp } from 'vue'
import App from './App.vue'
import BaseButton from './components/BaseButton.vue'
const app = createApp(App)
app.component('BaseButton', BaseButton)
app.mount('#app')
// Local component registration
<script>
import BaseButton from './components/BaseButton.vue'
export default {
components: {
BaseButton
}
}
</script>
// Vue 3 with script setup
<script setup>
import BaseButton from './components/BaseButton.vue'
// No registration needed, imported components are automatically available
</script>
Props
Passing data to child components with props
// Child component (Button.vue)
<script>
export default {
props: {
// Basic type check
text: String,
// Multiple types
id: [String, Number],
// Required prop
title: {
type: String,
required: true
},
// With default value
color: {
type: String,
default: 'primary'
},
// Object/array default (factory function)
user: {
type: Object,
default: () => ({ name: 'Guest' })
},
// Custom validator
size: {
type: String,
validator(value) {
return ['small', 'medium', 'large'].includes(value)
}
}
}
}
</script>
// Vue 3 with script setup
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
text: String,
title: {
type: String,
required: true
},
color: {
type: String,
default: 'primary'
}
})
</script>
// Parent usage
<template>
<Button
text="Click me"
title="Submit"
color="success"
:user="{ name: 'John' }"
size="medium"
/>
</template>
Events & Emits
Parent-child component communication with events
// Child component (Button.vue)
<template>
<button @click="handleClick">{{ text }}</button>
</template>
<script>
export default {
props: {
text: String
},
methods: {
handleClick() {
// Emit event to parent
this.$emit('click-event')
// With data
this.$emit('change', { id: 1, value: 'new value' })
}
},
// Vue 3: Declare emitted events
emits: ['click-event', 'change']
}
</script>
// Vue 3 with script setup
<script setup>
import { defineEmits } from 'vue'
const emits = defineEmits(['click-event', 'change'])
function handleClick() {
emits('click-event')
emits('change', { id: 1, value: 'new value' })
}
</script>
// Parent usage
<template>
<Button
text="Click me"
@click-event="handleButtonClick"
@change="handleChange"
/>
</template>
<script>
export default {
methods: {
handleButtonClick() {
console.log('Button clicked')
},
handleChange(data) {
console.log('Changed:', data)
}
}
}
</script>
Slots
Content distribution with slots
// Child component (Card.vue)
<template>
<div class="card">
<!-- Default slot -->
<slot>Default content</slot>
<!-- Named slots -->
<header>
<slot name="header">Default header</slot>
</header>
<main>
<slot name="content">Default content</slot>
</main>
<footer>
<slot name="footer">Default footer</slot>
</footer>
<!-- Scoped slots (passing data to parent) -->
<slot name="user" :user="user" :isActive="isActive">
{{ user.name }}
</slot>
</div>
</template>
<script>
export default {
data() {
return {
user: { name: 'John', email: 'john@example.com' },
isActive: true
}
}
}
</script>
// Parent usage
<template>
<Card>
<!-- Default slot content -->
<p>Some content for default slot</p>
<!-- Named slots (Vue 2.6+/Vue 3) -->
<template #header>
<h2>Card Title</h2>
</template>
<template #content>
<p>This is the main content area</p>
</template>
<template #footer>
<button>Action</button>
</template>
<!-- Scoped slot (accessing data from child) -->
<template #user="{ user, isActive }">
<span :class="{ active: isActive }">{{ user.name }} ({{ user.email }})</span>
</template>
</Card>
</template>
Advanced Features
Composition API
Vue 3 Composition API usage
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<p>Double: {{ double }}</p>
<p>User: {{ user.name }}</p>
</div>
</template>
<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
export default {
setup() {
// Reactive state
const count = ref(0)
const user = reactive({ name: 'John', role: 'Admin' })
// Methods
function increment() {
count.value++
}
// Computed properties
const double = computed(() => count.value * 2)
// Watchers
watch(count, (newValue) => {
console.log('Count changed to', newValue)
})
// Lifecycle hooks
onMounted(() => {
console.log('Component mounted')
})
// Expose to template
return {
count,
user,
increment,
double
}
}
}
</script>
// Vue 3 script setup syntax (simplified)
<script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
// State is automatically exposed to template
const count = ref(0)
const user = reactive({ name: 'John', role: 'Admin' })
// Methods
function increment() {
count.value++
}
// Computed properties
const double = computed(() => count.value * 2)
// Watchers
watch(count, (newValue) => {
console.log('Count changed to', newValue)
})
// Lifecycle hooks
onMounted(() => {
console.log('Component mounted')
})
</script>
Teleport
Teleport elements to specific DOM locations
<template>
<div>
<h1>My Vue App</h1>
<!-- Teleport content somewhere else in the DOM -->
<Teleport to="body">
<div class="modal" v-if="showModal">
<h2>Modal Title</h2>
<p>Modal content goes here</p>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
<!-- Teleport to a specific CSS selector -->
<Teleport to="#modals">
<div class="notification" v-if="showNotification">
New message received!
</div>
</Teleport>
<button @click="showModal = true">Show Modal</button>
<button @click="showNotification = true">Show Notification</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
const showNotification = ref(false)
</script>
Suspense
Handling async components with Suspense
<template>
<Suspense>
<!-- Component with async setup -->
<template #default>
<AsyncComponent />
</template>
<!-- Loading state -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// Async component
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
// AsyncComponent.vue
<template>
<div>
<h1>Async Component</h1>
<p>{{ data.message }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
// Async setup
const data = await fetchData()
async function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ message: 'Data loaded!' })
}, 2000)
})
}
</script>
Custom Directives
Creating and using custom directives
// Global directive registration (main.js)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// Simple directive
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// Directive with arguments & modifiers
app.directive('color', {
mounted(el, binding) {
// Access argument & value
const color = binding.arg || 'blue'
const value = binding.value || 'primary'
// Check for modifiers
if (binding.modifiers.bold) {
el.style.fontWeight = 'bold'
}
if (binding.modifiers.italic) {
el.style.fontStyle = 'italic'
}
// Set the color
el.style.color = `${color}-${value}`
}
})
app.mount('#app')
// Component-local directive
<script>
export default {
directives: {
highlight: {
mounted(el, binding) {
el.style.backgroundColor = binding.value || 'yellow'
}
}
}
}
</script>
// Usage
<template>
<!-- Auto-focus input -->
<input v-focus>
<!-- With argument & value -->
<p v-color:red="'dark'">Colored text</p>
<!-- With modifiers -->
<p v-color.bold.italic="'primary'">Styled text</p>
<!-- Local directive -->
<p v-highlight="'lightblue'">Highlighted text</p>
</template>
State Management
Vuex (Vue 2/3)
State management with Vuex
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
count: 0,
user: null,
loading: false
},
getters: {
doubleCount: (state) => state.count * 2,
isLoggedIn: (state) => !!state.user
},
mutations: {
INCREMENT(state, amount = 1) {
state.count += amount
},
SET_USER(state, user) {
state.user = user
},
SET_LOADING(state, status) {
state.loading = status
}
},
actions: {
// Simple action
incrementAsync({ commit }, amount) {
setTimeout(() => {
commit('INCREMENT', amount)
}, 1000)
},
// Action with async/await
async fetchUser({ commit }, userId) {
commit('SET_LOADING', true)
try {
const response = await fetch(`/api/users/${userId}`)
const user = await response.json()
commit('SET_USER', user)
} catch (error) {
console.error('Error fetching user:', error)
} finally {
commit('SET_LOADING', false)
}
}
},
// Namespaced modules
modules: {
cart: {
namespaced: true,
state: {
items: []
},
mutations: {
ADD_ITEM(state, item) {
state.items.push(item)
}
}
}
}
})
// Component usage
<script>
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// Map state properties
...mapState(['count', 'user', 'loading']),
...mapState('cart', ['items']),
// Map getters
...mapGetters(['doubleCount', 'isLoggedIn']),
// Direct access
tripleCount() {
return this.$store.state.count * 3
}
},
methods: {
// Map actions
...mapActions(['incrementAsync', 'fetchUser']),
...mapActions('cart', ['addToCart']),
// Direct commit
increment() {
this.$store.commit('INCREMENT')
},
// Direct dispatch
loadUser(id) {
this.$store.dispatch('fetchUser', id)
},
addItem(item) {
this.$store.commit('cart/ADD_ITEM', item)
}
}
}
</script>
// Composition API with Vuex
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
// State & getters
const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)
const cartItems = computed(() => store.state.cart.items)
// Actions & mutations
function increment() {
store.commit('INCREMENT')
}
function fetchUser(id) {
store.dispatch('fetchUser', id)
}
function addToCart(item) {
store.commit('cart/ADD_ITEM', item)
}
</script>
Pinia (Vue 3)
State management with Pinia (Vue 3)
// stores/counter.js
import { defineStore } from 'pinia'
// Define a store
export const useCounterStore = defineStore('counter', {
// State
state: () => ({
count: 0,
loading: false
}),
// Getters (like computed properties)
getters: {
doubleCount: (state) => state.count * 2,
// Getters that use other getters
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// Actions (like methods)
actions: {
increment() {
this.count++
},
async fetchCount() {
this.loading = true
try {
const response = await fetch('/api/count')
const data = await response.json()
this.count = data.count
} catch (error) {
console.error(error)
} finally {
this.loading = false
}
}
}
})
// Composition API setup
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Setup syntax (alternative style)
export const useUserStore = defineStore('user', () => {
// State
const user = ref(null)
const loading = ref(false)
// Getters
const isLoggedIn = computed(() => !!user.value)
const fullName = computed(() =>
user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
)
// Actions
async function login(credentials) {
loading.value = true
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
})
user.value = await response.json()
} finally {
loading.value = false
}
}
function logout() {
user.value = null
}
return {
user,
loading,
isLoggedIn,
fullName,
login,
logout
}
})
// Usage in components
<script setup>
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
const userStore = useUserStore()
// Destructure with storeToRefs to maintain reactivity
const { count, doubleCount } = storeToRefs(counterStore)
const { user, isLoggedIn, fullName } = storeToRefs(userStore)
// Actions can be destructured directly
const { increment, fetchCount } = counterStore
const { login, logout } = userStore
// Component methods
function handleLogin() {
login({ username: 'user', password: 'pass' })
}
</script>
Vue Router
Router Setup
Setting up Vue Router
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// Lazy-loaded component
component: () => import('@/views/About.vue')
},
{
// With params
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue')
},
{
// Nested routes
path: '/dashboard',
component: () => import('@/views/Dashboard.vue'),
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/dashboard/Overview.vue')
},
{
path: 'profile',
name: 'Profile',
component: () => import('@/views/dashboard/Profile.vue')
}
]
},
{
// Route with query params
path: '/search',
name: 'Search',
component: () => import('@/views/Search.vue')
},
{
// Named views
path: '/layout',
components: {
default: () => import('@/views/Default.vue'),
sidebar: () => import('@/components/Sidebar.vue'),
footer: () => import('@/components/Footer.vue')
}
},
{
// Catch-all 404 route
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router)
.mount('#app')
Navigation
Navigating with Vue Router
<template>
<!-- Router links -->
<nav>
<router-link to="/">Home</router-link>
<router-link :to="{ name: 'About' }">About</router-link>
<router-link
:to="{ name: 'User', params: { id: userId } }">
User Profile
</router-link>
<router-link
:to="{ path: '/search', query: { q: searchQuery } }">
Search
</router-link>
</nav>
<!-- Render matched component -->
<router-view></router-view>
<!-- Named views -->
<router-view name="sidebar"></router-view>
<router-view name="footer"></router-view>
<!-- Navigation buttons -->
<button @click="goToHome">Home</button>
<button @click="goToUser">User</button>
<button @click="goBack">Back</button>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const userId = ref(123)
const searchQuery = ref('vue')
// Programmatic navigation
function goToHome() {
router.push('/')
}
function goToUser() {
router.push({
name: 'User',
params: { id: userId.value }
})
}
function goBack() {
router.back()
}
// Accessing route params & query
console.log(route.params.id)
console.log(route.query.q)
</script>
Route Guards
Route navigation guards
// Global guards
router.beforeEach((to, from, next) => {
// Check authentication
const isAuthenticated = localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
// Redirect to login
next({ name: 'Login', query: { redirect: to.fullPath } })
} else {
// Proceed
next()
}
})
router.afterEach((to, from) => {
// Update document title
document.title = to.meta.title || 'Vue App'
})
// Route with meta fields
const routes = [
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: {
requiresAuth: true,
title: 'Admin Dashboard',
roles: ['admin']
}
}
]
// Per-route guard
{
path: '/profile',
name: 'Profile',
component: Profile,
beforeEnter: (to, from, next) => {
// Custom logic for this route
next()
}
}
// Component guards
export default {
name: 'UserProfile',
beforeRouteEnter(to, from, next) {
// Called before the component is created
// 'this' is not available here
fetchUserData(to.params.id).then(user => {
// Pass data to the component
next(vm => {
vm.setUser(user)
})
})
},
beforeRouteUpdate(to, from, next) {
// Called when route changes but component is reused
// e.g., /user/1 -> /user/2
// 'this' is available
this.loadUser(to.params.id)
next()
},
beforeRouteLeave(to, from, next) {
// Called when leaving the route
if (this.hasUnsavedChanges) {
const confirm = window.confirm('Discard changes?')
next(confirm)
} else {
next()
}
}
}
// Composition API route guards
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// Equivalent to beforeRouteUpdate
onBeforeRouteUpdate((to, from, next) => {
if (hasUnsavedChanges.value) {
const confirm = window.confirm('Discard changes?')
next(confirm)
} else {
next()
}
})
// Equivalent to beforeRouteLeave
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const confirm = window.confirm('Discard changes?')
next(confirm)
} else {
next()
}
})
</script>
Vue.js - Interactive Developer Reference
Hover over code blocks to copy or run in live playground