Vue.js Cheatsheet - Composition API, ref & computed

This reference targets Vue 3 developers working with the Composition API and <script setup>. It walks from reactivity basics (ref vs reactive) to computed and watchers, then props/emits/provide-inject for component communication, before covering routing, Pinia, templates, and performance tweaks like shallowRef and defineAsyncComponent. Each entry is a snippet you can drop into an existing SFC and observe the reactive update. After reading you should be able to manage local state with ref/reactive, react to changes with watch, wire up a child parent call, and lazy-load heavy components.

Languages·46 commands·Last updated 2026-07-21

Typical Use Case

For front-end developers using the Vue 3 Composition API. The sheet lays out one spine: reactive basics, lifecycle, component communication, composables, routing and state, template syntax, then performance. Start with ref for primitives and reactive for objects, computed for derived cache, and the choice between watch and watchEffect. Remember that destructuring reactive loses reactivity unless you go through toRefs. defineProps, defineEmits, defineExpose, defineModel and provide/inject cover the component layer; shallowRef, markRaw, v-memo and KeepAlive cover cost.

Reactivity Basics 8

const count = ref(0)
Reactive primitive
count.value++
Update a ref (.value in script)
const state = reactive({ name: "Vue" })
Reactive object
const doubled = computed(() => count.value * 2)
Read-only computed
const w = computed({ get: () => v.value, set: (x) => (v.value = x) })
Writable computed
watch(count, (n, o) => {}, { immediate: true })
Watch with immediate run
watchEffect(() => console.log(count.value))
Auto-tracked watch effect
const { name } = toRefs(state)
Destructure while staying reactive

Lifecycle Hooks 6

onBeforeMount(() => {})
Before mount
onMounted(() => {})
After mount (good for fetch)
onBeforeUpdate(() => {})
Before update
onUpdated(() => {})
After update
onBeforeUnmount(() => {})
Before unmount (cleanup)
onUnmounted(() => {})
After unmount

Component Communication 6

defineProps<{ msg: string }>()
Declare props (type form)
const emit = defineEmits(["change"])
Declare emitted events
defineExpose({ method() {} })
Expose methods to parent ref
provide("theme", "dark")
Provide data to descendants
const theme = inject("theme", "light")
Inject with a default
const model = defineModel<string>()
Two-way binding (Vue 3.4+)

Composables 5

function useCounter(n = 0) { const c = ref(n); return { c, inc: () => c.value++ } }
Custom counter composable
const { c, inc } = useCounter(10)
Use a custom composable
function useMouse() { const x = ref(0); const y = ref(0); return { x, y } }
Encapsulate mouse position
function useEventListener(el, ev, cb) { onUnmounted(() => el.removeEventListener(ev, cb)) }
Encapsulate listener with auto-cleanup
function useFetch(url) { /* return { data, error, loading } */ }
Encapsulate an async request

Router & State 7

const route = useRoute()
Get current route
const router = useRouter()
Get router instance
router.push("/users")
Programmatic navigation
router.replace("/login")
Replace route (no history)
const store = useCounterStore()
Get a Pinia store
const { count } = storeToRefs(store)
Destructure store (reactive)
store.$patch({ count: 1 })
Patch store state

Template Syntax 8

{{ message }}
Text interpolation
:href="url"
Attribute binding (v-bind shorthand)
@click="handler"
Event binding (v-on shorthand)
v-model="form.name"
Two-way binding
v-if / v-else-if / v-else
Conditional rendering
v-for="item in list" :key="item.id"
List rendering (key required)
<Teleport to="body">
Teleport to a DOM node
<slot name="header" />
Named slot

Performance 6

const list = shallowRef([])
Shallow ref (large objects/lists)
markRaw(obj)
Mark raw (never reactive)
v-memo="[a, b]"
Template memo (Vue 3.2+)
defineAsyncComponent(() => import("./Comp.vue"))
Lazy async component
<KeepAlive include="UserList">
Cache component instance (KeepAlive)
<Suspense>
Coordinate async loading (Suspense)

