Template Refs - DOM参照と子コンポーネント参照
ロードマップ: Vue.js学習ロードマップ
What(何についてか)
Template refs は、テンプレート上の特定 DOM 要素や子コンポーネントのインスタンスに対して、スクリプト側から直接参照を取得する仕組みである。Vue は通常、state を通じて宣言的に UI を制御するが、一部の場面では DOM API や外部ライブラリ初期化のために直接参照が必要になる。
Vue 3.5 以降では useTemplateRef() が基本の取得手段であり、テンプレート側の ref 属性名と対応させて参照を受け取る。
Why(なぜ必要か)
Vue の基本思想は「DOM を直接いじる前に state で表現する」ことである。しかし、以下のような場面では宣言的制御だけでは足りない。
- マウント直後に input へフォーカスしたい
- 要素サイズや位置を計測したい
- DOM 要素に対して 3rd party ライブラリを初期化したい
- 特定の子コンポーネントが公開するメソッドを親から呼びたい
Template refs は、こうした局所的な imperative 処理のための逃げ道である。ただし多用すると、Vue のデータフローから外れた処理が増え、保守性が落ちやすい。
How(どう動くか)
単一 DOM 要素の参照
useTemplateRef() はテンプレート上の ref="..." と対応する参照を作る。DOM 要素は初回レンダー後に生成されるため、参照先を安全に使えるのは onMounted() 以降である。
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const input = useTemplateRef('my-input')
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="my-input" />
</template>マウント前や v-if による非表示時には null になりうるため、監視する場合は if (input.value) のような null 考慮が必要である。
ライフサイクルとの関係
Template ref は script setup 実行時点ではまだ実体を持たない。処理順序は次の通りである。
flowchart TD A["script setup / setup 実行"] --> B["useTemplateRef で参照用の箱を作成"] B --> C["Vue がテンプレートをレンダー"] C --> D["DOM 要素を生成"] D --> E["ref.value に実体を代入"] E --> F["onMounted 実行"]
このため、template ref は「初期値として要素を保持している」のではなく、「レンダー結果として後から要素が入る」仕組みである。
子コンポーネントへの ref
ref は DOM 要素だけでなく子コンポーネントにも付与できる。親はその子コンポーネントのインスタンス参照を受け取れる。
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = useTemplateRef('child')
onMounted(() => {
// childRef.value に <Child /> の参照が入る
})
</script>
<template>
<Child ref="child" />
</template>ただしこれは親が子の内部に踏み込みやすく、強結合を生みやすい。親子間のやり取りは、まず props と emit を優先し、component ref はそれで表現しにくい場合に限定して使う。
<script setup> を使う子コンポーネントはデフォルトで private である。親に公開したい値やメソッドがある場合は defineExpose() で明示する。
<script setup>
import { ref } from 'vue'
const count = ref(0)
function focusInnerInput() {
// ...
}
defineExpose({
count,
focusInnerInput
})
</script>v-for 内の ref
v-for の中で同じ ref を使うと、対応する参照は配列になる。
<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'
const list = ref(['A', 'B', 'C'])
const itemRefs = useTemplateRef('items')
onMounted(() => {
console.log(itemRefs.value)
})
</script>
<template>
<ul>
<li v-for="item in list" ref="items">
{{ item }}
</li>
</ul>
</template>これは複数要素をまとめて扱う場合に便利だが、配列の並びを元データの順序と厳密に 1 対 1 対応させる前提では使わない方が安全である。
Function refs
ref には文字列ではなく関数も渡せる。これにより、参照先をどこにどう保存するかを自分で決められる。
<input :ref="(el) => { /* el を任意の場所へ保存する */ }">単一要素なら useTemplateRef() の方が素直である。function ref が有効なのは、複数要素を ID ベースで管理したい場合や、mount / unmount のたびに独自処理を差し込みたい場合である。
<script setup lang="ts">
import { ref } from 'vue'
const items = ref([
{ id: 101, name: 'A' },
{ id: 205, name: 'B' },
{ id: 309, name: 'C' }
])
const itemEls = new Map<number, HTMLLIElement>()
function scrollToItem(id: number) {
itemEls.get(id)?.scrollIntoView()
}
</script>
<template>
<ul>
<li
v-for="item in items"
:key="item.id"
:ref="(el) => {
if (el) itemEls.set(item.id, el as HTMLLIElement)
else itemEls.delete(item.id)
}"
>
{{ item.name }}
</li>
</ul>
</template>このように function ref は、ref 配列では扱いにくい「識別子ベースの DOM 管理」に向いている。
Options API → Composition API 差分(該当箇所のみ、補足として)
| 項目 | Options API(旧) | Composition API(新) |
|---|---|---|
| DOM 参照取得 | this.$refs.input | useTemplateRef('input') または ref(null) |
| 子コンポーネント参照 | this.$refs.child | useTemplateRef('child') |
| 公開範囲の制御 | expose オプション | defineExpose() |
this 依存 | this.$refs に依存 | this 不要、変数参照で扱う |
Key Concepts
| 用語 | 説明 |
|---|---|
| template ref | テンプレート上の DOM 要素や子コンポーネントへの参照を取得する仕組み |
useTemplateRef() | Vue 3.5 以降で template ref を受け取るための Composition API helper |
| component ref | 子コンポーネントのインスタンスへの参照。強結合になりやすいため慎重に使う |
defineExpose() | <script setup> の子コンポーネントが親へ公開する値やメソッドを明示する macro |
| ref array | v-for 内で同じ ref 名を使った時に得られる配列参照 |
| function ref | :ref に関数を渡し、参照の保存先や処理を自分で制御する手法 |
実務での判断軸
- まず state,
props,emitで解決できないかを考える - 単一 DOM 要素への直接アクセスなら
useTemplateRef()を使う v-for内で複数要素をまとめて扱うなら ref array を使う- ID ベースで厳密に複数要素を管理するなら function ref を使う
- 子コンポーネント ref は必要最小限にし、公開 API のみを
defineExpose()で明示する