Skip to content

cheatsheetz/vue

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Vue.js Cheat Sheet

A comprehensive reference for Vue.js - a progressive JavaScript framework for building user interfaces with reactive data binding and component-based architecture.


Table of Contents


Basic Setup

Installation

# Vue CLI
npm install -g @vue/cli
vue create my-project

# Vite (recommended)
npm create vue@latest my-project

# CDN
<script src="https://unpkg.com/vue@next"></script>

Basic Vue App

// main.js
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Vue Instance

Creating Vue App

// Options API
const { createApp } = Vue

createApp({
  data() {
    return {
      message: 'Hello Vue!',
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}).mount('#app')

Template Syntax

Text Interpolation

<template>
  <!-- Text interpolation -->
  <p>{{ message }}</p>
  
  <!-- Raw HTML -->
  <p v-html="rawHtml"></p>
  
  <!-- Attributes -->
  <div v-bind:id="dynamicId"></div>
  <div :id="dynamicId"></div> <!-- shorthand -->
  
  <!-- JavaScript expressions -->
  <p>{{ count + 1 }}</p>
  <p>{{ message.split('').reverse().join('') }}</p>
  <p>{{ user ? user.name : 'Guest' }}</p>
</template>

Directives

Basic Directives

<template>
  <!-- v-if conditional rendering -->
  <p v-if="show">Visible when show is true</p>
  <p v-else-if="maybeShow">Maybe visible</p>
  <p v-else>Fallback content</p>
  
  <!-- v-show (CSS display toggle) -->
  <p v-show="isVisible">Toggle visibility</p>
  
  <!-- v-for list rendering -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
  
  <!-- v-for with index -->
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }} - {{ item.name }}
  </li>
  
  <!-- v-for with objects -->
  <li v-for="(value, key) in user" :key="key">
    {{ key }}: {{ value }}
  </li>
  
  <!-- v-on event handling -->
  <button v-on:click="handleClick">Click me</button>
  <button @click="handleClick">Click me (shorthand)</button>
  <input @input="handleInput" @keyup.enter="handleEnter" />
  
  <!-- v-model two-way binding -->
  <input v-model="message" type="text" />
  <textarea v-model="text"></textarea>
  <input v-model="checked" type="checkbox" />
  <select v-model="selected">
    <option value="a">A</option>
    <option value="b">B</option>
  </select>
  
  <!-- v-bind class and style -->
  <div :class="{ active: isActive, 'text-danger': hasError }">
  <div :class="[activeClass, errorClass]">
  <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
  
  <!-- v-pre -->
  <span v-pre>{{ this will not be compiled }}</span>
  
  <!-- v-once -->
  <h1 v-once>{{ title }}</h1>
</template>

Custom Directives

// Global directive
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

// Local directive
directives: {
  focus: {
    mounted(el) {
      el.focus()
    }
  }
}

// Usage
<input v-focus />

Components

Single File Component

<template>
  <div class="greeting">
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
    <button @click="updateMessage">Update</button>
  </div>
</template>

<script>
export default {
  name: 'Greeting',
  data() {
    return {
      title: 'Hello Vue!',
      message: 'Welcome to Vue.js'
    }
  },
  methods: {
    updateMessage() {
      this.message = 'Message updated!'
    }
  }
}
</script>

<style scoped>
.greeting {
  padding: 20px;
  background: #f0f0f0;
}

h1 {
  color: #42b883;
}
</style>

Component Registration

// Global registration
import MyComponent from './components/MyComponent.vue'
app.component('MyComponent', MyComponent)

// Local registration
export default {
  components: {
    MyComponent
  }
}

Props and Events

Props

<!-- Parent Component -->
<template>
  <ChildComponent 
    :title="parentTitle"
    :user="currentUser"
    :items="list"
  />
</template>

<!-- Child Component -->
<script>
export default {
  props: {
    title: String,
    user: Object,
    items: Array
  },
  // Or with validation
  props: {
    title: {
      type: String,
      required: true,
      default: 'Default Title'
    },
    age: {
      type: Number,
      validator(value) {
        return value >= 0
      }
    }
  }
}
</script>

Events

<!-- Child Component -->
<template>
  <button @click="handleClick">Click me</button>
</template>

<script>
export default {
  emits: ['custom-event'],
  methods: {
    handleClick() {
      this.$emit('custom-event', 'Hello from child!')
    }
  }
}
</script>

