Vue 3 模板语法详解:从插值到指令的完整指南

1. 引言

Vue 3 作为当前最流行的前端框架之一,其模板语法是构建用户界面的核心基础。模板语法允许开发者以声明式的方式将 DOM 绑定到底层组件实例的数据上,让页面渲染变得直观而高效。本文将系统性地介绍 Vue 3 的模板语法,从基础的文本插值到复杂的指令用法,帮助你快速掌握并灵活运用。

2. 文本插值

文本插值是 Vue 模板中最基础的语法,使用双大括号(Mustache 语法)将数据渲染为文本。

javascript 复制代码
<template>
  <div>
    <p>用户名:{{ username }}</p>
    <p>年龄:{{ age }}</p>
    <p>计算后的值:{{ price * quantity }}</p>
    <p>方法调用:{{ formatDate(createTime) }}</p>
  </div>
</template>

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

const username = ref('张三')
const age = ref(25)
const price = ref(99.9)
const quantity = ref(2)
const createTime = new Date()

function formatDate(date) {
  return date.toLocaleDateString()
}
</script>

插值语法要点:

  • 双大括号内的内容会被解析为 JavaScript 表达式
  • 支持简单的运算、三元表达式、方法调用等
  • 数据变化时,视图会自动更新(响应式)
  • 插值只能用于文本节点,不能用于 HTML 属性

3. 原始 HTML 渲染

默认情况下,双大括号会将数据解释为纯文本,而非 HTML。如果需要渲染真正的 HTML,需要使用 v-html 指令。

javascript 复制代码
<template>
  <div>
    <!-- 普通插值:显示为纯文本 -->
    <p>{{ rawHtml }}</p>
    
    <!-- v-html:渲染为真正的 HTML -->
    <p v-html="rawHtml"></p>
  </div>
</template>

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

const rawHtml = ref('<strong style="color: red;">这是加粗的红色文字</strong>')
</script>

⚠️ 安全警告 :在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。请仅在可信内容上使用 v-html,永远不要对用户提供的内容使用。

4. 属性绑定(v-bind)

4.1 基础用法

v-bind 指令用于动态绑定 HTML 属性,简写为冒号 :

javascript 复制代码
<template>
  <div>
    <!-- 完整写法 -->
    <img v-bind:src="imageUrl" v-bind:alt="imageAlt">
    
    <!-- 简写形式 -->
    <img :src="imageUrl" :alt="imageAlt">
    
    <!-- 绑定 class -->
    <div :class="{ active: isActive, 'text-danger': hasError }">
      动态 class 绑定
    </div>
    
    <!-- 绑定 style -->
    <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
      动态 style 绑定
    </div>
  </div>
</template>

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

const imageUrl = ref('https://example.com/logo.png')
const imageAlt = ref('网站 Logo')
const isActive = ref(true)
const hasError = ref(false)
const textColor = ref('blue')
const fontSize = ref(16)
</script>

4.2 绑定多个属性

Vue 3 支持使用无参数的对象语法一次绑定多个属性:

javascript 复制代码
<template>
  <div v-bind="objectOfAttrs">
    这个 div 会继承 objectOfAttrs 中的所有属性
  </div>
</template>

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

const objectOfAttrs = reactive({
  id: 'container',
  class: 'wrapper',
  'data-test': 'test-id',
  style: 'background-color: #f0f0f0;'
})
</script>

5. 事件绑定(v-on)

5.1 基础用法

v-on 指令用于监听 DOM 事件,简写为 @

javascript 复制代码
<template>
  <div>
    <!-- 完整写法 -->
    <button v-on:click="handleClick">点击我</button>
    
    <!-- 简写形式 -->
    <button @click="handleClick">点击我</button>
    
    <!-- 内联语句 -->
    <button @click="count++">计数:{{ count }}</button>
    
    <!-- 传递事件对象 -->
    <button @click="handleEvent($event)">获取事件对象</button>
    
    <!-- 传递额外参数 -->
    <button @click="handleParams('参数1', 42)">传递参数</button>
  </div>
