Vue 3 模板语法完全指南
本文档从零开始讲解 Vue 3 的模板语法:模板在哪里、能写什么、不能写什么,以及文本插值、属性绑定、表达式、事件、class/style 等方方面面,配有大量示例,适合新手系统学习。
一、什么是 Vue 的模板?
1.1 模板在哪里?
在 Vue 单文件组件(.vue)里,<template> 标签内的 HTML 就是该组件的模板。
<template>
<div>
<p>这里是模板,可以写 HTML + Vue 语法</p>
</div>
</template>
Vue 会把这些内容编译成“渲染函数”,再根据数据生成真正的 DOM。
所以:模板 = HTML + Vue 专属语法。下面说的“模板语法”,就是指在 <template> 里能用的这些语法。
1.2 模板语法的两大类型
- 插值(Interpolation):在 HTML 里“插入”动态数据,最典型的是双花括号
{{ }}。 - 指令(Directive):以
v-开头的特殊属性,用来绑定属性、绑定事件、控制显示/循环等。
本文会先讲插值和表达式,再讲属性与事件绑定、class/style、条件与列表,帮你建立完整印象。
指令的详细用法可参考《Vue3指令.md》。
二、文本插值:双花括号 {{ }}
在模板里想显示 JavaScript 变量的值,用双花括号把表达式包起来即可。
2.1 基本用法
<template>
<div>
<p>消息:{{ message }}</p>
<p>数字:{{ count }}</p>
<p>布尔:{{ isOk }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('你好,Vue!')
const count = ref(100)
const isOk = ref(true)
</script>
渲染结果:
- “消息:你好,Vue!”
- “数字:100”
- “布尔:true”
注意: 在 <script setup> 里用 ref 定义的变量,在模板里不用写 .value,Vue 会自动解包。
2.2 表达式可以写什么?
花括号里必须是单个 JavaScript 表达式,不能写语句(如 if、for、const)。
✅ 正确示例:
<template>
<div>
<p>{{ message }}</p>
<p>{{ count + 1 }}</p>
<p>{{ isOk ? '是' : '否' }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<p>{{ user.name }}</p>
<p>{{ list.length }}</p>
<p>{{ fn() }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello')
const count = ref(10)
const isOk = ref(true)
const user = ref({ name: '小明' })
const list = ref([1, 2, 3])
function fn() {
return '函数返回值'
}
</script>
❌ 错误示例:
<!-- 不能写语句 -->
<p>{{ if (ok) { return 'yes' } }}</p>
<p>{{ const a = 1 }}</p>
<p>{{ for (let i of list) {} }}</p>
<!-- 不能写多个语句 -->
<p>{{ count++; count }}</p>
记住:一个表达式 = 一个会产生一个值的式子。
赋值、if、for、const/let 都是“语句”,不能直接写在 {{ }} 里。
2.3 插值会转成文本,不会解析 HTML
{{ }} 里的内容会以纯文本形式显示,不会当成 HTML 解析。
<template>
<p>{{ rawHtml }}</p>
</template>
<script setup>
import { ref } from 'vue'
const rawHtml = ref('<strong>加粗</strong>')
</script>
页面上会显示:
<strong>加粗</strong> 这一串字符,而不是加粗的“加粗”二字。
若需要把字符串当作 HTML 渲染,要使用指令 v-html(详见《Vue3指令.md》),并注意 XSS 安全。
三、在 HTML 属性里“插值”(属性绑定)
在普通 HTML 属性里不能直接写双花括号,写了也不会生效。
<!-- 错误:属性里这样写不会变成动态的 -->
<div id="{{ id }}"></div>
<img src="{{ imgSrc }}">
要让属性值随数据变化,必须用 v-bind(或简写 :)。
3.1 基本绑定:v-bind 或 :
<template>
<div>
<div :id="boxId">盒子</div>
<img :src="imgSrc" :alt="imgAlt" />
<a :href="url">链接</a>
</div>
</template>
<script setup>
import { ref } from 'vue'
const boxId = ref('main-box')
const imgSrc = ref('/logo.png')
const imgAlt = ref('Logo')
const url = ref('https://vuejs.org')
</script>
:id 等价于 v-bind:id。
属性名前的冒号表示“后面是 JavaScript 表达式”,所以可以写变量、运算、三元等。
3.2 布尔属性
像 disabled、readonly、checked 这类属性,绑定的值会按“真假”处理:
假值(false、0、”、null、undefined)时,该属性不会出现在 DOM 上。
<template>
<button :disabled="isDisabled">按钮</button>
<input type="checkbox" :checked="isChecked" />
</template>
<script setup>
import { ref } from 'vue'
const isDisabled = ref(false)
const isChecked = ref(true)
</script>
3.3 绑定多个属性:无参数 v-bind
若有一整个对象,键是属性名、值是属性值,可以用无参数的 v-bind 一次性绑定。
<template>
<div v-bind="attrObj">内容</div>
</template>
<script setup>
import { ref } from 'vue'
const attrObj = ref({
id: 'my-id',
class: 'box',
'data-name': 'test'
})
</script>
渲染结果相当于:
<div id="my-id" class="box" data-name="test">内容</div>
后面若对同一个属性又有单独绑定(如 :class="..."),会与 v-bind="attrObj" 合并,单独绑定的优先级更高。
四、模板里能用的 JavaScript 表达式(小结)
以下在插值 {{ }} 和 指令的值(如 :id="..."、v-if="...")里都适用。
-
可用:
- 变量名、对象属性访问(
user.name)、数组下标(list[0]) - 运算:
+ - * / %、count + 1 - 比较:
===、!==、>、<等 - 逻辑:
&&、||、! - 三元:
ok ? '是' : '否' - 函数调用:
fn()、message.toUpperCase() - 数组/对象字面量(在绑定 class/style 时常用)
- 变量名、对象属性访问(
-
不可用:
- 声明:
const、let、var、function - 流程控制:
if、for、while、switch - 赋值语句:
a = 1、count++(但count + 1这种表达式可以)
- 声明:
另外,模板中的表达式只能访问有限的白名单全局对象(如 Math、Date),不能随意访问 window、document 等。复杂逻辑应放在组件的 methods / 函数 或 计算属性 里,在模板里只做简单调用。
五、事件绑定:@事件名
在模板里给元素绑定事件,用 v-on:事件名 或简写 @事件名。
5.1 内联处理函数与传参
<template>
<div>
<button @click="count++">点击 +1</button>
<p>次数:{{ count }}</p>
<button @click="sayHi">无参</button>
<button @click="say('Vue')">传参</button>
<button @click="(e) => handle(e, 123)">事件对象 + 参数</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function sayHi() {
alert('Hi')
}
function say(name) {
alert('Hello, ' + name)
}
function handle(e, id) {
console.log(e.target, id)
}
</script>
@click="count++":内联表达式,直接执行。@click="sayHi":函数引用,无参时可这样写。@click="say('Vue')":带参数时要写调用,即加括号。- 需要事件对象时,在参数里写
$event或用箭头函数传e(如上例)。
5.2 事件修饰符(.prevent、.stop、.once 等)
常用写法:在事件名后加 .修饰符,如 @click.prevent、@submit.prevent。
<template>
<form @submit.prevent="onSubmit">
<button type="submit">提交(不刷新页面)</button>
</form>
<div @click="onDivClick">
<button @click.stop="onBtnClick">按钮(不冒泡)</button>
</div>
<button @click.once="doOnce">只触发一次</button>
</template>
<script setup>
function onSubmit() {
console.log('提交')
}
function onDivClick() {
console.log('div')
}
function onBtnClick() {
console.log('按钮')
}
function doOnce() {
console.log('只一次')
}
</script>
更多修饰符(如 .capture、.self、按键修饰符)见《Vue3指令.md》。
六、双向绑定:v-model(模板中的写法)
在表单控件上,用 v-model="变量" 即可实现“输入 ↔ 数据”双向同步。
这里只强调模板写法,原理和修饰符见《Vue3指令.md》。
<template>
<div>
<input v-model="text" placeholder="请输入" />
<p>你输入了:{{ text }}</p>
<input v-model.number="age" type="number" />
<input v-model.trim="keyword" />
<textarea v-model.lazy="desc"></textarea>
</div>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
const age = ref(0)
const keyword = ref('')
const desc = ref('')
</script>
七、class 与 style 的绑定(模板语法重点)
class 和 style 除了写死字符串,还可以用对象或数组动态绑定,都在模板里用 :class、:style 写表达式。
7.1 绑定 class:对象写法
对象的 键 是类名,值 为真时该类名会加上。
<template>
<div>
<p :class="{ active: isActive, 'text-danger': hasError }">段落</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
渲染结果:<p class="active">段落</p>。
若 hasError 为 true,会多一个 text-danger。
类名有横线时要加引号,如 'text-danger'。
7.2 绑定 class:数组写法
数组里放类名字符串或对象,最终会合并成一个 class 列表。
<template>
<div>
<p :class="[classA, classB]">段落</p>
<p :class="[classA, { active: isActive }]">段落</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const classA = ref('title')
const classB = ref('bold')
const isActive = ref(true)
</script>
7.3 绑定 style:对象写法
:style 的值是一个对象,键是 CSS 属性名(驼峰或短横线均可),值是字符串或数字(数字会默认加 px)。
<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>
7.4 绑定 style:数组写法(多个对象合并)
<template>
<p :style="[baseStyle, overrideStyle]">文字</p>
</template>
<script setup>
import { ref } from 'vue'
const baseStyle = ref({ color: 'blue', fontSize: '14px' })
const overrideStyle = ref({ fontSize: '20px' })
</script>
后面数组项会覆盖前面同名字段,所以最终 fontSize 为 20px。
八、条件渲染:v-if / v-else-if / v-else、v-show
在模板里用指令控制“是否渲染”或“显示/隐藏”。
8.1 v-if、v-else-if、v-else
<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-else-if、v-else 必须紧跟在带 v-if 或 v-else-if 的元素后面,中间不能插其他元素。
8.2 用 template 包一组元素
若要对多个元素一起做条件判断,可以用 <template> 包起来,<template> 不会渲染成真实节点。
<template>
<template v-if="isLoggedIn">
<h2>欢迎回来</h2>
<p>个人中心</p>
</template>
<template v-else>
<p>请先登录</p>
</template>
</template>
<script setup>
import { ref } from 'vue'
const isLoggedIn = ref(false)
</script>
8.3 v-show
v-show 通过 CSS display 控制显示/隐藏,元素始终在 DOM 里。
<template>
<p v-show="isVisible">看得见我</p>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
</script>
何时用 v-if、何时用 v-show 见《Vue3指令.md》。
九、列表渲染:v-for
在模板里用 v-for 根据数组或对象循环生成多份 DOM。
必须配合 :key,且 key 要唯一、稳定(通常用 id)。
9.1 遍历数组
<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: '香蕉' }
])
</script>
(item, index) 中第一个是当前项,第二个是索引;:key="item.id" 不要用 index(在列表会增删时容易出问题)。
9.2 遍历对象
<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
})
</script>
9.3 用 template 包一层
和 v-if 一样,可以用 <template> 包住多个元素一起循环。
<template>
<template v-for="item in list" :key="item.id">
<h3>{{ item.title }}</h3>
<p>{{ item.desc }}</p>
</template>
</template>
十、模板中的其他语法点
10.1 注释
HTML 注释会出现在最终 DOM 里;若不想输出到 DOM,可用 Vue 的注释写法(需在可被 Vue 编译的模板内):
<template>
<div>
<!-- 这是普通 HTML 注释,会出现在 DOM -->
<p>内容</p>
</div>
</template>
在 Vue 编译的模板里,一般用 <!-- 注释 --> 即可;避免在 {{ }} 或属性值中间写注释。
10.2 大小写
- HTML 属性、标签名不区分大小写,但推荐小写或短横线(如
my-prop)。 - 在模板里引用的变量名、方法名要和
<script setup>里定义的完全一致(区分大小写)。
10.3 避免 v-if 与 v-for 写在同一元素上
当同一节点上同时有 v-if 和 v-for 时,Vue 3 中 v-if 会先被判断,可能导致逻辑不符合预期。
推荐做法:用 <template> 包一层,或用计算属性先过滤再 v-for。
<!-- 不推荐 -->
<li v-for="item in list" v-if="item.show" :key="item.id">...</li>
<!-- 推荐:用 template 包一层 -->
<template v-for="item in list" :key="item.id">
<li v-if="item.show">...</li>
</template>
十一、模板语法速查表
| 需求 | 写法示例 |
|---|---|
| 显示变量/表达式 | {{ message }}、{{ count + 1 }} |
| 属性动态绑定 | :id="id"、v-bind:src="url" |
| 事件绑定 | @click="fn"、@click="fn(1)" |
| 双向绑定 | v-model="text"、v-model.number="n" |
| 条件渲染 | v-if / v-else-if / v-else、v-show |
| 列表渲染 | v-for="(item, i) in list" :key="item.id" |
| class 绑定 | :class="{ active: isActive }"、:class="[a, b]" |
| style 绑定 | :style="{ color: c }"、:style="[a, b]" |
| 不解析 HTML | 仅用 {{ }};要解析用 v-html(注意安全) |
十二、学习建议
- 先练熟:
{{ }}、:属性、@事件、v-model、v-if、v-for,这些是模板里最常用的。 - 牢记:模板里只能写单个表达式,不能写语句;复杂逻辑放到 script 里的函数或计算属性。
- 列表必带
:key,且 key 要唯一、稳定。 - class/style 多用对象和数组绑定,减少手拼字符串。
- 指令的详细说明(修饰符、自定义指令等)见《Vue3指令.md》;插槽见《Vue3插槽.md》。
把本文档里的示例在项目里敲一遍、改一改,会掌握得更快。祝你学习顺利。