本文 AI 產出,尚未審核

Vue3 模板語法:指令(Directives)概念


簡介

在 Vue3 中,**指令(Directives)**是連結 HTML 模板Vue 實例 的橋樑。透過指令,我們可以在 DOM 元素上宣告式地綁定行為、屬性或事件,而不需要手動操作原生 DOM。指令的設計初衷是讓 UI 的描述保持 簡潔、可讀,同時保留 高彈性 的程式邏輯。

對於剛踏入前端框架的開發者而言,熟悉指令的使用與原理是掌握 Vue3 模板語法 的關鍵;對於已有一定經驗的開發者,深入了解內建指令的細節與自訂指令的編寫,則能在大型專案中提升 可維護性開發效率。本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你完整掌握 Vue3 指令的運用。


核心概念

1. 什麼是指令?

Vue 的指令是一種以 v- 為前綴的特殊屬性,語法形式為 v-指令名稱:參數="表達式"。在編譯階段,Vue 會將指令轉換成對應的 渲染函式,於資料變更時自動更新 DOM。

  • 內建指令v-bindv-onv-ifv-forv-showv-modelv-textv-html 等。
  • 自訂指令:開發者可以透過 app.directive() 註冊自己的指令,以實作特殊的 DOM 行為(如聚焦、拖曳等)。

2. 生命週期鉤子

指令在綁定到元素時會依序觸發以下鉤子,了解它們有助於寫出正確且效能佳的自訂指令:

鉤子 說明
created 指令第一次綁定到元素時呼叫,此時尚未掛載到 DOM。
beforeMount 元素即將插入父節點前呼叫。
mounted 元素已插入 DOM,適合執行需要 DOM 的操作(如 focusaddEventListener)。
beforeUpdate 資料變更導致指令更新前呼叫。
updated 元素更新完畢後呼叫。
beforeUnmount 元素即將被移除前呼叫。
unmounted 元素已從 DOM 移除,適合清理資源(如移除監聽器)。

3. 常用內建指令解析

3.1 v-bind(簡寫 :

將資料綁定到 HTML 屬性。支援 動態屬性名稱多屬性一次綁定v-bind="obj")。

<!-- 動態綁定 class -->
<div :class="{ active: isActive, disabled: isDisabled }"></div>

<!-- 一次綁定多個屬性 -->
<button v-bind="buttonAttrs">送出</button>

3.2 v-on(簡寫 @

為元素添加 事件監聽器,支援 修飾符.stop.prevent.once.capture 等)以及 事件參數

<!-- 點擊事件,阻止預設行為 -->
<a href="https://example.com" @click.prevent="goHome">回首頁</a>

<!-- 只觸發一次的自訂事件 -->
<button @submit.once="handleSubmit">送出</button>

3.3 v-if / v-else-if / v-else

根據條件 有條件地渲染 整個元素與其子樹。與 v-show 的差異在於 v-if真正移除/新增 DOM,適合切換頻率較低的情況。

<p v-if="status === 'loading'">載入中…</p>
<p v-else-if="status === 'error'">發生錯誤</p>
<p v-else>資料已完成</p>

3.4 v-for

陣列或物件 上迭代,產生多筆相同結構的元素。常與 :key 搭配,提升重新渲染效能。

<ul>
  <li v-for="(item, index) in list" :key="item.id">
    {{ index + 1 }}. {{ item.name }}
  </li>
</ul>

3.5 v-model

表單元件 上實現 雙向綁定。Vue3 支援 修飾符.lazy.number.trim)以及 自訂指令v-model:propName)以對應非原生的 value/input 事件。

<input v-model.trim="username" placeholder="請輸入使用者名稱" />

4. 自訂指令實作範例

以下示範三個常見的自訂指令:自動聚焦拖曳點擊外部關閉。每個範例皆說明生命週期鉤子與清理工作。

4.1 v-focus – 元素掛載後自動聚焦

// main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 註冊全域指令 v-focus
app.directive('focus', {
  // 元素插入 DOM 後執行
  mounted(el) {
    el.focus()
  }
})

app.mount('#app')
<!-- 使用方式 -->
<input type="text" v-focus placeholder="一進來就自動聚焦" />

重點:只需在 mounted 鉤子聚焦即可,無需在 unmounted 做清理,因為聚焦不會留下副作用。

4.2 v-drag – 讓元素可拖曳

app.directive('drag', {
  mounted(el) {
    el.style.position = 'relative'
    let startX, startY, initialX, initialY

    const onMouseDown = (e) => {
      startX = e.clientX
      startY = e.clientY
      const rect = el.getBoundingClientRect()
      initialX = rect.left
      initialY = rect.top
      document.addEventListener('mousemove', onMouseMove)
      document.addEventListener('mouseup', onMouseUp)
    }

    const onMouseMove = (e) => {
      const dx = e.clientX - startX
      const dy = e.clientY - startY
      el.style.left = `${initialX + dx}px`
      el.style.top = `${initialY + dy}px`
    }

    const onMouseUp = () => {
      document.removeEventListener('mousemove', onMouseMove)
      document.removeEventListener('mouseup', onMouseUp)
    }

    el.addEventListener('mousedown', onMouseDown)
  },

  // 清除事件監聽,避免記憶體洩漏
  unmounted(el) {
    el.removeEventListener('mousedown', onMouseDown)
  }
})
<div v-drag class="box">拖我!</div>

