Vue3 模板语法详解 - 初学者完全指南
注意: 复制文中示例代码到 play.vuejs.org/ 即可直接看到结果。
什么是模板语法?
Vue 模板语法就是在 HTML 中嵌入 Vue 的特殊指令和表达式,让 HTML 能够响应数据变化。
想象一下:
- 普通 HTML:静态的,内容不会变
- Vue 模板:动态的,内容会根据数据自动更新
模板语法的核心概念
csharp
Vue 模板语法分类:
┌─────────────────────────────────────┐
│ 数据绑定语法 │
│ ┌───────────────────────────────┐ │
│ │ {{ }} 插值 │ │
│ │ v-bind 属性绑定 │ │
│ └───────────────────────────────┘ │
├─────────────────────────────────────┤
│ 指令语法 │
│ ┌───────────────────────────────┐ │
│ │ v-if 条件渲染 │ │
│ │ v-for 列表渲染 │ │
│ │ v-on 事件处理 │ │
│ │ v-model 双向绑定 │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
1. 数据绑定语法
1.1 文本插值 {{ }}
vue
<template>
<div>
<h1>欢迎来到 {{ appName }}!</h1>
<p>当前时间:{{ currentTime }}</p>
<p>计算结果:{{ count * 2 + 10 }}</p>
<p>消息:{{ message.toUpperCase() }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const appName = '我的Vue应用'
const currentTime = new Date().toLocaleString()
const count = ref(5)
const message = ref('hello world')
</script>
1.2 属性绑定 v-bind
vue
<template>
<div>
<!-- 完整语法 -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">
<a v-bind:href="linkUrl" v-bind:title="linkTitle">链接</a>
<!-- 简写语法 (常用) -->
<img :src="imageUrl" :alt="imageAlt">
<a :href="linkUrl" :title="linkTitle">链接</a>
<!-- 动态绑定 class -->
<div :class="containerClass">容器</div>
<div :class="{ active: isActive, disabled: isDisabled }">动态类</div>
<div :class="[baseClass, isActive ? 'active' : '']">数组类</div>
<!-- 动态绑定 style -->
<div :style="{ color: textColor, fontSize: fontSize + 'px' }">样式绑定</div>
<div :style="computedStyle">对象样式</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const imageUrl = 'https://picsum.photos/200/300'
const imageAlt = '随机图片'
const linkUrl = 'https://vuejs.org'
const linkTitle = 'Vue 官网'
const isActive = ref(true)
const isDisabled = ref(false)
const containerClass = ref('container')
const baseClass = 'base'
const textColor = ref('red')
const fontSize = ref(18)
const computedStyle = computed(() => ({
color: textColor.value,
fontSize: fontSize.value + 'px',
fontWeight: 'bold'
}))
</script>
2. 条件渲染指令
2.1 v-if
、v-else-if
、v-else
vue
<template>
<div>
<!-- 基础条件渲染 -->
<h1 v-if="isLoggedIn">欢迎回来,{{ userName }}!</h1>
<h1 v-else>请先登录</h1>
<!-- 多条件分支 -->
<div v-if="userRole === 'admin'">
<h2>管理员面板</h2>
<button>管理用户</button>
<button>系统设置</button>
</div>
<div v-else-if="userRole === 'user'">
<h2>用户面板</h2>
<button>查看资料</button>
<button>修改密码</button>
</div>
<div v-else>
<h2>访客模式</h2>
<button @click="showLogin">登录</button>
</div>
<!-- 使用 template 包装多个元素 -->
<template v-if="showDetails">
<h3>详细信息</h3>
<p>邮箱:{{ userEmail }}</p>
<p>注册时间:{{ registerTime }}</p>
</template>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isLoggedIn = ref(false)
const userName = ref('张三')
const userRole = ref('user')
const showDetails = ref(true)
const userEmail = ref('zhangsan@example.com')
const registerTime = ref('2024-01-01')
const showLogin = () => {
isLoggedIn.value = true
}
</script>
2.2 v-show
vue
<template>
<div>
<!-- v-show 只是切换 display 样式 -->
<div v-show="isVisible">这个元素会显示/隐藏</div>
<button @click="toggleVisibility">切换显示</button>
<!-- v-show vs v-if 的区别 -->
<div v-if="condition1">v-if:条件为假时元素不存在</div>
<div v-show="condition2">v-show:条件为假时元素仍存在,只是隐藏</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
const condition1 = ref(false)
const condition2 = ref(false)
const toggleVisibility = () => {
isVisible.value = !isVisible.value
}
</script>
3. 列表渲染指令 v-for
3.1 基础列表渲染
vue
<template>
<div>
<!-- 遍历数组 -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }} - {{ item.price }}
</li>
</ul>
<!-- 遍历时获取索引 ------此处的 item 和 index 不能颠倒 -->
<div v-for="(item, index) in items" :key="item.id">
<p>第 {{ index + 1 }} 项:{{ item.name }}</p>
</div>
<!-- 遍历对象 ------ index 要在最后一个 -->
<div v-for="(value, key, index) in userInfo" :key="key">
<p>{{ index + 1 }}. {{ key }}: {{ value }}</p>
</div>
<!-- 遍历数字 -->
<div v-for="n in 5" :key="n">
第 {{ n }} 项
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '苹果', price: 5 },
{ id: 2, name: '香蕉', price: 3 },
{ id: 3, name: '橙子', price: 4 }
])
const userInfo = ref({
name: '张三',
age: 25,
email: 'zhangsan@example.com',
city: '北京'
})
</script>
3.2 嵌套循环
vue
<template>
<div>
<!-- 嵌套循环渲染分类商品 -->
<div v-for="category in categories" :key="category.id">
<h2>{{ category.name }}</h2>
<ul>
<li v-for="product in category.products" :key="product.id">
{{ product.name }} - ¥{{ product.price }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const categories = ref([
{
id: 1,
name: '水果',
products: [
{ id: 1, name: '苹果', price: 5 },
{ id: 2, name: '香蕉', price: 3 }
]
},
{
id: 2,
name: '蔬菜',
products: [
{ id: 3, name: '白菜', price: 2 },
{ id: 4, name: '萝卜', price: 1.5 }
]
}
])
</script>
4. 事件处理指令 v-on
4.1 基础事件绑定
vue
<template>
<div>
<!-- 基础事件 -->
<button v-on:click="handleClick">点击我</button>
<button @click="handleClick">简写语法</button>
<!-- 表单事件 -->
<input @input="handleInput" placeholder="输入内容">
<input @change="handleChange" placeholder="改变后触发">
<input @focus="handleFocus" @blur="handleBlur" placeholder="焦点事件">
<!-- 键盘事件 -->
<input @keyup.enter="handleEnter" placeholder="按回车触发">
<input @keyup.delete="handleDelete" placeholder="按删除键触发">
<!-- 鼠标事件 -->
<div
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
style="padding: 20px; background: #f0f0f0; margin: 10px 0;"
>
鼠标悬停区域
</div>
<!-- 事件修饰符 -->
<button @click.stop="handleClick">阻止事件冒泡</button>
<button @click.prevent="handleClick">阻止默认行为</button>
<button @click.once="handleClick">只触发一次</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
const handleClick = () => {
alert('按钮被点击了!')
}
const handleInput = (event) => {
message.value = event.target.value
console.log('输入内容:', message.value)
}
const handleChange = (event) => {
console.log('内容改变:', event.target.value)
}
const handleFocus = () => {
console.log('获得焦点')
}
const handleBlur = () => {
console.log('失去焦点')
}
const handleEnter = (event) => {
console.log('按回车:', event.target.value)
}
const handleDelete = () => {
console.log('按删除键')
}
const handleMouseEnter = () => {
console.log('鼠标进入')
}
const handleMouseLeave = () => {
console.log('鼠标离开')
}
</script>
4.2 事件传参
vue
<template>
<div>
<!-- 传递静态参数 -->
<button @click="greet('张三')">问候张三</button>
<button @click="greet('李四')">问候李四</button>
<!-- 传递动态参数 -->
<button
v-for="user in users"
:key="user.id"
@click="selectUser(user)"
>
选择 {{ user.name }}
</button>
<!-- 传递事件对象 -->
<input @click="handleClickWithEvent" value="点击我">
<!-- 内联处理器 -->
<button @click="count++">计数:{{ count }}</button>
<button @click="message = 'Hello Vue!'">设置消息</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const message = ref('')
const users = ref([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
])
const selectedUser = ref(null)
const greet = (name) => {
alert(`你好,${name}!`)
}
const selectUser = (user) => {
selectedUser.value = user
console.log('选中用户:', user)
}
const handleClickWithEvent = (event) => {
console.log('事件对象:', event)
console.log('目标元素:', event.target)
}
</script>
5. 双向绑定指令 v-model
5.1 基础双向绑定
vue
<template>
<div>
<!-- 文本输入框 -->
<input v-model="message" placeholder="输入消息">
<p>输入的内容:{{ message }}</p>
<!-- 多行文本 -->
<textarea v-model="description" placeholder="输入描述"></textarea>
<p>描述内容:{{ description }}</p>
<!-- 复选框 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">选中状态:{{ checked }}</label>
<!-- 多个复选框 -->
<div>
<input type="checkbox" id="apple" value="苹果" v-model="checkedFruits">
<label for="apple">苹果</label>
<input type="checkbox" id="banana" value="香蕉" v-model="checkedFruits">
<label for="banana">香蕉</label>
<input type="checkbox" id="orange" value="橙子" v-model="checkedFruits">
<label for="orange">橙子</label>
<p>选中的水果:{{ checkedFruits }}</p>
</div>
<!-- 单选按钮 -->
<div>
<input type="radio" id="male" value="男" v-model="gender">
<label for="male">男</label>
<input type="radio" id="female" value="女" v-model="gender">
<label for="female">女</label>
<p>选择的性别:{{ gender }}</p>
</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 message = ref('')
const description = ref('')
const checked = ref(false)
const checkedFruits = ref([])
const gender = ref('')
const selectedCity = ref('')
</script>
5.2 v-model 修饰符
vue
<template>
<div>
<!-- .lazy 修饰符:change 时更新而不是 input -->
<input v-model.lazy="lazyMessage" placeholder="失去焦点时更新">
<p>lazy 值:{{ lazyMessage }}</p>
<!-- .number 修饰符:自动转换为数字 -->
<input v-model.number="numberValue" type="number" placeholder="输入数字">
<p>类型:{{ typeof numberValue }},值:{{ numberValue }}</p>
<!-- .trim 修饰符:自动去除首尾空格 -->
<input v-model.trim="trimmedMessage" placeholder="自动去除空格">
<p>原始值:"{{ trimmedMessage }}"</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const lazyMessage = ref('')
const numberValue = ref(0)
const trimmedMessage = ref('')
</script>
6. 模板语法进阶用法
6.1 计算属性在模板中使用
vue
<template>
<div>
<input v-model="firstName" placeholder="名">
<input v-model="lastName" placeholder="姓">
<!-- 直接在模板中使用表达式 -->
<p>全名:{{ firstName + ' ' + lastName }}</p>
<!-- 使用计算属性(推荐) -->
<p>全名:{{ fullName }}</p>
<p>反转全名:{{ reversedName }}</p>
<!-- 条件渲染计算属性 -->
<p v-if="hasLongName">名字很长</p>
<p v-else>名字不长</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('')
const lastName = ref('')
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value
})
const reversedName = computed(() => {
return fullName.value.split('').reverse().join('')
})
const hasLongName = computed(() => {
return fullName.value.length > 10
})
</script>
6.2 模板中的方法调用
vue
<template>
<div>
<input v-model="searchText" placeholder="搜索文本">
<!-- 在模板中调用方法 -->
<p>大写:{{ toUpperCase(searchText) }}</p>
<p>字符数:{{ getTextLength(searchText) }}</p>
<p>是否包含数字:{{ containsNumber(searchText) }}</p>
<!-- 方法作为事件处理器 -->
<button @click="clearText">清空</button>
<button @click="randomText">随机文本</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const searchText = ref('')
const toUpperCase = (text) => {
return text.toUpperCase()
}
const getTextLength = (text) => {
return text.length
}
const containsNumber = (text) => {
return /\d/.test(text)
}
const clearText = () => {
searchText.value = ''
}
const randomText = () => {
const texts = ['Hello', 'Vue3', 'Template', 'Syntax']
searchText.value = texts[Math.floor(Math.random() * texts.length)]
}
</script>
7. 实际应用示例
完整的待办事项应用
vue
<template>
<div class="todo-app">
<h1>待办事项列表</h1>
<!-- 添加新任务 -->
<div class="add-todo">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="输入新任务"
>
<button @click="addTodo">添加</button>
</div>
<!-- 筛选器 -->
<div class="filters">
<button
v-for="filter in filters"
:key="filter.value"
:class="{ active: currentFilter === filter.value }"
@click="currentFilter = filter.value"
>
{{ filter.name }}
</button>
</div>
<!-- 任务列表 -->
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
v-model="todo.completed"
>
<span
:class="{ 'todo-text': true, 'completed-text': todo.completed }"
@dblclick="todo.editing = true"
v-if="!todo.editing"
>
{{ todo.text }}
</span>
<input
v-else
v-model="todo.text"
@blur="todo.editing = false"
@keyup.enter="todo.editing = false"
@keyup.esc="todo.editing = false"
class="edit-input"
v-focus
>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<!-- 统计信息 -->
<div class="stats">
<p>总计:{{ todos.length }} 项</p>
<p>已完成:{{ completedCount }} 项</p>
<p>未完成:{{ remainingCount }} 项</p>
</div>
</div>
</template>
<script setup>
import { ref, computed, nextTick } from 'vue'
// 自定义指令:自动聚焦
const vFocus = {
mounted: (el) => el.focus()
}
// 数据
const newTodo = ref('')
const todos = ref([
{ id: 1, text: '学习 Vue 模板语法', completed: false, editing: false },
{ id: 2, text: '完成待办事项应用', completed: false, editing: false }
])
const currentFilter = ref('all')
const filters = [
{ name: '全部', value: 'all' },
{ name: '未完成', value: 'active' },
{ name: '已完成', value: 'completed' }
]
// 计算属性
const filteredTodos = computed(() => {
switch (currentFilter.value) {
case 'active':
return todos.value.filter(todo => !todo.completed)
case 'completed':
return todos.value.filter(todo => todo.completed)
default:
return todos.value
}
})
const completedCount = computed(() => {
return todos.value.filter(todo => todo.completed).length
})
const remainingCount = computed(() => {
return todos.value.length - completedCount.value
})
// 方法
const addTodo = () => {
if (newTodo.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodo.value.trim(),
completed: false,
editing: false
})
newTodo.value = ''
}
}
const removeTodo = (id) => {
todos.value = todos.value.filter(todo => todo.id !== id)
}
</script>
<style scoped>
.todo-app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.add-todo {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.add-todo input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.filters {
margin-bottom: 20px;
}
.filters button {
margin-right: 10px;
padding: 5px 10px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 4px;
}
.filters button.active {
background: #007bff;
color: white;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
gap: 10px;
}
.todo-list li.completed {
opacity: 0.6;
}
.completed-text {
text-decoration: line-through;
}
.edit-input {
flex: 1;
padding: 5px;
border: 1px solid #007bff;
border-radius: 4px;
}
.stats {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
}
.stats p {
margin: 5px 0;
}
</style>
模板语法最佳实践
1. 命名规范
vue
<template>
<!-- 好的命名 -->
<div class="user-profile">
<h2 class="profile-title">{{ userProfile.name }}</h2>
<p class="profile-email">{{ userProfile.email }}</p>
</div>
<!-- 避免的命名 -->
<div class="div1">
<h2>{{ data.name }}</h2>
<p>{{ data.email }}</p>
</div>
</template>
2. 性能优化
vue
<template>
<!-- 好的做法:使用 key -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- 避免:没有 key 或使用 index 作为 key -->
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.name }}
</li>
</ul>
</template>
3. 可读性优化
vue
<template>
<!-- 好的可读性 -->
<div>
<button
v-if="user.isAuthenticated"
@click="logout"
class="logout-btn"
>
退出登录
</button>
<button
v-else
@click="showLoginModal"
class="login-btn"
>
登录
</button>
</div>
<!-- 避免:复杂表达式直接写在模板中 -->
<div v-if="user && user.profile && user.profile.permissions && user.profile.permissions.includes('admin')">
管理员功能
</div>
</template>
总结
模板语法核心要点:
- 数据绑定 :
{{ }}
和v-bind
让数据在模板中显示 - 条件渲染 :
v-if
和v-show
控制元素显示/隐藏 - 列表渲染 :
v-for
遍历数组和对象 - 事件处理 :
v-on
响应用户交互 - 双向绑定 :
v-model
实现表单数据同步
学习建议:
- 循序渐进:先掌握基础语法,再学习高级用法
- 多练习:通过实际项目加深理解
- 注意性能 :合理使用
key
,避免不必要的重新渲染 - 保持简洁 :复杂逻辑放到
script
中,模板保持简单
掌握了这些模板语法,你就能创建动态、交互性强的 Vue 应用了!