模板语法 #
一、模板概述 #
Vue使用基于HTML的模板语法,允许开发者声明式地将DOM绑定到底层组件实例的数据上。
1.1 底层原理 #
text
模板 → 编译 → 渲染函数 → 虚拟DOM → 真实DOM
1.2 两种渲染方式 #
vue
<!-- 模板语法(推荐) -->
<template>
<div>{{ message }}</div>
</template>
<!-- 渲染函数 -->
<script>
import { h } from 'vue'
export default {
render() {
return h('div', this.message)
}
}
</script>
二、文本插值 #
2.1 基本插值 #
vue
<template>
<!-- 双大括号语法 -->
<p>{{ message }}</p>
<!-- 支持表达式 -->
<p>{{ number + 1 }}</p>
<p>{{ ok ? 'YES' : 'NO' }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<!-- 调用方法 -->
<p>{{ formatDate(date) }}</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue!')
const number = ref(10)
const ok = ref(true)
const date = ref(new Date())
function formatDate(d) {
return d.toLocaleDateString('zh-CN')
}
</script>
2.2 表达式限制 #
vue
<template>
<!-- 有效表达式 -->
<p>{{ count + 1 }}</p>
<p>{{ Math.random() > 0.5 ? '大' : '小' }}</p>
<!-- 无效表达式(不能使用语句) -->
<!-- <p>{{ var a = 1 }}</p> -->
<!-- <p>{{ if (ok) { return message } }}</p> -->
</template>
2.3 访问全局变量 #
javascript
// main.js
const app = createApp({})
// 配置全局变量
app.config.globalProperties.$formatDate = (date) => {
return new Date(date).toLocaleDateString('zh-CN')
}
// 模板中可访问的全局对象
// Math, Date, Array, Object, JSON, parseInt, parseFloat 等
三、原始HTML #
3.1 v-html指令 #
vue
<template>
<!-- 普通插值(转义HTML) -->
<p>{{ rawHtml }}</p>
<!-- 输出: <span style="color: red">红色文字</span> -->
<!-- v-html(渲染HTML) -->
<p v-html="rawHtml"></p>
<!-- 输出: 红色文字 -->
</template>
<script setup>
const rawHtml = '<span style="color: red">红色文字</span>'
</script>
3.2 安全警告 #
vue
<template>
<!-- 危险!不要在用户输入上使用v-html -->
<!-- <div v-html="userInput"></div> -->
<!-- 安全做法:使用纯文本 -->
<div>{{ userInput }}</div>
</template>
<script setup>
// 永远不要在用户提交的内容上使用v-html
// 可能导致XSS攻击
const userInput = '' // 用户输入
</script>
四、属性绑定 #
4.1 基本绑定 #
vue
<template>
<!-- 完整写法 -->
<div v-bind:id="dynamicId"></div>
<!-- 简写形式 -->
<div :id="dynamicId"></div>
<!-- 多个属性 -->
<div :id="dynamicId" :class="dynamicClass" :style="dynamicStyle"></div>
</template>
<script setup>
import { ref } from 'vue'
const dynamicId = ref('my-id')
const dynamicClass = ref('active')
const dynamicStyle = ref({ color: 'red' })
</script>
4.2 动态属性名 #
vue
<template>
<!-- 动态属性名 -->
<button :[attrName]="attrValue">按钮</button>
<!-- 动态事件名 -->
<button @[eventName]="handleClick">点击</button>
</template>
<script setup>
import { ref } from 'vue'
const attrName = ref('disabled')
const attrValue = ref(true)
const eventName = ref('click')
function handleClick() {
console.log('点击了')
}
</script>
4.3 绑定对象 #
vue
<template>
<!-- 绑定多个属性 -->
<div v-bind="objectOfAttrs"></div>
<!-- 等价于 -->
<div :id="objectOfAttrs.id" :class="objectOfAttrs.class"></div>
</template>
<script setup>
const objectOfAttrs = {
id: 'container',
class: 'wrapper active'
}
</script>
4.4 布尔型属性 #
vue
<template>
<!-- 布尔属性 -->
<button :disabled="isDisabled">按钮</button>
<!-- isDisabled为false时,disabled属性不会被渲染 -->
<button :disabled="false">按钮</button>
<!-- 其他布尔属性 -->
<input type="checkbox" :checked="isChecked">
<input type="text" :readonly="isReadonly">
<input type="text" :required="isRequired">
</template>
<script setup>
import { ref } from 'vue'
const isDisabled = ref(true)
const isChecked = ref(true)
const isReadonly = ref(false)
const isRequired = ref(true)
</script>
五、Class与Style绑定 #
5.1 绑定Class #
vue
<template>
<!-- 对象语法 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
<!-- 数组语法 -->
<div :class="[activeClass, errorClass]"></div>
<!-- 数组与对象混用 -->
<div :class="[activeClass, { 'text-danger': hasError }]"></div>
<!-- 与普通class共存 -->
<div class="static" :class="{ active: isActive }"></div>
</template>
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
const activeClass = ref('active')
const errorClass = ref('text-danger')
</script>
5.2 绑定Style #
vue
<template>
<!-- 对象语法 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<!-- 绑定样式对象 -->
<div :style="styleObject"></div>
<!-- 数组语法 -->
<div :style="[baseStyles, overridingStyles]"></div>
<!-- 多重值(浏览器自动选择支持的) -->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
</template>
<script setup>
import { ref } from 'vue'
const activeColor = ref('red')
const fontSize = ref(30)
const styleObject = ref({
color: 'red',
fontSize: '13px'
})
const baseStyles = ref({
color: 'blue',
fontSize: '14px'
})
const overridingStyles = ref({
color: 'green'
})
</script>
六、条件渲染 #
6.1 v-if / v-else / v-else-if #
vue
<template>
<div v-if="type === 'A'">
A类型
</div>
<div v-else-if="type === 'B'">
B类型
</div>
<div v-else-if="type === 'C'">
C类型
</div>
<div v-else>
其他类型
</div>
</template>
<script setup>
import { ref } from 'vue'
const type = ref('A')
</script>
6.2 template上的v-if #
vue
<template>
<!-- 在template上使用v-if -->
<template v-if="ok">
<h1>标题</h1>
<p>段落1</p>
<p>段落2</p>
</template>
</template>
6.3 v-show #
vue
<template>
<!-- v-show只是切换display属性 -->
<div v-show="isShow">显示/隐藏</div>
<!-- v-show不支持template -->
<!-- <template v-show="isShow">...</template> -->
</template>
6.4 v-if vs v-show #
| 特性 | v-if | v-show |
|---|---|---|
| 切换方式 | 销毁和重建DOM | CSS display切换 |
| 初始渲染 | 条件为false时不渲染 | 始终渲染 |
| 性能 | 切换开销大 | 初始渲染开销大 |
| 适用场景 | 条件很少改变 | 需要频繁切换 |
6.5 key管理 #
vue
<template>
<!-- 使用key强制重新渲染 -->
<div v-if="currentType === 'username'" key="username">
<input type="text" placeholder="用户名">
</div>
<div v-else key="email">
<input type="text" placeholder="邮箱">
</div>
<!-- 不使用key会复用元素 -->
<div v-if="currentType === 'username'">
<input type="text" placeholder="用户名">
</div>
<div v-else>
<input type="text" placeholder="邮箱">
</div>
</template>
七、列表渲染 #
7.1 遍历数组 #
vue
<template>
<!-- 基本遍历 -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- 带索引 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }}</span>
</template>
<script setup>
const items = [
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
]
</script>
7.2 遍历对象 #
vue
<template>
<!-- 遍历值 -->
<div v-for="value in object" :key="value">
{{ value }}
</div>
<!-- 遍历键和值 -->
<div v-for="(value, key) in object" :key="key">
{{ key }}: {{ value }}
</div>
<!-- 遍历键、值和索引 -->
<div v-for="(value, key, index) in object" :key="key">
{{ index }}. {{ key }}: {{ value }}
</div>
</template>
<script setup>
const object = {
name: '张三',
age: 25,
city: '北京'
}
</script>
7.3 template上的v-for #
vue
<template>
<ul>
<template v-for="item in items" :key="item.id">
<li>{{ item.name }}</li>
<li class="divider"></li>
</template>
</ul>
</template>
7.4 key的重要性 #
vue
<template>
<!-- 正确:使用唯一id -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- 不推荐:使用索引(可能导致问题) -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
<!-- 错误:没有key -->
<!-- <li v-for="item in items">{{ item.name }}</li> -->
</template>
八、事件处理 #
8.1 内联事件处理器 #
vue
<template>
<!-- 内联语句 -->
<button @click="count++">{{ count }}</button>
<!-- 调用方法 -->
<button @click="say('hello')">Say hello</button>
<!-- 访问事件对象 -->
<button @click="warn('警告', $event)">警告</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function say(message) {
alert(message)
}
function warn(message, event) {
if (event) {
event.preventDefault()
}
alert(message)
}
</script>
8.2 方法事件处理器 #
vue
<template>
<button @click="handleClick">点击</button>
</template>
<script setup>
function handleClick(event) {
// event是原生DOM事件
console.log(event.target)
}
</script>
8.3 事件修饰符 #
vue
<template>
<!-- 阻止默认行为 -->
<form @submit.prevent="onSubmit">
<button type="submit">提交</button>
</form>
<!-- 阻止事件冒泡 -->
<div @click="onDivClick">
<button @click.stop="onBtnClick">按钮</button>
</div>
<!-- 事件捕获模式 -->
<div @click.capture="doThis">...</div>
<!-- 只当事件在该元素本身触发 -->
<div @click.self="doThat">...</div>
<!-- 事件只触发一次 -->
<button @click.once="doOnce">只触发一次</button>
<!-- 链式调用 -->
<div @click.stop.prevent="doThat">...</div>
</template>
8.4 按键修饰符 #
vue
<template>
<!-- 按键别名 -->
<input @keyup.enter="submit">
<input @keyup.tab="nextInput">
<input @keyup.esc="cancel">
<input @keyup.space="addSpace">
<input @keyup.up="moveUp">
<input @keyup.down="moveDown">
<input @keyup.left="moveLeft">
<input @keyup.right="moveRight">
<!-- 系统修饰键 -->
<input @keyup.ctrl="onCtrl">
<input @keyup.alt="onAlt">
<input @keyup.shift="onShift">
<input @keyup.meta="onMeta">
<!-- 组合修饰键 -->
<input @keyup.ctrl.enter="onCtrlEnter">
<input @click.ctrl="onClickCtrl">
<!-- .exact精确修饰符 -->
<button @click.ctrl.exact="onCtrlOnly">只有Ctrl</button>
<button @click.ctrl.shift.exact="onCtrlShift">Ctrl+Shift</button>
</template>
8.5 鼠标按钮修饰符 #
vue
<template>
<button @click.left="onLeftClick">左键</button>
<button @click.middle="onMiddleClick">中键</button>
<button @click.right="onRightClick">右键</button>
</template>
九、v-model表单绑定 #
9.1 文本输入 #
vue
<template>
<input v-model="text" placeholder="输入文本">
<p>输入内容: {{ text }}</p>
<textarea v-model="content" placeholder="多行文本"></textarea>
<p>内容: {{ content }}</p>
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
const content = ref('')
</script>
9.2 复选框 #
vue
<template>
<!-- 单个复选框 -->
<input type="checkbox" v-model="checked">
<label>{{ checked }}</label>
<!-- 多个复选框 -->
<input type="checkbox" v-model="checkedNames" value="张三">
<label>张三</label>
<input type="checkbox" v-model="checkedNames" value="李四">
<label>李四</label>
<input type="checkbox" v-model="checkedNames" value="王五">
<label>王五</label>
<p>选中: {{ checkedNames }}</p>
</template>
<script setup>
import { ref } from 'vue'
const checked = ref(false)
const checkedNames = ref([])
</script>
9.3 单选按钮 #
vue
<template>
<input type="radio" v-model="picked" value="one">
<label>选项一</label>
<input type="radio" v-model="picked" value="two">
<label>选项二</label>
<p>选中: {{ picked }}</p>
</template>
<script setup>
import { ref } from 'vue'
const picked = ref('one')
</script>
9.4 选择框 #
vue
<template>
<!-- 单选 -->
<select v-model="selected">
<option disabled value="">请选择</option>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<!-- 多选 -->
<select v-model="selectedArr" multiple>
<option value="a">A</option>
<option value="b">B</option>
<option value="c">C</option>
</select>
<!-- 动态选项 -->
<select v-model="selectedId">
<option v-for="option in options" :key="option.id" :value="option.id">
{{ option.name }}
</option>
</select>
</template>
<script setup>
import { ref } from 'vue'
const selected = ref('')
const selectedArr = ref([])
const selectedId = ref(1)
const options = [
{ id: 1, name: '选项一' },
{ id: 2, name: '选项二' },
{ id: 3, name: '选项三' }
]
</script>
9.5 值绑定 #
vue
<template>
<!-- 复选框 -->
<input
type="checkbox"
v-model="toggle"
:true-value="trueValue"
:false-value="falseValue"
>
<p>{{ toggle }}</p>
<!-- 单选按钮 -->
<input type="radio" v-model="pick" :value="firstValue">
<input type="radio" v-model="pick" :value="secondValue">
</template>
<script setup>
import { ref } from 'vue'
const toggle = ref('yes')
const trueValue = 'yes'
const falseValue = 'no'
const pick = ref('')
const firstValue = 'first'
const secondValue = 'second'
</script>
9.6 修饰符 #
vue
<template>
<!-- .lazy - 在change事件后同步 -->
<input v-model.lazy="text">
<!-- .number - 转换为数字 -->
<input v-model.number="age" type="number">
<!-- .trim - 去除首尾空格 -->
<input v-model.trim="name">
</template>
十、总结 #
| 语法 | 说明 |
|---|---|
{{ }} |
文本插值 |
v-html |
输出原始HTML |
v-bind/: |
属性绑定 |
v-on/@ |
事件绑定 |
v-model |
双向绑定 |
v-if |
条件渲染(销毁/重建) |
v-show |
条件渲染(CSS切换) |
v-for |
列表渲染 |
v-slot/# |
插槽 |
模板语法要点:
- 使用双大括号进行文本插值
- v-bind用于属性绑定,可简写为
: - v-on用于事件绑定,可简写为
@ - v-model实现表单双向绑定
- v-if和v-show有不同的使用场景
- v-for需要配合key使用
最后更新:2026-03-26