</template>

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

const count = ref(0)

function handleClick() {
  console.log('按钮被点击了')
}

function handleEvent(event) {
  console.log('事件对象:', event)
  console.log('触发元素:', event.target)
}

function handleParams(param1, param2) {
  console.log(param1, param2)
}
</script>

5.2 事件修饰符

Vue 3 提供了丰富的事件修饰符,简化常见操作:

javascript 复制代码
<template>
  <div>
    <!-- 阻止默认行为 -->
    <a href="https://example.com" @click.prevent="handleClick">阻止跳转</a>
    
    <!-- 阻止事件冒泡 -->
    <div @click="outerClick">
      <button @click.stop="innerClick">阻止冒泡</button>
    </div>
    
    <!-- 只触发一次 -->
    <button @click.once="handleOnce">只触发一次</button>
    
    <!-- 按键修饰符 -->
    <input @keyup.enter="handleEnter" placeholder="按回车触发">
    <input @keyup.esc="handleEsc" placeholder="按 ESC 触发">
    
    <!-- 组合按键 -->
    <input @keyup.ctrl.enter="handleCtrlEnter" placeholder="Ctrl + Enter">
    
    <!-- 鼠标修饰符 -->
    <button @click.right="handleRightClick">右键点击</button>
    <button @click.middle="handleMiddleClick">中键点击</button>
  </div>
</template>

<script setup>
function handleClick() {
  console.log('点击已阻止默认行为')
}

function outerClick() {
  console.log('外层 div 被点击')
}

function innerClick() {
  console.log('内层按钮被点击')
}

function handleOnce() {
  console.log('这个只会执行一次')
}

function handleEnter() {
  console.log('按下了回车键')
}

function handleEsc() {
  console.log('按下了 ESC 键')
}

function handleCtrlEnter() {
  console.log('按下了 Ctrl + Enter')
}

function handleRightClick() {
  console.log('右键点击')
}

function handleMiddleClick() {
  console.log('中键点击')
}
</script>

6. 条件渲染(v-if / v-show)

6.1 v-if 系列

v-if 指令根据条件决定是否渲染元素,支持 v-else-ifv-else 链式使用:

javascript 复制代码
<template>
  <div>
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>
    
    <!-- 使用 template 包裹多个元素 -->
    <template v-if="isLoggedIn">
      <h2>欢迎回来,{{ username }}</h2>
      <p>您有 {{ unreadCount }} 条未读消息</p>
    </template>
    <template v-else>
      <h2>请先登录</h2>
      <button @click="login">登录</button>
    </template>
  </div>
</template>

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

const score = ref(85)
const isLoggedIn = ref(false)
const username = ref('张三')
const unreadCount = ref(3)

function login() {
  isLoggedIn.value = true
}
</script>

6.2 v-if vs v-show

javascript 复制代码
<template>
  <div>
    <!-- v-if:条件为 false 时不渲染元素 -->
    <p v-if="showIf">v-if 示例:条件为 false 时,这个元素不会出现在 DOM 中</p>
    
    <!-- v-show:始终渲染,仅切换 display 属性 -->
    <p v-show="showShow">v-show 示例:条件为 false 时,元素仍在 DOM 中,只是隐藏</p>
    
    <button @click="toggleIf">切换 v-if</button>
    <button @click="toggleShow">切换 v-show</button>
  </div>
</template>

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

const showIf = ref(true)
const showShow = ref(true)

function toggleIf() {
  showIf.value = !showIf.value
}

function toggleShow() {
  showShow.value = !showShow.value
}
</script>

选择建议:

  • v-if:条件很少改变时使用,切换开销较大,但初始渲染开销小
  • v-show:需要频繁切换时使用,切换开销小,但初始渲染开销大

