更新日:2025/10/29
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!
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.
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.
Refs are not limited to primitive values. You can store any type of data inside a ref: objects, arrays, strings, booleans, or even functions.
<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.
Both ref() and reactive() are used to create reactive state, but they behave differently.
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?
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.).
<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.
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.
You can use watch() and computed() with refs the same way you would with reactive data.
<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>
<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>
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.
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.
You can also use refs to access child components. This is especially useful for triggering component methods from a parent.
// 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.
Always use .value when accessing a ref inside the script:
// ❌ Incorrect
count
// ✅ Correct
count.value
Accessing ref DOM elements before onMounted will return null. Always use lifecycle hooks.
If you overwrite a ref with a plain variable, it will lose reactivity.
// ❌ Wrong
count = 5
// ✅ Correct
count.value = 5
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.
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
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:
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.
Referencing Values with Refs in Vue 3
オフショア開発のご紹介資料