Built for developers, by XinhND

v2.1.0

Ready

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

Vue.js
<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

Vue.js
<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

Vue.js
<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

Vue.js
<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

Vue.js
// 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

Vue.js
// 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

Vue.js
// 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

Vue.js
// 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

Vue.js
<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

Vue.js
<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

Vue.js
<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

Vue.js
// 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

Vue.js
// 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)

Vue.js
// 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

Vue.js
// 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

Vue.js
<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

Vue.js
// 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