7. 列表渲染(v-for)

7.1 基础用法

v-for 指令用于遍历数组或对象:

javascript 复制代码
<template>
  <div>
    <!-- 遍历数组 -->
    <ul>
      <li v-for="(item, index) in items" :key="item.id">
        {{ index }} - {{ item.name }}
      </li>
    </ul>
    
    <!-- 遍历对象 -->
    <ul>
      <li v-for="(value, key, index) in user" :key="key">
        {{ index }}. {{ key }}: {{ value }}
      </li>
    </ul>
    
    <!-- 遍历数字范围 -->
    <span v-for="n in 5" :key="n">{{ n }} </span>
  </div>
</template>

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

const items = ref([
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' }
])

const user = ref({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
})
</script>

7.2 key 的重要性

使用 v-for 时,务必为每个元素提供唯一的 key 属性,这有助于 Vue 高效地追踪节点身份,优化渲染性能:

javascript 复制代码
<template>
  <div>
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
    
    <!-- 使用 index 作为 key(不推荐,仅当列表不会重排时使用) -->
    <ul>
      <li v-for="(item, index) in items" :key="index">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

💡 提示:优先使用数据中稳定的唯一标识(如 id)作为 key,避免使用 index,因为当列表顺序变化时,index 会导致渲染错误。

7.3 与 v-if 的配合

在 Vue 3 中,v-if 的优先级高于 v-for,这意味着 v-if 无法访问 v-for 作用域内的变量:

javascript 复制代码
<template>
  <!-- ❌ 错误:v-if 无法访问 item -->
  <!-- <li v-for="item in items" v-if="item.isVisible">{{ item.name }}</li> -->
  
  <!-- ✅ 正确:使用 template 包裹 -->
  <template v-for="item in items" :key="item.id">
    <li v-if="item.isVisible">{{ item.name }}</li>
  </template>
  
  <!-- ✅ 推荐:使用计算属性过滤 -->
  <li v-for="item in visibleItems" :key="item.id">{{ item.name }}</li>
</template>

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

const items = ref([
  { id: 1, name: '苹果', isVisible: true },
  { id: 2, name: '香蕉', isVisible: false },
  { id: 3, name: '橙子', isVisible: true }
])

const visibleItems = computed(() => items.value.filter(item => item.isVisible))
</script>

8. 双向绑定(v-model)

v-model 指令在表单元素上创建双向数据绑定,是 Vue 中最常用的指令之一。

8.1 基础用法

javascript 复制代码
<template>
  <div>
    <!-- 文本输入 -->
    <input v-model="message" placeholder="请输入内容">
    <p>输入的内容:{{ message }}</p>
    
    <!-- 多行文本 -->
    <textarea v-model="description" placeholder="请输入描述"></textarea>
    
    <!-- 复选框 -->
    <input type="checkbox" v-model="checked">
    <p>是否选中:{{ checked }}</p>
    
    <!-- 单选按钮 -->
    <input type="radio" value="男" v-model="gender"> 男
    <input type="radio" value="女" v-model="gender"> 女
    <p>性别:{{ gender }}</p>
    
    <!-- 下拉选择 -->
    <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 message = ref('')
const description = ref('')
const checked = ref(false)
const gender = ref('')
const selectedCity = ref('')
</script>

8.2 修饰符

javascript 复制代码
<template>
  <div>
    <!-- .trim:自动去除首尾空格 -->
    <input v-model.trim="username" placeholder="用户名(自动去空格)">
    
    <!-- .number:自动转为数字 -->
    <input v-model.number="age" type="number" placeholder="年龄(自动转数字)">
    
    <!-- .lazy:改为 change 事件触发,而非 input 事件 -->
    <input v-model.lazy="message" placeholder="失焦时才更新">
    
    <p>用户名:{{ username }}</p>
    <p>年龄:{{ age }}(类型:{{ typeof age }})</p>
    <p>消息:{{ message }}</p>
  </div>
