Vue 指令(Directives)

指令(Directives)是 Vue 模板中帶有 v- 前綴的特殊屬性。指令的作用是當表達式的值改變時,響應式地將效果套用到 DOM 上。

v-bind - 屬性綁定

v-bind 用於動態綁定一個或多個 HTML 屬性到表達式。

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

const url = ref('https://vuejs.org')
const isDisabled = ref(true)
</script>

<template>
  <!-- 完整語法 -->
  <a v-bind:href="url">Vue 官網</a>

  <!-- 簡寫語法(推薦) -->
  <a :href="url">Vue 官網</a>

  <!-- 布林屬性 -->
  <button :disabled="isDisabled">按鈕</button>
</template>

綁定多個屬性

<script setup>
import { reactive } from 'vue'

const attrs = reactive({
  id: 'my-input',
  type: 'text',
  placeholder: '請輸入內容'
})
</script>

<template>
  <input v-bind="attrs">
</template>

v-on - 事件監聽

v-on 用於監聽 DOM 事件。

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

const count = ref(0)

function increment() {
  count.value++
}

function greet(name) {
  alert(`Hello, ${name}!`)
}
</script>

<template>
  <!-- 完整語法 -->
  <button v-on:click="increment">點擊次數:{{ count }}</button>

  <!-- 簡寫語法(推薦) -->
  <button @click="increment">點擊次數:{{ count }}</button>

  <!-- 內聯表達式 -->
  <button @click="count++">+1</button>

  <!-- 帶參數的方法 -->
  <button @click="greet('Vue')">打招呼</button>

  <!-- 存取原生事件物件 -->
  <button @click="(event) => console.log(event.target)">Log Event</button>

  <!-- 使用特殊變數 $event -->
  <button @click="greet($event.target.textContent)">點我</button>
</template>

事件修飾符

Vue 提供了多種事件修飾符來處理常見的 DOM 事件細節:

<template>
  <!-- 阻止預設行為 -->
  <form @submit.prevent="onSubmit">
    <button type="submit">送出</button>
  </form>

  <!-- 阻止事件冒泡 -->
  <div @click="onClick">
    <button @click.stop="onButtonClick">按鈕</button>
  </div>

  <!-- 事件只觸發一次 -->
  <button @click.once="doOnce">只能點一次</button>

  <!-- 事件捕獲模式 -->
  <div @click.capture="onCapture">...</div>

  <!-- 只有事件是從元素本身觸發時才執行 -->
  <div @click.self="onSelf">...</div>

  <!-- 修飾符可以串接 -->
  <a @click.stop.prevent="onClick">連結</a>
</template>
修飾符說明
.stop呼叫 event.stopPropagation()
.prevent呼叫 event.preventDefault()
.capture使用事件捕獲模式
.self只有事件從元素本身觸發時才執行
.once事件只會觸發一次
.passive不會呼叫 preventDefault(),提升滾動效能

按鍵修飾符

監聽鍵盤事件時可以使用按鍵修飾符:

<template>
  <!-- 只在按下 Enter 時觸發 -->
  <input @keyup.enter="submit">

  <!-- 按下 Esc 時觸發 -->
  <input @keyup.esc="cancel">

  <!-- 常用按鍵修飾符 -->
  <input @keyup.tab="onTab">
  <input @keyup.delete="onDelete">
  <input @keyup.space="onSpace">
  <input @keyup.up="onArrowUp">
  <input @keyup.down="onArrowDown">
  <input @keyup.left="onArrowLeft">
  <input @keyup.right="onArrowRight">
</template>

系統修飾鍵

<template>
  <!-- Ctrl + Click -->
  <div @click.ctrl="onCtrlClick">Ctrl + Click</div>

  <!-- Alt + Enter -->
  <input @keyup.alt.enter="onAltEnter">

  <!-- Ctrl + A -->
  <input @keyup.ctrl.a="selectAll">

  <!-- 系統修飾鍵:ctrl, alt, shift, meta (Mac 的 Command 鍵) -->
</template>

滑鼠按鍵修飾符

<template>
  <button @click.left="onLeftClick">左鍵點擊</button>
  <button @click.right="onRightClick">右鍵點擊</button>
  <button @click.middle="onMiddleClick">中鍵點擊</button>
</template>

v-model - 雙向資料綁定

v-model 在表單元素上建立雙向資料綁定:

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

const text = ref('')
const checked = ref(false)
const selected = ref('')
</script>

<template>
  <!-- 文字輸入 -->
  <input v-model="text" type="text">
  <p>輸入的文字:{{ text }}</p>

  <!-- 核取方塊 -->
  <input v-model="checked" type="checkbox">
  <p>勾選狀態:{{ checked }}</p>

  <!-- 選單 -->
  <select v-model="selected">
    <option value="">請選擇</option>
    <option value="a">選項 A</option>
    <option value="b">選項 B</option>
  </select>
  <p>選擇的值:{{ selected }}</p>
</template>

v-model 修飾符

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

const msg = ref('')
const age = ref(0)
</script>

<template>
  <!-- .lazy:在 change 事件後才更新(而非 input 事件) -->
  <input v-model.lazy="msg">

  <!-- .number:自動將輸入轉為數字 -->
  <input v-model.number="age" type="number">

  <!-- .trim:自動去除首尾空白字元 -->
  <input v-model.trim="msg">
</template>

更多表單相關用法請參考 表單處理

v-if / v-else-if / v-else - 條件渲染

根據條件決定是否渲染元素:

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

const score = ref(85)
const isLoggedIn = ref(false)
</script>

