ホーム

Blog

Referencing Values with Refs in Vue 3

A ref is a special reactive object that holds an internal value accessible via the .value property. Vue automatically updates the parts of the template or computed properties that depend on it.

更新日:2025/10/29

Referencing Values with Refs in Vue 3

Referencing Values with Refs in Vue 3

In Vue 3, one of the most powerful and commonly used features in the Composition API is refs. A ref provides a way to create reactive and mutable references to values, whether they are primitives, objects, DOM elements, or even components. Understanding how to use refs effectively is crucial to writing clean and maintainable Vue applications.

This tutorial will explain what refs are, how they differ from reactive(), how to use them in templates and scripts, how to interact with DOM elements, and how to use refs with lifecycle hooks. We’ll also explore advanced concepts like unwrapping and ref forwarding. Let’s dive in!

What Are Refs?

A ref is a special reactive object that holds an internal value accessible via the .value property. Whenever this value changes, Vue automatically updates the parts of the template or computed properties that depend on it.

The simplest way to define a ref is to import it from Vue and call ref():


import { ref } from 'vue'

const count = ref(0)

Here, count is a reactive reference to the number 0. To read its value, you use count.value, and to update it, you can simply assign a new value:


console.log(count.value) // 0
count.value  
console.log(count.value) // 1

Vue tracks these changes automatically, which makes refs the perfect tool for managing local component state.

Using Refs in Templates

When you use a ref inside a Vue component’s template, Vue automatically unwraps the .value property for you. This means you can reference the ref directly without typing .value.

Let’s look at an example:


<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increase</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value  
}
</script>

Notice that in the template, we wrote {{ count }} instead of {{ count.value }}. Vue handles the unwrapping automatically when binding data to the template.

Working with Different Data Types

Refs are not limited to primitive values. You can store any type of data inside a ref: objects, arrays, strings, booleans, or even functions.

Ref with an Object


<script setup>
import { ref } from 'vue'

const user = ref({
  name: 'John',
  age: 25
})

function updateName() {
  user.value.name = 'Alice'
}
</script>

<template>
  <div>
    <p>Name: {{ user.name }}</p>
    <p>Age: {{ user.age }}</p>
    <button @click="updateName">Change Name</button>
  </div>
</template>

Even though user holds an object, Vue automatically tracks reactivity for its nested properties. However, be careful: if you replace the entire object (e.g., user.value = { name: 'Bob', age: 30 }), Vue will still react, but you must always mutate via .value in the script.

Ref vs Reactive

Both ref() and reactive() are used to create reactive state, but they behave differently.

1. ref()

  • Works for any data type (primitive or object).
  • Always accessed via .value.
  • Ideal for single values or DOM references.

2. reactive()

  • Only works with objects (not primitives).
  • Access properties directly (no .value).
  • Ideal for complex state objects.

Here’s a comparison:


import { ref, reactive } from 'vue'

const count = ref(0)
const state = reactive({ count: 0 })

count.value  
state.count  

When to use which?

  • Use ref() for primitives or isolated variables.
  • Use reactive() for structured objects with multiple properties.

Using Refs for DOM Elements

Refs can also reference DOM elements directly. This is particularly useful when you need to interact with an element in the template (e.g., focusing an input, scrolling, etc.).

Example: Focusing an Input Field


<template>
  <div>
    <input ref="inputRef" type="text" placeholder="Type here..." />
    <button @click="focusInput">Focus Input</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const inputRef = ref(null)

function focusInput() {
  inputRef.value.focus()
}
</script>

Here, ref="inputRef" binds the actual DOM element to the inputRef variable once the component is mounted.

Accessing Template Refs in Lifecycle Hooks

Template refs are only populated after the component is mounted. To safely access them, use the onMounted lifecycle hook.


<template>
  <input ref="inputElement" />
</template>

<script setup>
import { ref, onMounted } from 'vue'

const inputElement = ref(null)

onMounted(() => {
  console.log(inputElement.value) // actual <input> element
  inputElement.value.focus()
})
</script>

If you try to access inputElement.value before mounting, it will be null. Always use onMounted for DOM manipulation.

Watching and Computing Refs

You can use watch() and computed() with refs the same way you would with reactive data.

Example: Watching a Ref


<script setup>
import { ref, watch } from 'vue'

const name = ref('John')

