ビュー 3: テンプレートリファレンスと DOM 操作: ref、useTemplateRef、$refs
テンプレートリファレンスを使用すると、DOM要素や子コンポーネントのインスタンスに直接アクセスできます。たとえば、input.focus()を呼び出したり、コンポーネントのメソッドにアクセスしたりできます。Vue 3.5では、TypeScriptの型推論と連携して機能する、より強力なuseTemplateRefコンポジションAPIが導入されました。
テンプレートリファレンスは「最後の手段」です。Vueでは、ほとんどの問題を解決するためにref、reactive、props、emitの使用を推奨しています。テンプレートリファレンスは、DOMやコンポーネントのインスタンスを直接操作する必要がある場合にのみ使用すべきです。
1. 学習内容
ref="el"テンプレート参照の基礎useTemplateRef(Vue 3.5 以降での使用を推奨)- DOM要素へのアクセス(focus、scrollIntoViewなど)
- 子コンポーネントのインスタンスへのアクセス(
defineExposeメソッドを使用) $refsコンポジションAPIを使用した代替構文v-for内の配列- 5つの主なユースケースと4つのアンチパターン
2. ログインフォームにおける「オートフォーカス」のジレンマ
(1) 課題:モーダルが開いた際、入力フィールドに自動的にフォーカスを合わせるにはどうすればよいでしょうか?
アリスは、ユーザー名入力欄に自動的にフォーカスが当たるようにするログインモーダルを作成しました:
JS
// ❌ The "Flip" Version:Directly querySelector
onMounted(() => {
const input = document.querySelector('.username-input')
input.focus() // ❌ Does not meet the requirements Vue Philosophy
})
Vueの哲学:DOMへの直接操作は避け、代わりにTemplate Refsを使用する。
プロダクトマネージャーのチャーリー:
「アリス、モーダルが開いたとき、ユーザーがすぐに文字を入力できるよう、ユーザー名入力欄に自動的にフォーカスが当たるようにしてください。」
(2) テンプレート参照の表示 解決策
VUE
<template>
<!-- ref="usernameInput" Mark this element -->
<input ref="usernameInput" type="text" class="username-input">
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 1. Create ref Variable(The name must match the template ref Match)
const usernameInput = ref(null)
onMounted(() => {
// 2. DOM Ready,Visit input Element
usernameInput.value.focus()
})
</script>
VUE
<!-- Pop-up Scenarios:Click the button to open modal,Automatic focus -->
<template>
<button @click="showModal = true">Login</button>
<Modal v-if="showModal" @close="showModal = false">
<input ref="usernameInput" type="text">
</Modal>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const showModal = ref(false)
const usernameInput = ref(null)
async function openModal() {
showModal.value = true
// ✅ Wait for DOM update, then focus
await nextTick()
usernameInput.value.focus()
}
</script>
(3) 収益
「Template Refs」の使用後:
- コードの可読性:「この要素を参照したい」と明確に記述する
- Vueの哲学に沿っている:DOMを直接操作しない
- 型安全性:TypeScript は要素の型を推論します
- 正しいライフサイクル:
onMountedまたはnextTickにアクセスしてください
3. ref の基本的な使い方
(1) 文字列参照 (Vue 2 スタイル、非推奨)
JS
// ❌ Not recommended:String ref
export default {
mounted() {
this.$refs.input.focus()
}
}
(2) ref 変数(Vue 3 で推奨)
VUE
<template>
<input ref="inputRef">
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
console.log(inputRef.value) // <input> DOM Element
inputRef.value.focus() // Call DOM API
})
</script>
(3) 5つの基本演算
JS
// 1. Visit DOM Element
inputRef.value // <input> Element
// 2. Call DOM API
inputRef.value.focus()
inputRef.value.blur()
inputRef.value.select()
inputRef.value.scrollIntoView()
// 3. Read/Edit DOM Properties
inputRef.value.value // input value
inputRef.value.disabled // disabled Properties
inputRef.value.style.color = 'red' // Edit Style
// 4. Monitoring DOM Events (Not recommended, use @event)
inputRef.value.addEventListener('focus', handler)
// 5. Accessing a Child Component Instance(For more details, see 17.4)
childRef.value.someMethod()
4. useTemplateRef(Vue 3.5 以降で推奨)
(1) なぜ useTemplateRef が必要なのでしょうか?
<script setup> 内の変数 ref には 2 つの名前(テンプレート ref と変数名)が必要であり、これが不整合を招きやすくなります。useTemplateRef では、1 つの名前でこれを処理しており、TypeScript の型推論はより強力です。
VUE
<template>
<input ref="usernameInput">
</template>
<script setup>
import { useTemplateRef, onMounted } from 'vue'
// ✅ One Name Does It All(Vue 3.5+)
const inputRef = useTemplateRef('usernameInput')
onMounted(() => {
inputRef.value.focus() // Type automatically inferred as HTMLInputElement
})
</script>
(2) 5つの主な利点
| 利点 | 説明 |
|---|---|
| 型推論 | TypeScript はこれを自動的に HTMLInputElement として認識します |
| 安全な名前変更 | 1か所で名前を変更するだけで、IDEが自動的に同期します |
| 名前の不一致を避ける | テンプレートの参照と変数名が一致しない場合、エラーが発生します |
| セットアップの簡略化 | const inputRef = ref(null) は不要 |
| DevToolsの機能強化 | Vue DevTools 5.x への対応 |
(3) 完全比較
VUE
<!-- Old notation:Variable ref -->
<template>
<input ref="usernameInput">
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null) // The name may not match the template.
onMounted(() => inputRef.value.focus())
</script>
<!-- New Writing Style:useTemplateRef(Vue 3.5+ Recommendations)-->
<template>
<input ref="usernameInput">
</template>
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputRef = useTemplateRef('usernameInput') // One Name Does It All
onMounted(() => inputRef.value.focus())
</script>
5. 子コンポーネントのインスタンスへのアクセス
(1) defineExpose: メソッドの公開
VUE
<!-- Child component:MyInput.vue -->
<template>
<input ref="inputRef" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({ modelValue: String })
const inputRef = ref(null)
// Exposed for use by the parent component
defineExpose({
focus: () => inputRef.value?.focus(),
select: () => inputRef.value?.select(),
clear: () => { inputRef.value.value = '' }
})
</script>
(2) 親コンポーネントへのアクセス
VUE
<template>
<MyInput ref="myInputRef" v-model="searchQuery" />
<button @click="focusInput">Focus Input</button>
</template>
<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'
const searchQuery = ref('')
const myInputRef = ref(null)
function focusInput() {
myInputRef.value.focus() // Calling Methods Exposed by Child Components
myInputRef.value.select() // You can also use chained calls
}
</script>
(3) 5つの主なユースケース
| シナリオ | 子コンポーネントによって公開される | 親コンポーネントによって呼び出される |
|---|---|---|
| フォーム特集 | focus() |
inputRef.focus() |
| フォームをクリア | clear() |
formRef.clear() |
| データを再読み込み | reload() |
tableRef.reload() |
| ポップアップを開く | open() |
modalRef.open() |
| フォームを送信 | submit() |
formRef.submit() |
6. v-for 内の ref 配列
(1) 基本的な使い方
VUE
<template>
<ul>
<!-- In v-for, ref auto-collected into array -->
<li v-for="item in items" :key="item.id" ref="itemRefs">
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
])
// ✅ Array Format
const itemRefs = ref([])
onMounted(() => {
// Page 2 Items DOM
itemRefs.value[1].style.color = 'red'
})
</script>
(2) 動的参照(動的な要素数を指定するv-for)
VUE
<template>
<button v-for="i in count" :key="i" :ref="el => buttonRefs[i] = el">
Button {{ i }}
</button>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const count = ref(3)
const buttonRefs = ref({})
onMounted(() => {
// buttonRefs[0] = 1st button
// buttonRefs[1] = 2nd button
console.log(buttonRefs.value[0])
})
</script>
(3) 覚えておくべき5つのポイント
| 注意事項 | 説明 |
|---|---|
| 配列の順序 | v-for 内のデータの順序と一致する |
| リアクティブ | ref 配列の変更にはウォッチが必要 |
| 条件付きレンダリング | v-if の後、refs は更新されない場合がある |
| 動的変数の数 | オブジェクト参照または関数参照の使用 |
| パフォーマンス | リファレンスの数が多い(100以上)と、レンダリングが遅くなる可能性があります |
7. 完全な例:5つの実例
(1) ▶ サンプル:. ログインフォームへの自動フォーカス
VUE
<template>
<form @submit.prevent="handleLogin">
<input ref="usernameRef" v-model="username" placeholder="Username">
<input ref="passwordRef" v-model="password" type="password" placeholder="Password">
<button>Login</button>
</form>
</template>
<script setup>
import { ref, onMounted, useTemplateRef } from 'vue'
// ❌ Old notation
const usernameRef = ref(null)
const passwordRef = ref(null)
// ✅ New Writing Style(Vue 3.5+)
// const usernameRef = useTemplateRef('usernameRef')
// const passwordRef = useTemplateRef('passwordRef')
const username = ref('')
const password = ref('')
onMounted(() => {
usernameRef.value.focus() // Autofocus Username
})
function handleLogin() {
console.log('Login:', username.value, password.value)
}
</script>
(2) ▶ サンプル:. ページの一番下まで自動スクロールする
VUE
<template>
<div ref="messagesRef" class="messages">
<div v-for="msg in messages" :key="msg.id">{{ msg.text }}</div>
</div>
<input v-model="newMessage" @keyup.enter="sendMessage">
<button @click="sendMessage">Send</button>
</template>
<script setup>
import { ref, nextTick } from 'vue'
const messages = ref([])
const newMessage = ref('')
const messagesRef = ref(null)
async function sendMessage() {
messages.value.push({ id: Date.now(), text: newMessage.value })
newMessage.value = ''
// ✅ Wait for DOM update, then scroll
await nextTick()
messagesRef.value.scrollTop = messagesRef.value.scrollHeight
}
</script>
(3) ▶ サンプル:. 親コンポーネントが子コンポーネントのメソッドを呼び出す場合
VUE
<!-- Child component:FormValidator.vue -->
<template>
<form>
<input v-model="email" placeholder="Email">
<input v-model="password" type="password" placeholder="Password">
</form>
</template>
<script setup>
import { ref } from 'vue'
const email = ref('')
const password = ref('')
const emailRef = ref(null)
const passwordRef = ref(null)
defineExpose({
validate: () => {
if (!email.value) {
emailRef.value.focus()
return false
}
if (!password.value || password.value.length < 6) {
passwordRef.value.focus()
return false
}
return true
},
reset: () => {
email.value = ''
password.value = ''
}
})
</script>
<!-- Parent Component:LoginPage.vue -->
<template>
<FormValidator ref="formRef" />
<button @click="submit">Submit</button>
</template>
<script setup>
import { ref } from 'vue'
import FormValidator from './FormValidator.vue'
const formRef = ref(null)
function submit() {
if (formRef.value.validate()) {
console.log('Valid!')
} else {
console.log('Invalid!')
}
}
</script>
(4) ▶ サンプル:. useTemplateRef の型推論
VUE
<template>
<input ref="usernameInput" type="text">
<MyChart ref="chartComponent" :data="chartData" />
</template>
<script setup lang="ts">
import { ref, useTemplateRef, onMounted } from 'vue'
import MyChart from './MyChart.vue'
// ✅ TypeScript Inference:HTMLInputElement | null
const usernameInput = useTemplateRef<HTMLInputElement>('usernameInput')
// ✅ TypeScript Inference:InstanceType<typeof MyChart> | null
const chartComponent = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')
onMounted(() => {
// usernameInput.value Automatic is HTMLInputElement
usernameInput.value?.focus()
// chartComponent.value "Auto" is a component instance
chartComponent.value?.refresh()
})
</script>
(5) ▶ サンプル:. よくある5つの間違いに関するクイックリファレンス
| エラー | 症状 | 解決策 |
|---|---|---|
| テンプレートの参照にスペルミスがあります | ref.value が null です | 参照名が一致しているか確認してください |
トップレベルの setup にアクセス |
ref.value は null です |
onMounted を使用してください |
| ポップアップ内でのアクセス | 機能しない | nextTick または同様の DOM メソッドを使用する |
| v-for による配列参照 | インデックスのずれ | :key を使用して順序を維持する |
| コンポーネント間の参照 | 未定義 | 子コンポーネントは defineExpose を使用 |
(6) ▶ サンプル:. 5つの主要な参照型の比較
| 種類 | 例 | 適用対象 |
|---|---|---|
| DOM要素 | ref="inputRef" → HTMLInputElement |
input/divへのアクセス |
| コンポーネントインスタンス | ref="childRef" → コンポーネントインスタンス |
トーンコンポーネントのメソッド |
| v-for 配列 | ref="itemRefs" → 配列 |
リスト項目のアクセス |
| 機能参照 | :ref="el => ..." → 単一要素 |
動的カウント |
| 文字列リファレンス | ref="name" → this.$refs |
ビュー2スタイル(推奨されません) |
❓ よくある質問
Q テンプレートリファレンスはどのような場合に使用すべきですか?
A 以下の4つの状況です:(1) DOMの操作(フォーカス/スクロール/キャンバス);(2) コンポーネントメソッドの呼び出し;(3) サードパーティ製ライブラリ(ECharts/Mapbox)の統合;(4) 要素の寸法の測定。それ以外のシナリオでは、refs、reactive、またはpropsを使用してください。
Q Vue 3.5 以降では
useTemplateRef は必須ですか?A はい。Vue 3.5 以降でのみサポートされています。Vue 3.4 以前では、
ref 変数を使用してください。TypeScript の型推論を使用することを推奨します。Q テンプレートリファレンスと
useRef(React)の違いは何ですか?A Reactの
useRef は可変リファレンスを返すのに対し、Vueのテンプレートリファレンスはリファレンスオブジェクトを返します。Vue 3.5以降で導入された useTemplateRef は、Reactの useRef に近いAPIを持っています。Q 子コンポーネントはどのメソッドを公開すべきですか?
A 親コンポーネントが実際に必要とするメソッド(focus、clear、validateなど)のみを公開してください。その他の内部メソッドは公開しないでください(カプセル化の原則)。
Q
v-for の ref 配列はいつ更新されますか?A
v-for が再レンダリングされるたびに更新されます。また、v-if の状態が切り替わった際にも更新されます。ref 配列に対して watch を使用することで、変更に対応することができます。Q テンプレートリファレンスは provide/inject と競合しますか?
A いいえ、競合しません。テンプレートリファレンスは「親から子へのアクセス」に使用されるのに対し、provide/inject は「レベル間のデータ共有」に使用されます。これらは併用可能です。
Q TypeScriptでrefに型を指定するにはどうすればよいですか?
A
useTemplateRef<HTMLInputElement>('usernameInput') または ref<HTMLInputElement | null>(null) を使用します。📖 まとめ
- テンプレート参照は、DOM要素や子コンポーネントのインスタンスに直接アクセスするために使用されます
- 記述方法:変数リファレンス(Vue 3)/useTemplateRef(Vue 3.5 以降;推奨)/文字列リファレンス(Vue 2 スタイル;非推奨)
- 5つの基本操作:フォーカス/スクロール/選択/プロパティの変更/コンポーネントの調整
defineExposeにより、子コンポーネントは親コンポーネントに対してメソッドを公開できるようになりますv-forでは、refが自動的に配列に格納されます- 5つの主要なシナリオ:オートフォーカス/ページ下部へのスクロール/トーン法/サードパーティ製ツールの連携/要素サイズの測定
- 4つのアンチパターン:スペルミス/トップレベルへのアクセス/
nextTickを使用しないポップアップ/コンポーネント間のref
📝 練習問題
(1) 基礎問題(難易度:⭐)
簡単なオートフォーカスフォームを実装する:
- 入力欄 1つ + ボタン 1つ
- ページが読み込まれると、入力フィールドに自動的にフォーカスが移ります
- ボタンがクリックされると、ボタンのテキストが「送信済み」に変わり、ボタンが非アクティブになります
(2) 上級問題(難易度:⭐⭐)
自動的に一番下までスクロールするチャットボックスを実装する:
- メッセージ一覧(divコンテナ)
- 入力欄 + 送信ボタン
- メッセージが送信されると、そのメッセージはリストに追加され、自動的にリストの一番下までスクロールされます
- メッセージのレンダリングには
v-forを使用し、DOM の管理にはref配列を使用します
(3) チャレンジ問題(難易度:⭐⭐⭐)
「親子コンポーネント+テンプレートリファレンス」の完全なシステムを実装する:
- FormValidator サブコンポーネント:validate() および reset() メソッドを提供します
- LoginPage の親コンポーネント:
validateメソッドを呼び出して検証を実行し、検証に失敗した場合はエラーを表示する - 5つの入力フィールド(ユーザー名/メールアドレス/パスワード/電話番号/キャプチャ)
- useTemplateRef (Vue 3.5 以降) + TypeScript の厳格な型付け
- ポップアップが開いたときに、最初の入力フィールドに自動的にフォーカスを合わせる
- エラーが発生した場合は、最初のエラー欄に注目してください



