Summary
Vue.js is a progressive JavaScript framework for building user interfaces that focuses on the view layer and can be incrementally adopted. Created by Evan You, Vue combines the best features of React (virtual DOM, component-based architecture) and Angular (templates, directives) while maintaining simplicity and flexibility. Vue 3, the latest major version, introduces the Composition API for better logic reuse and TypeScript support, while maintaining backward compatibility with the Options API. Its reactive data binding system, single-file components, and gentle learning curve make it an excellent choice for projects ranging from simple widgets to complex SPAs. With a rich ecosystem including Vue Router, Pinia (state management), and Vue DevTools, Vue provides everything needed for modern web development. This cheatsheet covers Vue fundamentals, Composition API, reactivity system, and best practices for technical interviews.
Core Concepts
Vue Instance
// Vue 3 - Composition API
import { createApp } from 'vue'
const app = createApp({
data() {
return { message: 'Hello Vue!' }
}
})
app.mount('#app')
// Vue 2 - Options API
new Vue({
el: '#app',
data: { message: 'Hello Vue!' }
})
Template Syntax
<!-- Interpolation -->
<span>{{ message }}</span>
<!-- v-html (renders raw HTML) -->
<div v-html="rawHtml"></div>
<!-- Expressions -->
<div>{{ number + 1 }}</div>
<div>{{ ok ? 'YES' : 'NO' }}</div>
Directives
Essential Directives
<!-- v-if / v-else-if / v-else -->
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>Not A/B</div>
<!-- v-show (toggles display) -->
<h1 v-show="isVisible">Hello!</h1>
<!-- v-for -->
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.text }}
</li>
<!-- v-model (two-way binding) -->
<input v-model="message" />
<input v-model.trim="msg" />
<input v-model.number="age" type="number" />
<input v-model.lazy="msg" />
<!-- v-on (@ shorthand) -->
<button @click="doThis"></button>
<button @click.stop="doThis"></button>
<button @click.prevent="doThis"></button>
<button @click.once="doThis"></button>
<!-- v-bind (: shorthand) -->
<img :src="imageSrc" />
<div :class="{ active: isActive }"></div>
<div :style="{ color: activeColor }"></div>
v-if vs v-show
- v-if: Conditional rendering (creates/destroys elements)
- v-show: Always rendered, toggles CSS display
- Use v-show for frequent toggles
Components
Component Registration
// Global Registration
app.component('my-component', {
template: '<div>A custom component!</div>'
})
// Local Registration
export default {
components: {
'my-component': {
template: '<div>A custom component!</div>'
}
}
}
// Single File Component (SFC)
// MyComponent.vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return { message: 'Hello!' }
}
}
</script>
Props
// Child Component
export default {
props: {
// Basic type check
propA: Number,
// Multiple possible types
propB: [String, Number],
// Required
propC: {
type: String,
required: true
},
// Default value
propD: {
type: Number,
default: 100
},
// Custom validator
propE: {
validator(value) {
return ['success', 'warning'].includes(value)
}
}
}
}
// Parent Component
<MyComponent :prop-a="42" prop-c="hello" />
Custom Events
// Child Component
export default {
methods: {
handleClick() {
this.$emit('custom-event', 'payload')
}
}
}
// Parent Component
<MyComponent @custom-event="handleEvent" />
Slots
<!-- Child Component -->
<template>
<div>
<slot></slot> <!-- Default slot -->
<slot name="header"></slot> <!-- Named slot -->
<slot name="item" :item="item"></slot> <!-- Scoped slot -->
</div>
</template>
<!-- Parent Component -->
<MyComponent>
<template #default>Default content</template>
<template #header>Header content</template>
<template #item="{ item }">{{ item.name }}</template>
</MyComponent>
Lifecycle Hooks
Options API
export default {
beforeCreate() { /* Before instance initialization */ },
created() { /* After instance created, before mounting */ },
beforeMount() { /* Before mounting to DOM */ },
mounted() { /* After mounting to DOM */ },
beforeUpdate() { /* Before re-render */ },
updated() { /* After re-render */ },
beforeUnmount() { /* Before component destruction */ },
unmounted() { /* After component destroyed */ }
}
Composition API
import {
onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted
} from 'vue'
export default {
setup() {
onMounted(() => console.log('Mounted!'))
onUnmounted(() => console.log('Unmounted!'))
}
}
Computed Properties & Watchers
Computed Properties
// Options API
export default {
data() {
return { firstName: 'John', lastName: 'Doe' }
},
computed: {
// Getter only
fullName() {
return `${this.firstName} ${this.lastName}`
},
// Getter + Setter
fullNameWithSetter: {
get() {
return `${this.firstName} ${this.lastName}`
},
set(newValue) {
[this.firstName, this.lastName] = newValue.split(' ')
}
}
}
}
// Composition API
import { computed, ref } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
Watchers
// Options API
export default {
data() {
return { question: '', answer: '' }
},
watch: {
// Simple watcher
question(newVal, oldVal) {
this.getAnswer()
},
// Deep watcher
someObject: {
handler(newVal, oldVal) {
console.log('Object changed')
},
deep: true,
immediate: true
}
}
}
// Composition API
import { watch, watchEffect, ref } from 'vue'
const question = ref('')
const answer = ref('')
// Watch single ref
watch(question, (newVal, oldVal) => {
getAnswer()
})
// Watch multiple sources
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
// ...
})
// watchEffect (runs immediately and tracks dependencies)
watchEffect(() => {
console.log(`Count is: ${count.value}`)
})
Composition API
Basic Setup
<script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
// Reactive state
const count = ref(0)
const state = reactive({ message: 'Hello' })
// Methods
const increment = () => count.value++
// Computed
const doubled = computed(() => count.value * 2)
// Lifecycle
onMounted(() => console.log('Component mounted'))
// Watch
watch(count, (newVal) => console.log(`Count: ${newVal}`))
</script>
Composables (Custom Hooks)
// useCounter.js
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
// Component
<script setup>
import { useCounter } from './useCounter'
const { count, increment } = useCounter()
</script>
Vue Router
Basic Setup
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User },
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
// Route guard
return checkAuth()
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
Navigation
// Programmatic navigation
router.push('/users')
router.push({ name: 'user', params: { id: 123 }})
router.replace('/users')
router.go(-1)
// In template
<router-link to="/about">About</router-link>
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>
Route Guards
// Global guards
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated) {
return '/login'
}
})
// Component guards
export default {
beforeRouteEnter(to, from, next) {
// Called before component is created
next()
},
beforeRouteUpdate(to, from) {
// Called when route changes but component is reused
},
beforeRouteLeave(to, from) {
// Called when leaving the component
}
}
State Management (Pinia)
Store Definition
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++
}
}
})
// Composition API style
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
return { count, doubleCount, increment }
})
Using Store
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.increment()
</script>
<template>
<div>{{ counter.count }}</div>
<div>{{ counter.doubleCount }}</div>
</template>
Reactivity System
Vue 3 Reactivity
import { ref, reactive, toRef, toRefs, readonly } from 'vue'
// ref - for primitives
const count = ref(0)
console.log(count.value) // Access with .value
// reactive - for objects
const state = reactive({ count: 0 })
console.log(state.count) // Direct access
// toRef - create ref from reactive property
const countRef = toRef(state, 'count')
// toRefs - convert reactive object to refs
const { count, name } = toRefs(state)
// readonly - create readonly reactive object
const readonlyState = readonly(state)
Reactivity Caveats
// Vue 2 - Array mutations
vm.$set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
// Vue 2 - Object property addition
vm.$set(vm.userProfile, 'age', 27)
// Vue 3 - All operations are reactive by default
state.newProp = 'value' // This works!
Advanced Patterns
Provide/Inject
// Parent component
import { provide } from 'vue'
provide('user', { name: 'John' })
// Child component (any level deep)
import { inject } from 'vue'
const user = inject('user')
Dynamic Components
<component :is="currentComponent" />
<script>
import CompA from './CompA.vue'
import CompB from './CompB.vue'
export default {
components: { CompA, CompB },
data() {
return { currentComponent: 'CompA' }
}
}
</script>
Async Components
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
// With loading/error states
const AsyncComp = defineAsyncComponent({
loader: () => import('./Foo.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
Custom Directives
// Global directive
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// Local directive
export default {
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
}
// Usage
<input v-focus />
Performance Optimization
Keep-Alive
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
<!-- With props -->
<keep-alive :include="['CompA', 'CompB']" :max="10">
<router-view />
</keep-alive>
Lazy Loading Routes
const routes = [
{
path: '/about',
component: () => import('./views/About.vue')
}
]
v-memo (Vue 3.2+)
<!-- Only re-renders when id changes -->
<div v-for="item in list" :key="item.id" v-memo="[item.id]">
{{ item.name }}
</div>
Best Practices
Component Design
- Keep components small and focused
- Use props for parent→child communication
- Use events for child→parent communication
- Avoid mutating props directly
- Use computed properties for derived state
Naming Conventions
- Components: PascalCase (MyComponent)
- Props: camelCase in JS, kebab-case in templates
- Events: kebab-case (my-event)
- Slots: kebab-case
Performance Tips
- Use v-show for frequent toggles
- Add :key to v-for items
- Lazy load routes and components
- Use computed properties instead of methods in templates
- Debounce expensive operations
Testing Vue Applications
Unit Testing with Vitest
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from './Counter.vue'
describe('Counter', () => {
it('increments count when button is clicked', async () => {
const wrapper = mount(Counter)
expect(wrapper.text()).toContain('Count: 0')
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('Count: 1')
})
it('emits custom event', async () => {
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.emitted()).toHaveProperty('increment')
expect(wrapper.emitted().increment[0]).toEqual([1])
})
})
Testing Composables
import { renderHook, act } from '@testing-library/vue-hooks'
import { useCounter } from './useCounter'
test('counter increments', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count.value).toBe(0)
act(() => {
result.current.increment()
})
expect(result.current.count.value).toBe(1)
})
Common Patterns & Solutions
Global State Without Vuex/Pinia
// Simple reactive store
import { reactive, readonly } from 'vue'
const state = reactive({
user: null,
isAuthenticated: false
})
const mutations = {
setUser(user) {
state.user = user
state.isAuthenticated = !!user
},
logout() {
state.user = null
state.isAuthenticated = false
}
}
export default {
state: readonly(state),
...mutations
}
Form Validation Pattern
<script setup>
import { ref, computed } from 'vue'
const email = ref('')
const password = ref('')
const errors = computed(() => {
const errors = {}
if (!email.value.includes('@')) {
errors.email = 'Invalid email'
}
if (password.value.length < 8) {
errors.password = 'Password must be at least 8 characters'
}
return errors
})
const isValid = computed(() => Object.keys(errors.value).length === 0)
</script>
Debounced Search
import { ref, watch } from 'vue'
import { debounce } from 'lodash-es'
const searchQuery = ref('')
const searchResults = ref([])
const debouncedSearch = debounce(async (query) => {
const results = await api.search(query)
searchResults.value = results
}, 300)
watch(searchQuery, (newQuery) => {
if (newQuery) {
debouncedSearch(newQuery)
} else {
searchResults.value = []
}
})
Interview Preparation Checklist
- ✅ Core Concepts: Reactivity, components, directives
- ✅ Template Syntax: Interpolation, directives, event handling
- ✅ Component Communication: Props, events, slots, provide/inject
- ✅ Lifecycle Hooks: Options API vs Composition API
- ✅ State Management: Local state, Pinia, composables
- ✅ Routing: Vue Router, guards, lazy loading
- ✅ Composition API: setup, ref, reactive, computed, watch
- ✅ Performance: keep-alive, lazy loading, v-memo
- ✅ Testing: Unit tests, component testing, E2E
- ✅ Vue 3 Features: Teleport, Suspense, Fragments
Key Takeaway: Vue.js excels at providing a progressive, approachable framework that scales from simple widgets to complex applications. Its reactive data binding, component system, and excellent tooling make it highly productive. Master the reactivity system, understand when to use Composition vs Options API, and know the ecosystem tools (Vue Router, Pinia) for successful Vue development.
Component Design
- Keep components small and focused
- Use props for parent→child communication
- Use events for child→parent communication
- Avoid mutating props directly
- Use computed properties for derived state
Naming Conventions
- Components: PascalCase (MyComponent)
- Props: camelCase in JS, kebab-case in templates
- Events: kebab-case (my-event)
- Slots: kebab-case
Performance Tips
- Use v-show for frequent toggles
- Add :key to v-for items
- Lazy load routes and components
- Use computed properties instead of methods in templates
- Debounce expensive operations
Key Interview Concepts
Vue vs Other Frameworks
| Feature | Vue | React | Angular |
|---|---|---|---|
| Learning Curve | Gentle | Moderate | Steep |
| Data Binding | Two-way | One-way | Two-way |
| Architecture | MVVM | Component-based | MVC/Component |
| Template Syntax | HTML-based | JSX | HTML + TypeScript |
| State Management | Pinia/Vuex | Redux/Context | Services/RxJS |
| Performance | Fast | Fast | Good |
| TypeScript | Optional | Optional | Required |
| Bundle Size | ~34KB | ~42KB | ~130KB |
Reactivity System Deep Dive
// Vue 2 - Object.defineProperty
Object.defineProperty(obj, 'count', {
get() { /* track dependencies */ },
set(newVal) { /* trigger updates */ }
})
// Vue 3 - Proxy
const state = new Proxy(target, {
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
trigger(target, key)
return Reflect.set(target, key, value, receiver)
}
})
Composition API vs Options API
| Aspect | Options API | Composition API |
|---|---|---|
| Organization | By options (data, methods) | By logical concern |
| Type Inference | Limited | Excellent |
| Code Reuse | Mixins (problematic) | Composables (clean) |
| Tree-shaking | Limited | Better |
| Learning Curve | Easier | Steeper initially |
| Bundle Size | Slightly larger | Optimizable |
Computed vs Methods vs Watch
| Feature | Computed | Methods | Watch |
|---|---|---|---|
| Caching | Yes | No | N/A |
| Dependencies | Automatic tracking | None | Explicit |
| Use Case | Derived state | Event handlers | Side effects |
| Return Value | Required | Optional | None |
| Async | No | Yes | Yes |
Virtual DOM & Diffing Algorithm
// Simplified Vue Virtual DOM process
1. Template → Render Function → Virtual DOM Tree
2. State Change → New Virtual DOM Tree
3. Diff Algorithm (Compare Old vs New)
4. Patch Operations → Update Real DOM
// Key optimizations:
- Static hoisting (compile-time)
- Patch flags (targeted updates)
- Fragment support (multiple roots)
- Cached event handlers
Vue 2 vs Vue 3 Migration Guide
| Feature | Vue 2 | Vue 3 |
|---|---|---|
| Reactivity | Object.defineProperty | Proxy |
| API | Options API | Options + Composition |
| Root Elements | Single | Multiple (Fragments) |
| v-model | Single | Multiple on component |
| Lifecycle | beforeDestroy/destroyed | beforeUnmount/unmounted |
| Async Components | () => import() | defineAsyncComponent |
| Filters | Supported | Removed (use methods) |
| TypeScript | Plugin-based | First-class support |