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-if 和 v-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