Vue3 – 模板語法:Template refs(ref="xxx")
簡介
在 Vue3 中,Template refs(簡稱 ref)是一種直接取得 DOM 元素或子元件實例的方式。雖然 Vue 鼓勵以資料驅動的方式操作 UI,但在某些情境(例如聚焦輸入框、呼叫子元件方法、與第三方函式庫整合)仍需要直接存取底層節點。ref="xxx" 讓我們可以在模板中標記目標,之後在組件的 setup 或 options 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) 的元素,對應的陣列會隨渲染狀態增減,請在使用前檢查null或undefined。
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 內的同步程式若直接存取會拋錯。 |
使用 onMounted、nextTick 或 watch 確保已取得元素。 |
在 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。 |
最佳實踐
- 僅在需要時使用 ref:先思考是否能透過
v-model、computed、watch 等方式解決,若真的需要操作 DOM,才使用 ref。 - 使用
shallowRef取代ref:若只存放 DOM 或元件實例,且不需要深層響應式追蹤,shallowRef能稍微提升效能。 - 在
onMounted或nextTick中使用:確保 DOM 已渲染完成。 - 保持 ref 命名語意明確:如
searchInput、modalDialog,避免使用過於簡短的el、ref1。 - 清理資源:在
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 的模板語法為你的應用增添更多彈性與力量! 🚀