<style>
.box {
  width: 120px;
  height: 80px;
  background: #42b983;
  color: #fff;
  line-height: 80px;
  text-align: center;
  cursor: move;
}
</style>

最佳實踐:在 unmounted 內務必移除所有在 mounted 時註冊的全域事件,否則會導致 記憶體洩漏

4.3 v-click-outside – 點擊元素外部時觸發

app.directive('click-outside', {
  beforeMount(el, binding) {
    // 事件處理器需要保存於 el 上,才能在 unmounted 時正確移除
    el.__vueClickOutside__ = (event) => {
      if (!el.contains(event.target)) {
        // 呼叫傳入的回呼函式
        binding.value(event)
      }
    }
    document.addEventListener('click', el.__vueClickOutside__)
  },

  unmounted(el) {
    document.removeEventListener('click', el.__vueClickOutside__)
    delete el.__vueClickOutside__
  }
})
<template>
  <div class="modal" v-if="show" v-click-outside="close">
    <p>點擊外部即可關閉此對話框</p>
  </div>
  <button @click="show = true">開啟 Modal</button>
</template>

<script setup>
import { ref } from 'vue'
const show = ref(false)
const close = () => (show.value = false)
</script>

<style>
.modal {
  position: fixed;
  top: 30%;
  left: 50%;
  transform: translateX(-50%);
  background: #fff;
  padding: 20px;
  border: 1px solid #ddd;
}
</style>

技巧:使用 el.__vueClickOutside__ 暴露給指令本身,避免在 unmounted 時找不到正確的參考。


常見陷阱與最佳實踐

陷阱 說明 解決方式
指令名稱拼寫錯誤 Vue 只會辨識以 v- 開頭的指令,拼寫錯誤會變成普通屬性,導致無效。 使用 IDE 的自動補全或在 app.directive() 時統一命名。
忘記加 :key (在 v-for 中) 沒有 key 會導致 Vue 以 最小化重排 的方式更新,可能產生錯誤的 UI 狀態。 為每個迭代項目提供唯一且穩定的 key(如資料庫 ID)。
v-if 中使用 v-model v-if 會移除整個節點,導致 v-model 的狀態被重置。 若需要保留狀態,改用 v-show 或將狀態提升至父層。
自訂指令未清理事件 事件監聽留在全域,會造成記憶體洩漏與意外觸發。 unmounted 鉤子中 一定 移除所有註冊的事件。
過度使用 v-html 直接插入未經過濾的 HTML 會產生 XSS 風險。 僅在可信來源的內容使用,或透過 DOMPurify 之類的套件過濾。
v-bind 中使用函式 每次渲染都會重新建立函式,降低效能。 盡量使用 計算屬性方法返回值,避免在模板內直接寫函式。

最佳實踐總結

  1. 保持指令簡潔:指令的職責應該單一,例如「聚焦」只做聚焦,「點擊外部」只負責偵測外部點擊。
  2. 遵循生命週期:所有需要操作 DOM 的行為放在 mounted,清理工作放在 unmounted
  3. 使用修飾符:合理利用 .stop.prevent.once 等修飾符,減少程式碼冗餘。
  4. 避免過度嵌套 v-if / v-for:過深的條件與迭代會使模板難以閱讀與維護。可抽離成子元件或使用計算屬性預先整理資料。
  5. 型別安全:在 v-model 上使用 .number.trim,確保取得的資料型別符合預期。

實際應用場景

場景 可能使用的指令 為什麼選擇它
表單驗證 v-modelv-bind:classv-on:submit.prevent 雙向綁定即時取得輸入值,class 動態切換錯誤樣式,prevent 防止頁面重新整理。
彈出式選單 / Modal v-ifv-showv-click-outside(自訂) v-if 控制掛載與釋放,v-click-outside 處理點擊外部關閉的需求。
列表排序與拖曳 v-forv-drag(自訂) v-for 產生可拖曳的項目,v-drag 完成 UI 交互。
條件式載入資源 v-ifv-bind:srcv-on:load 只在需要時載入圖片或影片,load 事件可做懶加載或錯誤處理。
動態樣式切換 v-bind:classv-bind:style 依據資料狀態即時變更 CSS 類別或行內樣式,實現主題切換、暗色模式等。

案例說明:假設在一個電商平台的商品列表頁,我們需要在使用者點擊「加入購物車」後顯示一個暫時的提示框。可以使用 v-show 控制提示框的顯示(因為切換頻繁),同時搭配 v-bind:class 依據成功或失敗切換樣式。若提示框需要在點擊外部時自動關閉,則加入 v-click-outside 自訂指令即可完成整個需求。


總結

Vue3 的 指令 為模板提供了 宣告式可組合 的方式來操作 DOM。透過 v-bindv-onv-ifv-forv-model 等內建指令,我們可以快速完成屬性綁定、事件監聽、條件渲染與列表迭代等常見需求;而自訂指令則讓開發者在 特定 UI 行為(如聚焦、拖曳、點擊外部)上擁有更大的彈性。

在實務開發中,遵循指令的生命週期、妥善清理資源、合理使用修飾符與 key,是避免效能問題與記憶體洩漏的關鍵。結合上述概念與範例,你可以在 Vue3 專案裡以 簡潔、可讀、可維護 的方式編寫 UI,從而提升開發效率與使用者體驗。

祝你在 Vue3 的世界裡玩得開心,寫出更優雅的前端程式碼!