← Cheatsheets
Tags: vue, composition-api, lifecycle, directives, slots,
pinia, frontend
Last updated: 2026-06-26
Vue.js Cheatsheet
Quick Reference
| Concept | Composition API | Options API |
| Reactive state |
ref() / reactive() |
data() |
| Computed |
computed() |
computed: {} |
| Watchers |
watch() / watchEffect() |
watch: {} |
| Lifecycle |
onMounted(), etc. |
mounted(), etc. |
| Methods |
plain functions |
methods: {} |
| Props |
defineProps() |
props: {} |
| Emits |
defineEmits() |
emits: {} |
| Store |
useXxxStore() (Pinia) |
this.$store (Vuex) |
Composition API (<script setup>)
Reactive State
<script setup>
import { ref, reactive } from "vue";
const count = ref(0);
const user = reactive({ name: "", age: 0 });
count.value++; // .value in JS
user.name = "Max"; // No .value for reactive
</script>
<template>
<p>{{ count }}</p> <!-- auto-unwrapped -->
</template>
computed
import { computed, ref } from "vue";
const items = ref([1, 2, 3]);
const doubled = computed(() =>
items.value.map(i => i * 2));
watch / watchEffect
import { watch, watchEffect, ref } from "vue";
const query = ref("");
watch(query, (newVal, oldVal) => {
fetchResults(newVal);
}, { immediate: true });
watchEffect(() => {
console.log(query.value); // Auto-tracks deps
});
defineProps & defineEmits
const props = defineProps({
title: String,
count: { type: Number, default: 0 },
});
const emit = defineEmits(["update", "delete"]);
emit("update", { id: 1 });
defineModel (Vue 3.4+)
// Two-way binding shortcut
const model = defineModel();
// or with options
const model = defineModel({ type: String, default: "" });
// Template: <input v-model="model" />
Template Directives
| Directive | Purpose | Example |
v-bind / : |
Bind attribute |
:href="url" |
v-on / @ |
Attach event |
@click="handler" |
v-model |
Two-way binding |
<input v-model="name"> |
v-if / v-else |
Conditional |
<div v-if="show"> |
v-show |
Toggle display |
<div v-show="ok"> |
v-for |
Loop |
<li v-for="i in items"
:key="i.id"> |
v-once |
Render once |
<span v-once>{{ init }}</span> |
v-memo |
Memoise subtree |
<div v-memo="[a, b]"> |
v-model Modifiers
<input v-model.lazy="msg"> <!-- change, not input -->
<input v-model.number="age"> <!-- cast to number -->
<input v-model.trim="name"> <!-- trim whitespace -->
Slots
Default Slot
<!-- Child -->
<div class="card"><slot>Default</slot></div>
<!-- Parent -->
<Card>Hello</Card>
Named Slots
<!-- Child -->
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
<!-- Parent -->
<Layout>
<template #header><h1>Title</h1></template>
<p>Body</p>
<template #footer><small>Footer</small></template>
</Layout>
Scoped Slots
<!-- Child: <slot :item="item" :index="index" /> -->
<!-- Parent -->
<template #default="{ item, index }">
{{ index }}: {{ item.name }}
</template>
Lifecycle Hooks
| Composition | Options | When |
onBeforeMount | beforeMount |
Before DOM insertion |
onMounted | mounted |
After DOM insertion |
onBeforeUpdate | beforeUpdate |
Before re-render |
onUpdated | updated |
After re-render |
onBeforeUnmount |
beforeUnmount |
Before removal |
onUnmounted | unmounted |
After removal |
onErrorCaptured |
errorCaptured |
Child error caught |
Pinia (State Management)
Define a Store
// stores/counter.ts
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const double = computed(() => count.value * 2);
function increment() { count.value++; }
return { count, double, increment };
});
// Options store style
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0 }),
getters: { double: (s) => s.count * 2 },
actions: { increment() { this.count++; } },
});
Using a Store
import { useCounterStore } from "@/stores/counter";
import { storeToRefs } from "pinia";
const store = useCounterStore();
// Reactive destructure of state + getters
const { count, double } = storeToRefs(store);
// Methods destructure directly
const { increment } = store;
Component Communication
Props Down, Events Up
<!-- Parent -->
<Child :title="title" @update="title = $event" />
<!-- Child -->
const props = defineProps({ title: String });
const emit = defineEmits(["update"]);
emit("update", "new title");
provide / inject
// Ancestor
provide("theme", ref("dark"));
// Descendant
const theme = inject("theme", "light"); // default fallback
Teleport
<Teleport to="body">
<Modal />
</Teleport>
<Teleport to="#target" :disabled="isMobile">
<Tooltip />
</Teleport>
TypeScript with Vue
<script setup lang="ts">
const user = ref<User>({ name: "", age: 0 });
const props = defineProps<{
title: string; count?: number
}>();
const emit = defineEmits<{
update: [id: number]; close: []
}>();
</script>
Tips
- Prefer
<script setup> — less
boilerplate, better TypeScript inference.
- Use
ref() for primitives and when
reassigning; use reactive() for objects
mutated in place.
watchEffect auto-tracks dependencies
— great for simple side-effects where you don't need old/new value
comparison.
- Use
defineModel (Vue 3.4+) instead of
manual prop + emit for v-model in custom components.
- Pinia's
storeToRefs() preserves
reactivity when destructuring state and getters.
- Vue's
<Transition> and
<TransitionGroup> components handle enter/leave
animations without extra libraries.