404 Not Found

404 Not Found


nginx

ビュー 3: テンプレートリファレンスと DOM 操作: ref、useTemplateRef、$refs

テンプレートリファレンスを使用すると、DOM要素や子コンポーネントのインスタンスに直接アクセスできます。たとえば、input.focus()を呼び出したり、コンポーネントのメソッドにアクセスしたりできます。Vue 3.5では、TypeScriptの型推論と連携して機能する、より強力なuseTemplateRefコンポジションAPIが導入されました。

テンプレートリファレンスは「最後の手段」です。Vueでは、ほとんどの問題を解決するためにref、reactive、props、emitの使用を推奨しています。テンプレートリファレンスは、DOMやコンポーネントのインスタンスを直接操作する必要がある場合にのみ使用すべきです。

1. 学習内容


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」の使用後:


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) を使用します。

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

簡単なオートフォーカスフォームを実装する:

(2) 上級問題(難易度:⭐⭐)

自動的に一番下までスクロールするチャットボックスを実装する:

(3) チャレンジ問題(難易度:⭐⭐⭐)

「親子コンポーネント+テンプレートリファレンス」の完全なシステムを実装する:

  1. FormValidator サブコンポーネント:validate() および reset() メソッドを提供します
  2. LoginPage の親コンポーネント:validate メソッドを呼び出して検証を実行し、検証に失敗した場合はエラーを表示する
  3. 5つの入力フィールド(ユーザー名/メールアドレス/パスワード/電話番号/キャプチャ)
  4. useTemplateRef (Vue 3.5 以降) + TypeScript の厳格な型付け
  5. ポップアップが開いたときに、最初の入力フィールドに自動的にフォーカスを合わせる
  6. エラーが発生した場合は、最初のエラー欄に注目してください
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%