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>

ただしこれは親が子の内部に踏み込みやすく、強結合を生みやすい。親子間のやり取りは、まず propsemit を優先し、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.inputuseTemplateRef('input') または ref(null)
子コンポーネント参照this.$refs.childuseTemplateRef('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 arrayv-for 内で同じ ref 名を使った時に得られる配列参照
function ref:ref に関数を渡し、参照の保存先や処理を自分で制御する手法

実務での判断軸

  • まず state, props, emit で解決できないかを考える
  • 単一 DOM 要素への直接アクセスなら useTemplateRef() を使う
  • v-for 内で複数要素をまとめて扱うなら ref array を使う
  • ID ベースで厳密に複数要素を管理するなら function ref を使う
  • 子コンポーネント ref は必要最小限にし、公開 API のみを defineExpose() で明示する