Chapter 43: Vue Reference
Vue References (usually just called refs)
In Vue 3, the word “ref” appears in two completely different but related contexts, and almost every beginner gets confused between them at first.
So let’s be very clear from the beginning:
- ref() — the reactivity primitive (most important thing in Vue 3)
- template ref — the way to get direct access to a DOM element or component instance
Today we’ll cover both in detail — because both are called “ref”, both are extremely important, and both are used in almost every real Vue component you will ever write.
Part 1 – ref() – The Reactivity Primitive (The Heart of Vue 3)
This is the single most important API in Vue 3 Composition API.
What it does (simple explanation)
ref() creates a reactive reference — a tiny wrapper object that holds your value and makes it reactive.
When you change the value inside the ref → Vue automatically knows which parts of your UI depend on it → only those parts re-render.
Basic Example – Counter
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<template> <div class="counter"> <h2>Count: {{ count }}</h2> <button @click="increment">+1</button> <button @click="reset">Reset</button> <p>Double is: {{ double }}</p> </div> </template> <script setup> import { ref, computed } from 'vue' // 1. Create a reactive reference const count = ref(0) // ref holds the number 0 // 2. To read/change the value in JavaScript → use .value function increment() { count.value++ // ← must use .value in <script> } function reset() { count.value = 0 } // 3. Computed values also use .value when reading refs const double = computed(() => { return count.value * 2 // ← .value again }) </script> <style scoped> .counter { text-align: center; padding: 2rem; } button { margin: 0.5rem; padding: 0.8rem 1.5rem; } </style> |
Important Rules about ref() – Must Memorize
| Situation | How you read/write it | Where you write it |
|---|---|---|
| In JavaScript / <script setup> | count.value (always use .value) | Must use .value |
| In template ({{ }}, v-if, v-bind…) | {{ count }} — no .value needed | Automatic unwrapping |
| In computed / watch | count.value | Must use .value |
| Passing to child as prop | :count=”count” — pass the ref itself | Pass ref object |
| Receiving in child | const props = defineProps<{ count: number }>() — gets unwrapped value | Gets the raw value |
Why ref() and not just let count = 0?
|
0 1 2 3 4 5 6 7 |
let count = 0 // ← normal variable count++ // Vue has NO IDEA this changed → UI doesn't update |
|
0 1 2 3 4 5 6 7 |
const count = ref(0) // ← ref wrapper count.value++ // Vue tracks this → UI auto-updates |
reactive() vs ref() – Quick Comparison
| Feature | ref() | reactive() |
|---|---|---|
| Best for | Primitives (number, string, boolean) | Objects / arrays |
| Syntax to change | count.value++ | user.age++ (no .value) |
| Can hold primitives? | Yes | No |
| Can replace the value? | Yes (count.value = 100) | No — replacing whole object breaks reactivity |
| Unwraps in template? | Yes — {{ count }} works | Yes — {{ user.name }} works |
Rule of thumb 2026
- Use ref() for almost everything (number, string, boolean, single value)
- Use reactive() only when you have a real object with many properties you want to mutate directly
Part 2 – Template Refs (Accessing DOM or Component Instances)
This is the second meaning of “ref” — completely different.
You use ref=”someName” in the template to get a reference to the real DOM element (or Vue component instance).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<template> <div> <input ref="username" type="text" placeholder="Enter username" /> <button @click="focusUsername"> Focus the input </button> </div> </template> <script setup> import { ref, onMounted } from 'vue' // 1. Declare the ref (starts as null) const username = ref(null) // or ref<HTMLInputElement | null>(null) with TS function focusUsername() { // 2. Access the real <input> element if (username.value) { username.value.focus() username.value.select() // highlight text } } // 3. Safe to access after mount onMounted(() => { console.log('Input element is now:', username.value) }) </script> |
Template refs cheat sheet
| Goal | Code pattern | When ref.value is ready? |
|---|---|---|
| Focus input on mount | ref=”inputEl” → onMounted(() => inputEl.value.focus()) | After onMounted |
| Get size / position | const rect = inputEl.value.getBoundingClientRect() | After mount |
| Scroll to element | inputEl.value.scrollIntoView({ behavior: ‘smooth’ }) | After mount |
| Access child component instance | ref=”child” → child.value.someExposedMethod() | After mount |
| Multiple refs (v-for) | :ref=”el => refsArray[index] = el” | After mount |
Accessing child component methods
Child:
|
0 1 2 3 4 5 6 7 8 9 |
<script setup> function sayHello() { alert('Hello from child!') } defineExpose({ sayHello }) // ← make it available via ref </script> |
Parent:
|
0 1 2 3 4 5 6 7 8 9 10 11 |
<ChildComponent ref="childRef" /> <button @click="childRef?.sayHello()">Call child method</button> <script setup> const childRef = ref(null) </script> |
Quick Summary Table – Two Kinds of “ref” in Vue 3
| Kind of ref | Purpose | Syntax in script | Syntax in template | .value needed in JS? |
|---|---|---|---|---|
| ref() (reactivity) | Make variable reactive | const count = ref(0) | {{ count }} | Yes |
| template ref | Get DOM element or component instance | const el = ref(null) | ref=”el” | Yes (el.value) |
Pro Tips from Real Projects (2026)
- Always check if (ref.value) — refs are null before mount & after unmount
- Use nextTick() when you change state and immediately need to access updated DOM
- For third-party libraries (Chart.js, Leaflet, Swiper…) → template refs are the #1 integration point
- Don’t overuse template refs — prefer declarative solutions (v-model, v-if, transitions) when possible
- In TypeScript → type your refs: ref<HTMLInputElement | null>(null)
Your mini practice:
- Create a search input that auto-focuses on mount (use template ref + onMounted)
- Add a button “Scroll to bottom” that scrolls a long list to the last item (use template ref on last element)
- Create a child component that exposes a method → call it from parent via ref
Any part confusing? Want to see:
- Template refs + <Transition> for animated focus?
- Accessing child component with TypeScript?
- Common gotcha with v-if + ref?
- Real Chart.js or Google Maps integration with refs?
Just tell me — we’ll build the next practical ref example together 🚀