<!-- Parent Component -->
<template>
  <ChildComponent @custom-event="handleCustomEvent" />
</template>

<script>
export default {
  methods: {
    handleCustomEvent(message) {
      console.log(message)
    }
  }
}
</script>

Composition API

Basic Setup

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <p>Double: {{ doubleCount }}</p>
  </div>
</template>

<script>
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    // Reactive state
    const count = ref(0)
    
    // Computed property
    const doubleCount = computed(() => count.value * 2)
    
    // Method
    const increment = () => {
      count.value++
    }
    
    // Lifecycle hook
    onMounted(() => {
      console.log('Component mounted')
    })
    
    // Return to template
    return {
      count,
      doubleCount,
      increment
    }
  }
}
</script>

Composition API with Script Setup

<template>
  <div>
    <p>{{ message }}</p>
    <input v-model="inputValue" />
    <UserCard :user="user" @update="handleUpdate" />
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import UserCard from './UserCard.vue'

// Props
const props = defineProps({
  initialMessage: String
})

// Emits
const emit = defineEmits(['change'])

// Reactive state
const message = ref(props.initialMessage || 'Hello')
const inputValue = ref('')
const user = ref({ name: 'John', age: 30 })

// Computed
const uppercaseMessage = computed(() => message.value.toUpperCase())

// Watcher
watch(inputValue, (newValue) => {
  console.log('Input changed:', newValue)
  emit('change', newValue)
})

// Methods
const handleUpdate = (data) => {
  user.value = { ...user.value, ...data }
}

// Lifecycle
onMounted(() => {
  console.log('Component mounted')
})
</script>

Composables (Custom Composition Functions)

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  const isEven = computed(() => count.value % 2 === 0)
  
  return {
    count,
    increment,
    decrement,
    reset,
    isEven
  }
}

// Usage in component
<script setup>
import { useCounter } from '@/composables/useCounter'

const { count, increment, decrement, isEven } = useCounter(10)
</script>

Reactivity

Reactive References

import { ref, reactive, toRefs } from 'vue'

export default {
  setup() {
    // ref for primitives
    const count = ref(0)
    const message = ref('Hello')
    
    // reactive for objects
    const state = reactive({
      user: { name: 'John', age: 30 },
      items: [1, 2, 3]
    })
    
    // Convert reactive object to refs
    const { user, items } = toRefs(state)
    
    return {
      count,
      message,
      state,
      user,
      items
    }
  }
}

Computed Properties

Options API

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`
    },
    // Computed with getter and setter
    fullNameGS: {
      get() {
        return `${this.firstName} ${this.lastName}`
      },
      set(value) {
        const [firstName, lastName] = value.split(' ')
        this.firstName = firstName
        this.lastName = lastName
      }
    }
  }
}

Composition API

import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`
    })
    
    // Writable computed
    const fullNameWritable = computed({
      get: () => `${firstName.value} ${lastName.value}`,
      set: (value) => {
        const [first, last] = value.split(' ')
        firstName.value = first
        lastName.value = last
      }
    })
    
    return { firstName, lastName, fullName, fullNameWritable }
  }
}

Watchers

Options API

export default {
  data() {
    return {
      question: '',
      answer: 'I cannot answer until you ask a question!'
    }
  },
  watch: {
    // Simple watcher
    question(newQuestion, oldQuestion) {
      if (newQuestion.includes('?')) {
        this.getAnswer()
      }
    },
    
    // Deep watcher for objects
    user: {
      handler(newUser, oldUser) {
        console.log('User changed:', newUser)
      },
      deep: true
    },
    
    // Immediate execution
    searchTerm: {
      handler(newTerm) {
        this.performSearch(newTerm)
      },
      immediate: true
    }
  }
}

Composition API

import { ref, watch, watchEffect } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = ref({ name: 'John', age: 30 })
    
    // Basic watcher
    watch(count, (newCount, oldCount) => {
      console.log(`Count changed from ${oldCount} to ${newCount}`)
    })
    
    // Watch multiple sources
    watch([count, user], ([newCount, newUser], [oldCount, oldUser]) => {
      console.log('Count or user changed')
    })
    
    // Deep watcher
    watch(user, (newUser) => {
      console.log('User changed:', newUser)
    }, { deep: true })
    
    // watchEffect - automatically tracks dependencies
    watchEffect(() => {
      console.log(`Count is ${count.value}`)
    })
    
    return { count, user }
  }
}

Lifecycle Hooks