</template>

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

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

9. 计算属性与侦听器

9.1 计算属性(computed)

计算属性用于处理复杂的逻辑,基于响应式依赖进行缓存:

javascript 复制代码
<template>
  <div>
    <input v-model.number="price" placeholder="单价">
    <input v-model.number="quantity" placeholder="数量">
    
    <p>总价:{{ totalPrice }} 元</p>
    <p>折扣价:{{ discountedPrice }} 元</p>
    <p>是否免运费:{{ isFreeShipping ? '是' : '否' }}</p>
  </div>
</template>

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

const price = ref(0)
const quantity = ref(0)

const totalPrice = computed(() => price.value * quantity.value)

const discountedPrice = computed(() => {
  if (totalPrice.value >= 100) {
    return totalPrice.value * 0.9  // 满 100 打 9 折
  }
  return totalPrice.value
})

const isFreeShipping = computed(() => totalPrice.value >= 50)
</script>

9.2 侦听器(watch)

侦听器用于侦听数据变化并执行副作用操作:

javascript 复制代码
<template>
  <div>
    <input v-model="searchQuery" placeholder="搜索关键词">
    <p>搜索结果:{{ searchResult }}</p>
  </div>
</template>

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

const searchQuery = ref('')
const searchResult = ref('')

// 基础用法
watch(searchQuery, (newValue, oldValue) => {
  console.log(`搜索词从 "${oldValue}" 变为 "${newValue}"`)
  searchResult.value = `正在搜索:${newValue}`
})

// 立即执行 + 深度侦听
watch(
  searchQuery,
  (newValue) => {
    console.log('立即执行一次,然后每次变化时执行')
  },
  { immediate: true, deep: true }
)
</script>

10. 模板引用(ref)

ref 属性用于获取 DOM 元素或子组件的引用:

javascript 复制代码
<template>
  <div>
    <input ref="inputRef" placeholder="输入内容">
    <button @click="focusInput">聚焦输入框</button>
    <button @click="getInputValue">获取输入值</button>
  </div>
</template>

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

const inputRef = ref(null)

onMounted(() => {
  // 组件挂载后可以访问 DOM 元素
  console.log('输入框元素:', inputRef.value)
})

function focusInput() {
  inputRef.value.focus()
}

function getInputValue() {
  console.log('输入框的值:', inputRef.value.value)
}
</script>

11. 动态组件与插槽

11.1 动态组件

使用 <component> 元素配合 :is 属性实现动态组件切换:

javascript 复制代码
<template>
  <div>
    <button @click="currentComponent = 'ComponentA'">显示组件 A</button>
    <button @click="currentComponent = 'ComponentB'">显示组件 B</button>
    
    <compo
相关推荐
invicinble1 小时前
【无标题】
前端
EasyGBS1 小时前
从接入到稳定播放:无插件直播H5视频流媒体播放器EasyPlayer.js如何撑起Web端流媒体
开发语言·前端·javascript
不可能片场1 小时前
nohup 之后进程还是死了:GUI 子进程的存活陷阱
前端·electron
Carl_奕然1 小时前
【智能体】Agent的四种设计模式之:React(2026最新版)
javascript·人工智能·python·react.js·设计模式·语言模型
名为沙丁鱼的猫7291 小时前
React/JSX 编码规范
前端·javascript·react.js
是吕先森1 小时前
【python】selenium实现web自动化测试
前端·python·selenium
Highcharts.js1 小时前
甘特图纵向任务、横向时间显示图表可视化开发|Highcharts甘特图示例
开发语言·前端·javascript·可视化·甘特图·数据可视化·highcharts
yume_sibai2 小时前
Element Plus 导航与反馈组件完全指南(15个核心组件)
前端·elementui·交互
烬羽2 小时前
给 Agent 一个检索式记忆:把对话历史存进 Milvus,该记的都记得
javascript·数据库·agent