本文 AI 產出,尚未審核

Vue3 – 模板語法:Template refs(ref="xxx"

簡介

在 Vue3 中,Template refs(簡稱 ref)是一種直接取得 DOM 元素或子元件實例的方式。雖然 Vue 鼓勵以資料驅動的方式操作 UI,但在某些情境(例如聚焦輸入框、呼叫子元件方法、與第三方函式庫整合)仍需要直接存取底層節點。ref="xxx" 讓我們可以在模板中標記目標,之後在組件的 setupoptions API 中透過 ref 變數取得對應的引用。

掌握 Template refs 能提升開發效率、避免不必要的 DOM 查詢,同時保持 Vue 的響應式特性。以下將從概念說明、實作範例、常見陷阱到最佳實踐,完整介紹在 Vue3 中如何安全、有效地使用 ref


核心概念

1. 基本語法與取得方式

在模板中使用 ref 屬性為目標元素或子元件命名:

<input ref="nameInput" type="text" placeholder="請輸入姓名">

Composition API 中,我們透過 ref(或 shallowRef)取得:

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const nameInput = ref(null)   // 初始化為 null,稍後會被 Vue 填入 DOM 元素

    onMounted(() => {
      // 此時 nameInput.value 已指向 <input> 元素
      nameInput.value.focus()
    })

    return { nameInput }
  }
}

Options API 中,寫法類似,只是 ref 會自動掛在 this.$refs

export default {
  mounted() {
    this.$refs.nameInput.focus()
  }
}

重點:在 Composition API 中,ref 變數必須回傳(return)給模板,否則模板無法存取。

2. 取得子元件實例

<!-- 父層模板 -->
<ChildComponent ref="child" />
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'

export default {
  components: { ChildComponent },
  setup() {
    const child = ref(null)

    onMounted(() => {
      // child.value 為子元件的公開實例
      child.value.exposedMethod()
    })

    return { child }
  }
}

子元件若想限制外部可呼叫的屬性與方法,可使用 defineExpose

export default {
  setup(_, { expose }) {
    const count = ref(0)

    function exposedMethod() {
      console.log('子元件被呼叫!', count.value)
    }

    expose({ exposedMethod })   // 只暴露這個方法
  }
}

3. 多個同名 ref(ref 陣列)

v-for 產生的多筆元素上使用相同的 ref,Vue 會把它們收集成陣列:

<ul>
  <li v-for="(item, i) in items" :key="i" ref="listItem">{{ item }}</li>
</ul>
import { ref, onMounted } from 'vue'

export default {
  setup() {
    const listItem = ref([])   // 會被 Vue 填入多個 <li> 元素

    onMounted(() => {
      // 例如給每個 li 加上紅色底色
      listItem.value.forEach(el => el.style.background = 'pink')
    })

    return { listItem }
  }
}

注意:若 ref 被用於條件渲染 (v-if) 的元素,對應的陣列會隨渲染狀態增減,請在使用前檢查 nullundefined

4. 在 <script setup> 中的簡化寫法

<script setup> 允許直接使用 ref 變數,而不必顯式 return

<template>
  <button ref="btn" @click="handle">點我</button>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const btn = ref(null)

function handle() {
  console.log('按鈕寬度:', btn.value.offsetWidth)
}

onMounted(() => {
  btn.value.focus()
})
</script>

5. 與第三方 UI 函式庫結合

許多 UI 套件(如 Swiper, Chart.js, Quill)需要一個實際的 DOM 容器。使用 ref 可以直接把容器傳給套件:

<div ref="chartContainer" style="height:300px;"></div>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { Chart } from 'chart.js'

export default {
  setup() {
    const chartContainer = ref(null)
    let chartInstance = null

    onMounted(() => {
      chartInstance = new Chart(chartContainer.value, {
        type: 'bar',
        data: { /* ... */ },
        options: { /* ... */ }
      })
    })

    onBeforeUnmount(() => {
      chartInstance?.destroy()
    })

    return { chartContainer }
  }
}

常見陷阱與最佳實踐

陷阱 說明 解決方案
ref 為 null 時直接使用 ref 只在組件掛載後才會被填入,setup 內的同步程式若直接存取會拋錯。 使用 onMountednextTickwatch 確保已取得元素。
v-if 中使用 ref 卻忘記判斷 條件渲染會導致 ref 為 undefined,尤其在 watch 中使用時常見。 先檢查 if (myRef.value),或改用 v-show(保持元素在 DOM)
大量直接操作 DOM 失去 Vue 的聲明式優勢,維護成本升高。 只在不可避免的情境(聚焦、動畫、第三方套件)使用,其他情況盡量使用資料驅動。
子元件未使用 defineExpose 父層仍能取得實例,但會暴露全部內部屬性,增加耦合。 使用 defineExpose 明確限制可存取的 API。
忘記在 <script setup> 中回傳 ref 雖然 <script setup> 自動回傳,但若使用 export default 方式則需手動 return 依照使用方式確認是否需要 return

最佳實踐

  1. 僅在需要時使用 ref:先思考是否能透過 v-model、computed、watch 等方式解決,若真的需要操作 DOM,才使用 ref。
  2. 使用 shallowRef 取代 ref:若只存放 DOM 或元件實例,且不需要深層響應式追蹤,shallowRef 能稍微提升效能。
  3. onMountednextTick 中使用:確保 DOM 已渲染完成。
  4. 保持 ref 命名語意明確:如 searchInputmodalDialog,避免使用過於簡短的 elref1
  5. 清理資源:在 onBeforeUnmount 中銷毀第三方套件或取消事件監聽,防止記憶體泄漏。

實際應用場景

場景 為什麼需要 ref 範例簡述
表單自動聚焦 使用者開啟表單時希望第一個欄位自動取得焦點。 onMounted 中呼叫 inputRef.value.focus()
彈出式對話框 需要在打開時把焦點移到關閉按鈕,或在關閉時返回先前元素。 透過 ref 取得對話框容器與關閉按鈕,配合 focus()
與第三方圖表套件整合 Chart.js、ECharts 等需要一個實際的 <canvas><div> 容器。 如上文的 Chart.js 範例。
自訂指令或動畫 在指令 mounted 中取得元素以執行動畫或拖曳。 const el = elRef.value; el.classList.add('animate')
子元件方法呼叫 父層需要觸發子元件的驗證或重置行為。 父層 childRef.value.validate(),子元件使用 defineExpose 暴露 validate

總結

Template refs 是 Vue3 中一把「直通」DOM 與子元件的鑰匙。透過 ref="xxx" 我們可以在模板中標記目標,並在 setup(Composition API)或 this.$refs(Options API)裡安全取得。正確使用時,它能:

  • 簡化聚焦、捲動、動畫等 UI 操作
  • 讓第三方函式庫無縫接入(只要提供容器節點)。
  • 在父子元件間提供受控的介面(配合 defineExpose)。

同時,也要避免過度依賴直接 DOM 操作,保持 Vue 的聲明式精神。遵守「在 onMounted/nextTick 之後使用、必要時清理資源、適度使用 shallowRef」等最佳實踐,能讓你的 Vue3 專案在可維護性與效能上都保持最佳狀態。

祝你在開發過程中玩得開心,讓 Vue3 的模板語法為你的應用增添更多彈性與力量! 🚀