Vue3指令

Vue 3 指令完全指南(新手向)

本文档详细介绍 Vue 3 中所有常用指令的用法,配有大量示例,适合零基础学习。


一、什么是指令?

指令(Directive) 是写在 HTML 标签上的、以 v- 开头的特殊属性。Vue 会根据这些指令对 DOM 做“响应式”的更新或绑定事件。

指令长什么样?

<div v-if="isVisible">我会根据 isVisible 显示或隐藏</div>
  • v-if 是指令名
  • "isVisible" 是指令的表达式,通常是组件里定义的变量或简单运算

指令的组成(进阶)

一个完整指令可能包含:

  1. 名称:如 v-ifv-for
  2. 参数:在名称后加冒号,如 v-bind:href 里的 href
  3. 修饰符:以点开头的后缀,如 v-model.lazy 里的 .lazy
  4. :等号右边的表达式,如 v-if="count > 0" 里的 count > 0

二、内容渲染类指令

1. v-text —— 纯文本渲染

把变量的纯文本内容写入元素,相当于设置 element.textContent

语法: v-text="表达式"

<template>
  <div>
    <p v-text="message"></p>
    <!-- 等价于 -->
    <p>{{ message }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const message = ref('你好,Vue 3!')
</script>

注意: v-text 会覆盖该元素内部的全部内容,一般更推荐用双花括号 {{ }}


2. v-html —— 原始 HTML 渲染

把变量的值当作 HTML 代码 插入到元素中。
⚠️ 仅在你完全信任内容来源时使用,否则容易导致 XSS 攻击。

语法: v-html="表达式"

<template>
  <div>
    <div v-html="rawHtml"></div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const rawHtml = ref('<strong>加粗文字</strong> 和 <em>斜体</em>')
</script>

渲染结果: 页面上会显示加粗和斜体,而不是标签字符串。


3. v-show —— 显示/隐藏(切换 CSS display)

根据表达式真假切换元素的 display(true 显示,false 隐藏)。
元素始终留在 DOM 里,只是被隐藏。

语法: v-show="表达式"

<template>
  <div>
    <button @click="isShow = !isShow">切换显示</button>
    <p v-show="isShow">你看得见我!</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const isShow = ref(true)
</script>

适用场景: 频繁切换显示/隐藏时,用 v-show 性能更好(不用反复创建/销毁 DOM)。


4. v-if / v-else-if / v-else —— 条件渲染(是否渲染到 DOM)

根据条件决定是否把这块 DOM 渲染出来。为 false 时,对应的节点不会出现在 DOM 中。

语法:

  • v-if="表达式"
  • v-else-if="表达式"(可选,必须紧跟在 v-ifv-else-if 后面)
  • v-else(可选,不需要值,必须紧跟在 v-ifv-else-if 后面)
<template>
  <div>
    <p v-if="score >= 90">优秀!</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const score = ref(85)
</script>

v-if 和 v-show 怎么选?

对比 v-if v-show
机制 不满足条件时不渲染 DOM 始终渲染,用 CSS 隐藏
切换成本 有销毁/创建成本 只改 display,成本低
适用 不常切换的条件块 频繁显示/隐藏

三、列表渲染:v-for

根据数据源循环渲染一组元素。

语法:
v-for="(项, 索引?) in 数组"
v-for="(值, 键?) in 对象"

遍历数组

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

<script setup>
import { ref } from 'vue'
const list = ref([
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' }
])
</script>

重要: 使用 v-for 时务必加 :key,且 key 要唯一(通常用 id)。这样 Vue 才能正确做 diff 和复用,避免渲染错乱。

遍历对象

<template>
  <ul>
    <li v-for="(value, key) in user" :key="key">
      {{ key }}: {{ value }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'
const user = ref({
  name: '小明',
  age: 18,
  city: '北京'
})
</script>

遍历数字范围

<template>
  <span v-for="n in 5" :key="n">{{ n }}</span>
  <!-- 渲染 1 2 3 4 5 -->
</template>

与 v-if 一起用

不推荐在同一个元素上写 v-forv-if。需要“按条件过滤列表”时,用计算属性先过滤,再对结果做 v-for

<template>
  <ul>
    <li v-for="item in activeList" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

<script setup>
import { computed, ref } from 'vue'
const list = ref([
  { id: 1, name: '任务A', done: false },
  { id: 2, name: '任务B', done: true }
])
const activeList = computed(() => list.value.filter(item => !item.done))
</script>

四、属性绑定:v-bind(可简写为 :

数据绑定到元素的属性上,属性值会随数据变化而更新。

语法: v-bind:属性名="表达式" 或简写 :属性名="表达式"

绑定普通属性

<template>
  <div>
    <img :src="imgUrl" :alt="imgAlt" />
    <a :href="link">点击跳转</a>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const imgUrl = ref('/logo.png')
const imgAlt = ref('网站 Logo')
const link = ref('https://vuejs.org')
</script>

绑定 class(对象/数组写法)

<template>
  <div>
    <!-- 对象:键为类名,值为是否添加 -->
    <p :class="{ active: isActive, 'text-danger': hasError }">段落</p>

    <!-- 数组 -->
    <p :class="[classA, classB]">段落</p>

    <!-- 数组 + 对象混合 -->
    <p :class="[classA, { active: isActive }]">段落</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
const classA = ref('title')
const classB = ref('bold')
</script>

绑定 style(对象/数组)

<template>
  <div>
    <p :style="{ color: textColor, fontSize: fontSize + 'px' }">彩色文字</p>
    <p :style="styleObj">另一段</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const textColor = ref('red')
const fontSize = ref(16)
const styleObj = ref({
  backgroundColor: 'lightblue',
  padding: '10px'
})
</script>

五、事件绑定:v-on(可简写为 @

给元素绑定事件,如点击、输入、键盘等。

语法: v-on:事件名="处理函数或内联语句"@事件名="..."

基本用法

<template>
  <div>
    <button @click="count++">点击了 {{ count }} 次</button>
    <button @click="sayHello">打招呼</button>
    <button @click="greet('Vue')">传参</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
function sayHello() {
  alert('你好!')
}
function greet(name) {
  alert('你好,' + name + '!')
}
</script>

访问原生事件对象:$event

<template>
  <button @click="handleClick($event)">点击</button>
</template>

<script setup>
function handleClick(e) {
  console.log(e.target)  // 被点击的 DOM 元素
}
</script>

常用事件修饰符

修饰符 作用说明
.prevent 调用 event.preventDefault(),阻止默认行为(如表单提交、链接跳转)
.stop 调用 event.stopPropagation(),阻止冒泡
.once 事件最多触发一次
.capture 使用捕获阶段
.self 仅当事件来自当前元素自身时才触发
<template>
  <div>
    <!-- 阻止默认行为:比如提交表单时不刷新页面 -->
    <form @submit.prevent="onSubmit">
      <button type="submit">提交</button>
    </form>

    <!-- 阻止冒泡:点击按钮时不会触发外层 div 的点击 -->
    <div @click="onDivClick">
      <button @click.stop="onBtnClick">按钮</button>
    </div>

    <!-- 只触发一次 -->
    <button @click.once="doOnce">只执行一次</button>
  </div>
</template>

<script setup>
function onSubmit() {
  console.log('表单提交了')
}
function onDivClick() {
  console.log('div 被点了')
}
function onBtnClick() {
  console.log('按钮被点了')
}
function doOnce() {
  console.log('只会出现一次')
}
</script>

按键修饰符

<template>
  <input
    type="text"
    @keyup.enter="onEnter"
    @keyup.esc="onEsc"
  />
</template>

<script setup>
function onEnter() {
  console.log('按下了回车')
}
function onEsc() {
  console.log('按下了 Esc')
}
</script>

六、双向绑定:v-model

表单控件上实现双向绑定:数据变化会更新视图,用户输入也会更新数据。

语法: v-model="变量"
(本质是 :value + @input 的语法糖,不同控件内部事件可能不同)

文本框

<template>
  <div>
    <input v-model="username" type="text" placeholder="请输入用户名" />
    <p>你输入的是:{{ username }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const username = ref('')
</script>

多行文本

<template>
  <textarea v-model="intro" placeholder="个人简介"></textarea>
  <p>{{ intro }}</p>
</template>

<script setup>
import { ref } from 'vue'
const intro = ref('')
</script>

复选框(单个 / 多个)

<template>
  <div>
    <!-- 单个:绑定布尔值 -->
    <label>
      <input type="checkbox" v-model="agree" />
      我同意协议
    </label>
    <p>同意状态:{{ agree }}</p>

    <!-- 多个:绑定数组 -->
    <label><input type="checkbox" v-model="hobbies" value="读书" /> 读书</label>
    <label><input type="checkbox" v-model="hobbies" value="运动" /> 运动</label>
    <label><input type="checkbox" v-model="hobbies" value="音乐" /> 音乐</label>
    <p>选中的爱好:{{ hobbies }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const agree = ref(false)
const hobbies = ref([])
</script>

单选框

<template>
  <div>
    <label><input type="radio" v-model="gender" value="男" /> 男</label>
    <label><input type="radio" v-model="gender" value="女" /> 女</label>
    <p>性别:{{ gender }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const gender = ref('男')
</script>

下拉选择

<template>
  <div>
    <select v-model="selectedCity">
      <option value="">请选择</option>
      <option value="北京">北京</option>
      <option value="上海">上海</option>
      <option value="广州">广州</option>
    </select>
    <p>选中:{{ selectedCity }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const selectedCity = ref('')
</script>

v-model 修饰符

修饰符 作用
.lazy change 时再同步(如失焦、选择完成),而不是每次 input 都同步
.number 尽量把输入转成数字类型
.trim 自动去掉首尾空格
<template>
  <div>
    <!-- 失焦时才更新 -->
    <input v-model.lazy="message" />

    <!-- 转为数字 -->
    <input v-model.number="age" type="number" />

    <!-- 去除首尾空格 -->
    <input v-model.trim="keyword" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
const message = ref('')
const age = ref(0)
const keyword = ref('')
</script>

七、插槽:v-slot(可简写为 #

用于在子组件里预留“洞”,由父组件传入 HTML 或组件。
插槽名默认是 default,具名插槽可写 v-slot:名字#名字

语法:

  • v-slot:插槽名="作用域"
  • 简写:#插槽名="作用域"
  • 默认插槽:v-slot="作用域"#default="作用域"

默认插槽

子组件:

<!-- Child.vue -->
<template>
  <div class="card">
    <slot></slot>
  </div>
</template>

父组件:

<template>
  <Child>
    <p>这里的内容会出现在子组件的 slot 位置</p>
  </Child>
</template>

具名插槽

子组件:

<!-- Layout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

父组件:

<template>
  <Layout>
    <template #header>
      <h1>页面标题</h1>
    </template>

    <p>主体内容</p>

    <template #footer>
      <p>版权信息</p>
    </template>
  </Layout>
</template>

作用域插槽(子传数据给父)

子组件通过 <slot :变量名="值"> 把数据传出去,父组件用 v-slot="scope" 接收。

子组件:

<!-- UserList.vue -->
<template>
  <ul>
    <li v-for="user in users" :key="user.id">
      <slot :user="user">{{ user.name }}</slot>
    </li>
  </ul>
</template>

<script setup>
defineProps(['users'])
</script>

父组件:

<template>
  <UserList :users="users">
    <template #default="{ user }">
      <strong>{{ user.name }}</strong> - {{ user.age }} 岁
    </template>
  </UserList>
</template>

<script setup>
import { ref } from 'vue'
const users = ref([
  { id: 1, name: '小明', age: 18 },
  { id: 2, name: '小红', age: 20 }
])
</script>

八、其他内置指令

1. v-pre —— 不编译

该元素及其子元素不会被 Vue 编译,直接输出原始 Mustache 和指令。

<template>
  <div v-pre>
    {{ 这里的双花括号会原样显示 }}
    <span v-if="false">这里的 v-if 也不会生效</span>
  </div>
</template>

用途: 展示代码示例、避免误编译。


2. v-once —— 只渲染一次

元素只渲染一次,之后数据再变也不会更新。

<template>
  <p v-once>初始值:{{ msg }}</p>
  <!-- 后续修改 msg,这段文字不会变 -->
</template>

<script setup>
import { ref } from 'vue'
const msg = ref('只显示这一次')
</script>

用途: 静态、不需要响应的内容,可略省性能。


3. v-memo(Vue 3.2+)—— 条件性跳过更新

只有依赖的数组里的值变化时,才重新渲染该元素及其子节点。

语法: v-memo="[依赖1, 依赖2, ...]"

<template>
  <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
    <p>{{ item.name }}</p>
  </div>
</template>

用途: 大列表优化,减少不必要的子树更新。


4. v-cloak —— 避免未编译模板闪现

在 Vue 接管前,模板会短暂以原始 {{ }} 形式显示。配合 CSS 可隐藏未编译内容,等编译完成后再显示。

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

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

九、自定义指令(了解即可)

除了内置指令,还可以用 app.directive()const vXxx = { ... } 注册自定义指令,用来直接操作 DOM。

注册全局自定义指令

// main.js
const app = createApp(App)
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

在组件内注册局部指令

<script setup>
const vFocus = {
  mounted(el) {
    el.focus()
  }
}
</script>

<template>
  <input v-focus />
</template>

钩子函数(常用)

  • created:元素创建时
  • beforeMount:挂载前
  • mounted:挂载后(常用,可操作 DOM)
  • beforeUpdate / updated:更新前后
  • beforeUnmount / unmounted:卸载前后

示例:自定义 v-color

<template>
  <p v-color="'red'">红色文字</p>
  <p v-color="color">动态颜色</p>
</template>

<script setup>
import { ref } from 'vue'
const color = ref('blue')

const vColor = {
  mounted(el, binding) {
    el.style.color = binding.value
  },
  updated(el, binding) {
    el.style.color = binding.value
  }
}
</script>

十、指令速查表

指令 作用简述
v-text 设置元素文本内容
v-html 插入原始 HTML(慎用)
v-show 根据条件切换 display 显示/隐藏
v-if / v-else-if / v-else 条件渲染,不满足则不渲染 DOM
v-for 列表渲染,需配合 :key
v-on / @ 绑定事件
v-bind / : 绑定属性、class、style
v-model 表单双向绑定
v-slot / # 插槽:父传内容给子、或接收子组件作用域
v-pre 不编译,原样输出
v-once 只渲染一次
v-memo 依赖不变时跳过更新
v-cloak 配合 CSS 避免未编译模板闪现

十一、学习建议

  1. 先练熟:v-ifv-forv-bindv-onv-model,这些用得最多。
  2. 列表一定加 :key,且 key 要稳定、唯一。
  3. v-ifv-show 分清:少切换用 v-if,频繁切换用 v-show
  4. 表单用 v-model,记得用 .lazy.number.trim 等修饰符。
  5. 组件化时再深入 v-slot 和自定义指令。

把本文档里的示例在本地跑一遍,改一改数据与条件,会掌握得更快。祝你学习顺利!

发表评论