Click any item to expand the explanation and examples.
⚡ Reactivity
ref() — reactive primitive reactivity
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Alice')
// Access/modify with .value in script
count.value++
console.log(count.value)
</script>
<template>
<!-- No .value needed in template -->
<p>{{ count }}</p>
<button @click="count++">+1</button>
</template>
reactive() — reactive object reactivity
<script setup>
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: { name: 'Alice' }
})
// No .value needed
state.count++
state.user.name = 'Bob'
</script>
Use ref() for primitives, reactive() for objects. When in doubt, use ref().
computed() — derived values reactivity
<script setup>
import { ref, computed } from 'vue'
const items = ref([1, 2, 3, 4, 5])
const total = computed(() => items.value.reduce((a, b) => a + b, 0))
const even = computed(() => items.value.filter(n => n % 2 === 0))
</script>
<template>
<p>Total: {{ total }}</p>
</template>
watch() / watchEffect() reactivity
<script setup>
import { ref, watch, watchEffect } from 'vue'
const query = ref('')
// Watch specific ref
watch(query, (newVal, oldVal) => {
console.log(`Changed from ${oldVal} to ${newVal}`)
})
// Watch multiple
watch([firstName, lastName], ([first, last]) => {
console.log(`${first} ${last}`)
})
// Auto-track dependencies
watchEffect(() => {
console.log('Query is:', query.value)
})
</script>
🧩 Components
Props and emits components
<!-- ChildComponent.vue -->
<script setup>
const props = defineProps({
title: String,
count: { type: Number, default: 0 }
})
const emit = defineEmits(['update', 'delete'])
function handleClick() {
emit('update', props.count + 1)
}
</script>
<!-- Parent -->
<ChildComponent
title="Hello"
:count="5"
@update="handleUpdate"
@delete="handleDelete"
/>
v-model on components components
<!-- TextInput.vue -->
<script setup>
const model = defineModel()
</script>
<template>
<input v-model="model" />
</template>
<!-- Parent -->
<script setup>
const name = ref('')
</script>
<TextInput v-model="name" />
Slots components
<!-- Card.vue -->
<template>
<div class="card">
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer">Default footer</slot></footer>
</div>
</template>
<!-- Usage -->
<Card>
<template #header><h2>Title</h2></template>
<p>Main content goes here</p>
<template #footer><button>Save</button></template>
</Card>
📝 Template Directives
v-if, v-for, v-show, v-bind, v-on template
<template>
<!-- Conditional -->
<p v-if="loggedIn">Welcome!</p>
<p v-else-if="loading">Loading...</p>
<p v-else>Please log in</p>
<!-- Show/hide (CSS display toggle) -->
<p v-show="visible">I toggle with CSS</p>
<!-- Loop -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- Bind attribute -->
<img :src="imageUrl" :alt="imageAlt" />
<div :class="{ active: isActive, highlight: score > 90 }" />
<!-- Events -->
<button @click="handleClick">Click</button>
<form @submit.prevent="handleSubmit">...</form>
<input @keyup.enter="search" />
</template>
🔄 Lifecycle
onMounted, onUnmounted, etc. lifecycle
<script setup>
import { onMounted, onUnmounted, onUpdated } from 'vue'
onMounted(() => {
console.log('Component mounted')
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
onUpdated(() => {
console.log('DOM updated')
})
</script>
See also: Tailwind CSS cheat sheet | TypeScript cheat sheet