Parameter matrix

ParameterEffectExample
ref(0)Makes a primitive reactive; reads/writes in the script need .value.const count = ref(0)
count.valueReads or modifies a ref value; auto-unwrapped in the template.count.value++
reactive({ })Makes every property of an object reactive with direct property access.const state = reactive({ name: "Vue" })
computed(() => ...)A derived read-only value, recomputed only when dependencies change.const doubled = computed(() => count.value * 2)
computed({ get, set })A writable computed property that forwards assignment to the source state.computed({ get: () => v.value, set: (x) => (v.value = x) })
watch(src, cb, opts)Watches for changes; the callback receives the new and old values.watch(count, (n, o) => {}, { immediate: true })
watchEffect(fn)Automatically tracks dependencies used inside fn and reruns when any changes.watchEffect(() => console.log(count.value))
toRefs(obj)Destructures into individual refs, staying reactive after destructuring.const { name } = toRefs(state)
onBeforeMountFires before mount, when the DOM does not exist yet.onBeforeMount(() => {})
onMountedFires after mount when the DOM is ready, commonly used for requests.onMounted(() => {})
onBeforeUpdate / onUpdatedFires before a re-render caused by data changes / after the re-render completes.onUpdated(() => {})
onBeforeUnmount / onUnmountedFires before / after full unmount, used to clean up timers.onUnmounted(() => clearInterval(t))
defineProps<{ }>()Declare props with a type argument, no runtime import needed.defineProps<{ msg: string }>()
defineEmits([ ])Declare the events this component emits to its parent.const emit = defineEmits(["change"])
defineExpose({ })Expose members to the parent through a template ref.defineExpose({ method() {} })
defineModel<string>()Two-way binding for a component, available from Vue 3.4.const model = defineModel<string>()
provide / injectHand data down to descendants and pick it up again, with a fallback default.inject("theme", "light")
useXxx() composablePackage state and side effects into a hook reusable across components.const { c, inc } = useCounter(10)
useEventListener(el, ev, cb)Attach a listener and remove it again automatically on unmount.useEventListener(el, ev, cb)
useFetch(url)Wrap an async request and hand back data, error and loading.function useFetch(url) {}
useRoute / useRouterRead the current route, or get the router instance.const router = useRouter()
router.push / replaceNavigate programmatically, or swap the current entry so no history is kept.router.replace("/login")
useCounterStore()Get the Pinia store instance.const store = useCounterStore()
storeToRefs(store)Destructure store state while keeping it reactive.const { count } = storeToRefs(store)
store.$patch({ })Update several state fields in one call.store.$patch({ count: 1 })
{{ message }}Text interpolation in the template.<p>{{ message }}</p>
:href="url"Attribute binding, the shorthand for v-bind.<a :href="url">go</a>
@click="handler"Event binding, the shorthand for v-on.<button @click="handler">
v-model="form.name"Two-way binding straight onto an object property.<input v-model="form.name" />
v-if / v-else-if / v-elseConditional rendering.<div v-if="loading">Loading...</div>
v-for + :keyList rendering; the key is mandatory.<li v-for="item in list" :key="item.id">
<Teleport to="body">Move the rendered content to a different DOM node.<Teleport to="body">
<slot name="header" />A named slot.<slot name="header" />
shallowRef([])Shallow reactivity — only the reference is tracked, which keeps a long list cheap.const list = shallowRef([])
markRaw(obj)Mark an object so Vue never wraps it in a proxy.markRaw(obj)
v-memo="[a, b]"Template memoization: skip the update while the dependencies are unchanged.v-memo="[a, b]"
defineAsyncComponentLazy-load a component on demand.defineAsyncComponent(() => import("./Comp.vue"))
<KeepAlive include="X">Cache component instances so their state is not thrown away.<KeepAlive include="UserList">
<Suspense>Coordinate the loading and display of async dependencies.<Suspense>