<template>
  <!-- 基本用法 -->
  <p v-if="isLoggedIn">歡迎回來!</p>
  <p v-else>請先登入</p>

  <!-- 多重條件 -->
  <div v-if="score >= 90">優秀</div>
  <div v-else-if="score >= 60">及格</div>
  <div v-else>不及格</div>
</template>

在 template 上使用 v-if

如果要同時切換多個元素,可以在 <template> 上使用 v-if

<template>
  <template v-if="isLoggedIn">
    <h1>歡迎回來</h1>
    <p>您的帳號:{{ username }}</p>
    <button @click="logout">登出</button>
  </template>
  <template v-else>
    <h1>請登入</h1>
    <button @click="login">登入</button>
  </template>
</template>

<template> 只是一個不可見的包裝元素,不會被渲染到 DOM 中。

v-show - 條件顯示

v-show 也是根據條件顯示元素,但它是透過 CSS 的 display 屬性來切換:

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

const isVisible = ref(true)
</script>

<template>
  <p v-show="isVisible">這段文字可以顯示/隱藏</p>
  <button @click="isVisible = !isVisible">切換</button>
</template>

v-if vs v-show

特性v-ifv-show
切換方式新增/移除 DOM 元素切換 CSS display 屬性
初始渲染成本條件為假時不渲染無論條件都會渲染
切換成本較高(重新建立元素)較低(只改 CSS)
適用場景條件很少改變需要頻繁切換
v-show 不支援在 <template> 上使用,也不能和 v-else 搭配。

v-for - 列表渲染

v-for 用於渲染列表:

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

const items = ref([
  { id: 1, text: '學習 Vue' },
  { id: 2, text: '建立專案' },
  { id: 3, text: '部署應用' }
])

const user = ref({
  name: 'John',
  age: 30,
  city: 'Taipei'
})
</script>

<template>
  <!-- 遍歷陣列 -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </ul>

  <!-- 取得索引 -->
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index + 1 }}. {{ item.text }}
    </li>
  </ul>

  <!-- 遍歷物件 -->
  <ul>
    <li v-for="(value, key) in user" :key="key">
      {{ key }}: {{ value }}
    </li>
  </ul>

  <!-- 遍歷數字範圍 -->
  <span v-for="n in 5" :key="n">{{ n }} </span>
  <!-- 輸出:1 2 3 4 5 -->
</template>
使用 v-for 時一定要提供 :key 屬性,且 key 值必須是唯一的。這有助於 Vue 追蹤每個節點的身份,從而重用和重新排序現有元素。

v-for 與 v-if

不建議在同一元素上同時使用 v-forv-if。如果需要過濾列表,建議使用 computed 屬性:

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

const todos = ref([
  { id: 1, text: '學習 Vue', done: true },
  { id: 2, text: '建立專案', done: false },
  { id: 3, text: '部署應用', done: false }
])

// 使用 computed 過濾
const incompleteTodos = computed(() => {
  return todos.value.filter(todo => !todo.done)
})
</script>

<template>
  <ul>
    <li v-for="todo in incompleteTodos" :key="todo.id">
      {{ todo.text }}
    </li>
  </ul>
</template>

更多列表渲染的用法請參考 列表渲染

v-slot - 插槽

v-slot 用於定義和使用插槽內容,簡寫為 #

<!-- 子元件 BaseLayout.vue -->
<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<!-- 父元件使用 -->
<template>
  <BaseLayout>
    <template v-slot:header>
      <h1>頁面標題</h1>
    </template>

    <!-- 預設插槽 -->
    <p>主要內容</p>

    <template #footer>  <!-- 簡寫語法 -->
      <p>頁尾資訊</p>
    </template>
  </BaseLayout>
</template>

更多插槽的用法請參考 Slots 插槽

v-pre

v-pre 會跳過這個元素及其子元素的編譯過程,直接顯示原始的 Mustache 標籤:

<template>
  <!-- 會顯示 {{ message }} 而不是值 -->
  <span v-pre>{{ message }}</span>
</template>

這在需要顯示原始 Mustache 語法的說明文件時很有用。

v-once

v-once 只會渲染元素一次,之後的重新渲染會跳過這個元素及其子元素:

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

const message = ref('Hello')
</script>

<template>
  <!-- 即使 message 改變,也不會更新 -->
  <p v-once>{{ message }}</p>

  <!-- 這個會隨著 message 更新 -->
  <p>{{ message }}</p>

  <button @click="message = 'Changed'">修改</button>
</template>

這可以用於優化效能,避免不需要更新的內容重新渲染。

v-memo(Vue 3.2+)

v-memo 用於記憶模板的子樹,只有當依賴的值改變時才會重新渲染:

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

const items = ref([/* 大量資料 */])
const selected = ref(null)
</script>

<template>
  <div v-for="item in items" :key="item.id" v-memo="[item.id === selected]">
    <p>{{ item.name }}</p>
    <p :class="{ active: item.id === selected }">
      {{ item.id === selected ? '已選中' : '' }}
    </p>
  </div>
</template>

v-memo 接收一個陣列,只有當陣列中的任一值改變時,才會更新該元素。這對於大型列表的效能優化很有幫助。

v-cloak

v-cloak 用於隱藏尚未編譯完成的模板,防止使用者看到原始的 Mustache 標籤閃爍:

<style>
[v-cloak] {
  display: none;
}
</style>

<template>
  <div v-cloak>
    {{ message }}
  </div>
</template>

編譯完成後,v-cloak 屬性會被移除。這在使用 CDN 引入 Vue 時特別有用,因為模板可能會在 Vue 載入前就顯示。

自訂指令

除了內建指令,Vue 也允許你建立自訂指令。詳細內容請參考 自訂指令