Options API vs Composition API

// Options API
export default {
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created')
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted() {
    console.log('mounted')
  },
  beforeUpdate() {
    console.log('beforeUpdate')
  },
  updated() {
    console.log('updated')
  },
  beforeUnmount() {
    console.log('beforeUnmount')
  },
  unmounted() {
    console.log('unmounted')
  }
}

// Composition API
import { 
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      console.log('beforeMount')
    })
    
    onMounted(() => {
      console.log('mounted')
    })
    
    onBeforeUpdate(() => {
      console.log('beforeUpdate')
    })
    
    onUpdated(() => {
      console.log('updated')
    })
    
    onBeforeUnmount(() => {
      console.log('beforeUnmount')
    })
    
    onUnmounted(() => {
      console.log('unmounted')
    })
  }
}

Vue Router

Setup

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/about', name: 'About', component: About },
  { path: '/user/:id', name: 'User', component: User },
  { 
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      // Route guard
      if (isAuthenticated) {
        next()
      } else {
        next('/login')
      }
    }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

// main.js
import { createApp } from 'vue'
import router from './router'
import App from './App.vue'

createApp(App).use(router).mount('#app')

Router Usage

<template>
  <div>
    <!-- Router links -->
    <router-link to="/">Home</router-link>
    <router-link :to="{ name: 'User', params: { id: 123 } }">User</router-link>
    
    <!-- Router view -->
    <router-view />
  </div>
</template>

<script>
export default {
  methods: {
    navigateToUser() {
      // Programmatic navigation
      this.$router.push('/user/456')
      this.$router.push({ name: 'User', params: { id: 456 } })
      this.$router.replace('/new-path')
      this.$router.go(-1) // Go back
    }
  },
  created() {
    // Access route information
    console.log(this.$route.params.id)
    console.log(this.$route.query)
    console.log(this.$route.path)
  }
}
</script>

Vuex/Pinia State Management

Pinia (Recommended)

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Eduardo'
  }),
  
  getters: {
    doubleCount: (state) => state.count * 2,
    getUserById: (state) => {
      return (userId) => state.users.find(user => user.id === userId)
    }
  },
  
  actions: {
    increment() {
      this.count++
    },
    async fetchUser(id) {
      const user = await api.getUser(id)
      this.users.push(user)
    }
  }
})

// Component usage
<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

// Access state
console.log(counter.count)

// Access getters
console.log(counter.doubleCount)

// Call actions
counter.increment()
</script>

Vuex (Legacy)

// store/index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    count: 0,
    user: null
  },
  
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    SET_USER(state, user) {
      state.user = user
    }
  },
  
  actions: {
    async fetchUser({ commit }, userId) {
      const user = await api.getUser(userId)
      commit('SET_USER', user)
    }
  },
  
  getters: {
    doubleCount: state => state.count * 2,
    isLoggedIn: state => !!state.user
  }
})

// Component usage
export default {
  computed: {
    count() {
      return this.$store.state.count
    },
    doubleCount() {
      return this.$store.getters.doubleCount
    }
  },
  methods: {
    increment() {
      this.$store.commit('INCREMENT')
    },
    fetchUser(id) {
      this.$store.dispatch('fetchUser', id)
    }
  }
}

Forms

Form Handling

<template>
  <form @submit.prevent="handleSubmit">
    <!-- Text input -->
    <input 
      v-model="form.name"
      type="text"
      placeholder="Name"
      :class="{ error: errors.name }"
    />
    <span v-if="errors.name" class="error">{{ errors.name }}</span>
    
    <!-- Email input -->
    <input 
      v-model="form.email"
      type="email"
      placeholder="Email"
    />
    
    <!-- Checkbox -->
    <label>
      <input v-model="form.subscribe" type="checkbox" />
      Subscribe to newsletter
    </label>
    
    <!-- Radio buttons -->
    <div>
      <label>
        <input v-model="form.gender" value="male" type="radio" />
        Male
      </label>
      <label>
        <input v-model="form.gender" value="female" type="radio" />
        Female
      </label>
    </div>
    
    <!-- Select -->
    <select v-model="form.country">
      <option value="">Select Country</option>
      <option v-for="country in countries" :key="country.code" :value="country.code">
        {{ country.name }}
      </option>
    </select>
    
    <!-- Multiple select -->
    <select v-model="form.skills" multiple>
      <option value="js">JavaScript</option>
      <option value="vue">Vue.js</option>
      <option value="react">React</option>
    </select>
    
    <!-- File input -->
    <input @change="handleFileChange" type="file" accept="image/*" />
    
    <button type="submit" :disabled="!isFormValid">Submit</button>
  </form>