Common pitfalls

SymptomA value that displays in the template reads as undefined in the script.

Causeref is not auto-unwrapped in the script, and .value was missed on access.

FixWrite count.value consistently in the script; only the template can use count directly.

SymptomAfter destructuring a reactive object its properties lose reactivity and changing data does not update the UI.

CauseDestructuring grabs a plain-value snapshot, cutting the link to the source object.

FixUse toRefs(state) to destructure into refs, or keep the object intact and access state.name directly.

SymptomThe watch is written but never fires once.

Causewatch is lazy by default and only runs when the source value changes.

FixAdd { immediate: true } when it must run once up front.

SymptomLarge list operations clearly drop frames, re-rendering the whole subtree on every refresh.

CauseThe overhead of deep reactive proxies is amplified on long lists.

FixSwitch large objects to shallowRef and trigger manually on whole replacement; use v-memo in the template to skip unrelated subtrees.

SymptomAfter a route change the browser back button returns to it.

Causerouter.push was used instead of router.replace, pushing the navigation onto the history stack.

FixFor logins and redirects that should not leave history, use router.replace("/login") instead.

SymptomThe component is gone but the timer keeps running and requests keep firing.

CauseThe side effect was set up in onMounted, but the cleanup is missing or hooked incorrectly.

FixPut cleanup in onUnmounted so it pairs with the setup logic; wrappers like useEventListener make event-listener handling easier.

SymptomA v-for list shows mismatched data or corrupted state.

CauseThe list items lack :key, so diffing can only reuse nodes by index.

FixAdd a stable, unique :key to every v-for, such as :key="item.id".

Troubleshooting

  1. 1Confirm whether a value is really reactive.

    console.log(count.value);

    If the script cannot read the value, check whether .value is missing, or whether the variable is not a ref/reactive at all.

  2. 2Destructure an object while keeping reactivity.

    const { name } = toRefs(state)

    Afterwards name is a ref, so use name.value in the script; destructuring state directly loses reactivity.

  3. 3Make the watcher run once on initialization.

    watch(count, (n, o) => {}, { immediate: true })

    immediate makes the callback fire right away with the old value undefined — good for fetching data on init.

  4. 4Issue requests after the DOM is ready.

    onMounted(() => { loadData() })

    The DOM is unavailable during SSR or before a parent finishes rendering, so onMounted is the safe place to fire requests.

  5. 5Check whether a navigation was pushed onto the history stack.

    router.replace("/login")

    Replace-style navigation leaves no history; only switch to router.push("/users") when you need a "back to previous page".

  6. 6Cut the render cost of a long list

    const list = shallowRef([])

    Assign a fresh array whenever the whole result set changes instead of mutating in place, because the reassignment is what triggers the update, and it costs far less than a deep proxy.

  7. 7Load a heavy component on demand

    defineAsyncComponent(() => import("./Comp.vue"))

    The chunk is requested the first time the component is rendered, so the initial payload shrinks accordingly.

Command Examples

Keep reactivity when destructuring with toRefs

const state = reactive({ name: "Vue", count: 1 }) // destructuring here drops reactivity const { name } = state // correct: destructure into refs const { name: n, count } = toRefs(state)

Properties of a reactive object are proxied, but a destructured value is only a plain snapshot — mutating state.name never updates it. toRefs wraps each property in a ref, which is what makes passing them to children or using them in computed safe.

Destructuring a Pinia store also needs storeToRefs

const store = useCounterStore() const { count } = storeToRefs(store) // stays reactive const { inc, reset } = store // actions destructure fine

Actions are plain functions and survive destructuring, but state properties are refs and become snapshots. The reliable pattern is to take state through storeToRefs and actions directly — the most common Vue 3 convention.

Use immediate on watch to run the callback on first render

watch( () => props.userId, (n, o) => { load(n) }, { immediate: true }, )