watch(name, (newVal, oldVal) => {
  console.log(`Name changed from ${oldVal} to ${newVal}`)
})

setTimeout(() => {
  name.value = 'Alice'
}, 2000)
</script>

Example: Using computed() with Refs


<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>

<template>
  <p>Full name: {{ fullName }}</p>
</template>

Refs in Loops and v-for

When using ref with multiple elements generated via v-for, Vue gives you an array of references.


<template>
  <ul>
    <li v-for="(item, index) in items" :key="index" ref="listRefs">{{ item }}</li>
  </ul>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const items = ref(['Apple', 'Banana', 'Orange'])
const listRefs = ref([])

onMounted(() => {
  console.log(listRefs.value) // array of <li> elements
})
</script>

This is useful when you need to apply logic to multiple DOM nodes after rendering.

Deep Dive: Ref Unwrapping

Vue automatically unwraps refs when you use them in templates or when a ref is a property of a reactive object. This is called ref unwrapping.

Example:


import { ref, reactive } from 'vue'

const name = ref('John')
const state = reactive({
  name,
  age: 30
})

console.log(state.name) // 'John', no .value needed

state.name = 'Alice'
console.log(name.value) // 'Alice'

This works because Vue unwraps ref properties inside a reactive object. However, this only happens one level deep.

Ref in Custom Components

You can also use refs to access child components. This is especially useful for triggering component methods from a parent.

Example: Parent-Child Communication via Ref


// ChildComponent.vue
<script setup>
import { ref } from 'vue'

const message = ref('Hello from Child!')

function greet() {
  alert(message.value)
}
defineExpose({ greet })
</script>

<template>
  <div>Child Component</div>
</template>

// ParentComponent.vue
<template>
  <ChildComponent ref="childRef" />
  <button @click="callChildMethod">Call Child Method</button>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childRef = ref(null)

function callChildMethod() {
  childRef.value.greet()
}
</script>

In this example, the parent component uses childRef to call a method (greet) defined in the child component. The defineExpose function allows you to selectively expose child methods or properties.

Best Practices with Refs

  • Use ref() for primitives – Use ref when dealing with single values like numbers, strings, or booleans.
  • Keep DOM refs null initially – Always initialize DOM refs with null.
  • Access DOM refs only after mount – Use onMounted before interacting with DOM elements.
  • Expose only necessary child methods – Avoid exposing unnecessary internal logic through defineExpose.
  • Don’t mix ref() and reactive() excessively – Keep your state management consistent.

Common Mistakes

1. Forgetting .value in Script

Always use .value when accessing a ref inside the script:


// ❌ Incorrect
count  

// ✅ Correct
count.value  

2. Accessing DOM Refs Too Early

Accessing ref DOM elements before onMounted will return null. Always use lifecycle hooks.

3. Replacing Refs with Non-reactive Values

If you overwrite a ref with a plain variable, it will lose reactivity.


// ❌ Wrong
count = 5

// ✅ Correct
count.value = 5

Combining Ref with v-model

Refs work seamlessly with v-model for two-way binding:


<template>
  <input v-model="name" />
  <p>Hello, {{ name }}</p>
</template>

<script setup>
import { ref } from 'vue'

const name = ref('John')
</script>

This automatically keeps the input field and the ref in sync.

Performance Tips

  • Use shallowRef() if you don’t need deep reactivity.
  • Use triggerRef() to force updates manually for complex nested data.
  • Avoid unnecessary ref() wrapping inside loops or computed properties.

Example: Using shallowRef()


import { shallowRef } from 'vue'

const obj = shallowRef({ nested: { a: 1 } })

obj.value.nested.a = 2
// This won't trigger reactivity because shallowRef only tracks top-level changes

Conclusion

The ref() function is one of the foundational features of Vue 3’s Composition API. It bridges the gap between reactivity, DOM access, and component interaction in a clean and predictable way. Understanding when and how to use refs—alongside reactive(), computed(), and watch()—gives you the power to write scalable and efficient Vue applications.

In summary:

  • Use ref() for simple reactive values.
  • Use reactive() for complex state.
  • Access DOM elements via template refs and onMounted().
  • Be mindful of ref unwrapping and always use .value in scripts.

By mastering refs, you unlock one of Vue 3’s most elegant and flexible patterns for managing state and DOM interaction in modern frontend development.