</template>

<script setup>
import { reactive, computed } from 'vue'

const form = reactive({
  name: '',
  email: '',
  subscribe: false,
  gender: '',
  country: '',
  skills: [],
  file: null
})

const errors = reactive({})

const countries = ref([
  { code: 'us', name: 'United States' },
  { code: 'ca', name: 'Canada' }
])

const isFormValid = computed(() => {
  return form.name && form.email && !Object.keys(errors).length
})

const validateField = (field, value) => {
  switch (field) {
    case 'name':
      if (!value) {
        errors.name = 'Name is required'
      } else {
        delete errors.name
      }
      break
    case 'email':
      if (!value || !value.includes('@')) {
        errors.email = 'Valid email is required'
      } else {
        delete errors.email
      }
      break
  }
}

const handleFileChange = (event) => {
  form.file = event.target.files[0]
}

const handleSubmit = async () => {
  // Validate form
  validateField('name', form.name)
  validateField('email', form.email)
  
  if (!isFormValid.value) return
  
  try {
    await submitForm(form)
    // Reset form
    Object.assign(form, {
      name: '',
      email: '',
      subscribe: false,
      gender: '',
      country: '',
      skills: [],
      file: null
    })
  } catch (error) {
    console.error('Submit error:', error)
  }
}
</script>

Transitions

Basic Transitions

<template>
  <div>
    <button @click="show = !show">Toggle</button>
    
    <!-- Basic transition -->
    <transition name="fade">
      <p v-if="show">Hello Vue!</p>
    </transition>
    
    <!-- List transitions -->
    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item.id" class="list-item">
        {{ item.text }}
      </li>
    </transition-group>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const show = ref(true)
const items = ref([
  { id: 1, text: 'Item 1' },
  { id: 2, text: 'Item 2' }
])
</script>

<style>
/* Fade transition */
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

/* List transitions */
.list-enter-active {
  transition: all 0.5s ease;
}
.list-leave-active {
  transition: all 0.5s ease;
  position: absolute;
}

.list-enter-from {
  opacity: 0;
  transform: translateX(30px);
}
.list-leave-to {
  opacity: 0;
  transform: translateX(-30px);
}

.list-move {
  transition: transform 0.5s ease;
}
</style>

Best Practices

Component Design

<!-- Good: Single File Component Structure -->
<template>
  <!-- Use semantic HTML -->
  <article class="user-card">
    <header class="user-card__header">
      <h2>{{ user.name }}</h2>
    </header>
    <div class="user-card__content">
      <UserAvatar :src="user.avatar" :size="'medium'" />
      <UserDetails :user="user" />
    </div>
  </article>
</template>

<script setup>
// Import statements
import { computed } from 'vue'
import UserAvatar from './UserAvatar.vue'
import UserDetails from './UserDetails.vue'

// Props with validation
const props = defineProps({
  user: {
    type: Object,
    required: true,
    validator: (user) => user && user.id && user.name
  }
})

// Emits declaration
const emit = defineEmits(['update-user', 'delete-user'])

// Computed properties
const userInitials = computed(() => {
  return props.user.name.split(' ').map(n => n[0]).join('')
})
</script>

<style scoped>
.user-card {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 1rem;
}

.user-card__header {
  margin-bottom: 1rem;
}
</style>

Performance Tips

// Use shallowRef for large objects that don't need deep reactivity
import { shallowRef } from 'vue'
const largeObject = shallowRef({ /* large data */ })

// Use markRaw for objects that should never be reactive
import { markRaw } from 'vue'
const nonReactiveObject = markRaw({ /* data */ })

// Lazy load components
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue'))

Common Patterns

Pattern Use Case Example
Slot Pattern Flexible content distribution <slot name="header" />
Provide/Inject Dependency injection provide('theme', theme)
Mixins Code reuse (Options API) mixins: [validationMixin]
Composables Logic reuse (Composition API) useCounter(), useApi()

Build and Deployment

Build Commands

# Development
npm run dev

# Production build
npm run build

# Preview production build
npm run preview

# Environment variables
VITE_API_URL=https://api.example.com npm run build

Resources


Originally compiled from various sources. Contributions welcome!

About

Repo for vue

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published