By default watch only fires on change, never on the initial mount. immediate: true routes first-load and later updates through the same callback, so the fetch logic is not duplicated.

Move cleanup into onBeforeUnmount

let timer onMounted(() => { timer = setInterval(poll, 5000) }) onBeforeUnmount(() => { clearInterval(timer) })

A timer that survives unmount keeps firing, triggers state updates and logs "update on unmounted component" warnings. Clearing timers and listeners in onBeforeUnmount is a prerequisite for a reusable component.

Use shallowRef for large lists to avoid deep proxying

const rows = shallowRef([]) rows.value = await fetchRows() // replace wholesale to trigger the update // rows.value[0].name = "x" will not re-render; use rows.value = [...rows.value]

shallowRef proxies only the first level, so a table of thousands of rows does not visibly slow down from deep proxying. The tradeoff: mutating a field inside the array does not re-render, so you must replace the reference.

Common Pitfalls

  • Destructuring a reactive object or Pinia store yields a snapshot: mutating the source leaves the view frozen, which is easily misdiagnosed as "reactivity broke" when the reactivity was never there.
  • Side effects inside computed (fetching, DOM writes, accumulating counters) run an unpredictable number of times; move them into watch or watchEffect.
  • A v-for without :key makes the diff reuse the wrong nodes, showing as input values bleeding between rows or state shifting after a delete. Keys must be stable unique ids, never array indexes.
  • Awaiting inside a component that unmounts before the response resolves leaks memory and warns about state on an unmounted instance; abort the request in onBeforeUnmount or clean up in onUnmounted.
  • Managing the same state with both a ref and a reactive object produces two unsynchronised proxies, so a write in one place never lands in the other. Pick one container per state.
  • store.$patch shallow-merges: passing an object replaces the named fields wholesale instead of deep-merging, so nested objects lose sibling keys unless you patch each path separately.

Tips

  • ref is for primitives, reactive for objects; ref needs .value in script but auto-unwraps in templates.
  • computed caches and recomputes only on dependency change; watch is lazy unless immediate: true.
  • defineProps/Emits/Expose/Model are compiler macros - no import needed.
  • Destructuring a reactive object loses reactivity; wrap with toRefs first.
  • Use shallowRef + v-memo for big lists, defineAsyncComponent for route components.

FAQ

How do I choose between ref and reactive?

ref suits primitives and objects you intend to replace wholesale — you read and write through .value, and templates unwrap it automatically. reactive suits a set of properties that change together, accessed directly as state.name. In practice, prefer ref for component state: it can be replaced wholesale so dependencies are harder to miss, whereas reactive loses reactivity when destructured.

Why does destructuring props in setup break reactivity?

Destructuring copies the property off the reactive object into a plain value, and that copy no longer tracks the original. Either keep using props.msg, or convert first with toRefs: const { msg } = toRefs(props), after which each ref still points at the underlying property and msg.value stays reactive.

When should I use computed versus watch?

computed is for values derived from existing state: it is cached and only recomputes when dependencies change, which suits totals and formatting. watch is for side effects triggered by a change — fetching, writing localStorage, touching the DOM. It is lazy by default, so use immediate or deep to alter timing or depth. Prefer computed whenever it can express the requirement; it is simpler and cheaper.

When should I reach for provide and inject?

Use them to pass one value across several component layers without threading props through each level. The parent calls provide("theme", "dark") and any descendant calls inject("theme", "light") with a default. Injection is not limited to direct parent-child relations. Forms, theming and i18n suit this pattern; for data moving only one or two levels, props stays more explicit and traceable.

How do I optimize slow rendering of large lists?

Two directions. Reduce unnecessary reactivity: for lists of a few thousand rows use shallowRef with markRaw to skip deep proxying, which saves substantial overhead. And limit render scope: lazy-load with defineAsyncComponent(() => import("./Comp.vue")) and cache unchanged template fragments with v-memo. In practice the bottleneck is usually data volume, so consider pagination or virtual scrolling first.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us