Vue 模板语法完全指南
一、什么是模板语法?
比喻理解
想象你在写一封信:
- 普通HTML:信的固定内容
- Vue模板语法:可以插入变量、条件判断、循环等动态内容
<!-- 普通HTML -->
<p>你好,张三!</p>
<!-- Vue模板 -->
<p>你好,{{ name }}!</p>
<!-- 可以动态显示:你好,李四! -->
二、插值语法(显示数据)
1. 文本插值 – 基本用法
<!-- 双大括号语法(Mustache语法) -->
<template>
<div>
<p>消息:{{ message }}</p>
<p>价格:¥{{ price }}</p>
<p>总数:{{ count + 1 }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!',
price: 100,
count: 5
}
}
}
</script>
注意:双大括号内是JavaScript表达式
<!-- 可以执行简单的表达式 -->
<p>计算:{{ 5 + 3 }}</p> <!-- 显示:8 -->
<p>三元运算:{{ isOk ? '是' : '否' }}</p>
<p>反转:{{ message.split('').reverse().join('') }}</p>
2. 一次性插值 – v-once
<!-- 只渲染一次,之后不再更新 -->
<p v-once>这个值不会变:{{ initialValue }}</p>
<!-- 实际应用场景 -->
<div v-once>
<h1>{{ title }}</h1> <!-- 只在初始化时渲染 -->
<p>创建时间:{{ createTime }}</p>
</div>
三、原始HTML – v-html
对比文本插值
<template>
<div>
<!-- 文本插值:显示原始文本 -->
<p>使用文本插值:{{ rawHtml }}</p>
<!-- 显示为:<span>红色文字</span> -->
<!-- v-html:渲染为HTML -->
<p>使用v-html:<span v-html="rawHtml"></span></p>
<!-- 显示为:红色文字(实际是红色) -->
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<span>红色文字</span>'
}
}
}
</script>
⚠️ 安全警告
<!-- ❌ 危险!永远不要这样做! -->
<div v-html="userInput"></div>
<!-- 缘由:用户可能输入恶意脚本 -->
<!-- 用户输入可能是:<script>恶意代码</script> -->
最佳实践:
data() {
return {
// 只信任服务器返回的、已消毒的内容
safeHtml: '<strong>安全内容</strong>',
// 从外部获取的内容要处理
externalContent: this.sanitize(apiResponse.html)
}
},
methods: {
sanitize(html) {
// 使用第三方库如DOMPurify清理HTML
return DOMPurify.sanitize(html)
}
}
四、属性绑定 – v-bind
1. 基本用法
<!-- 绑定普通属性 -->
<img v-bind:src="imageSrc">
<a v-bind:href="url">链接</a>
<!-- 简写(最常用) -->
<img :src="imageSrc">
<a :href="url">链接</a>
2. 绑定布尔属性
<!-- disabled属性,当isDisabled为true时禁用 -->
<button :disabled="isDisabled">按钮</button>
<!-- 多个复选框 -->
<input type="checkbox" :checked="isChecked">
<input type="radio" :checked="selected === 'A'">
3. 动态绑定属性名
<!-- 根据数据决定绑定哪个属性 -->
<template>
<div>
<!-- 绑定动态属性名 -->
<div :[attributeName]="value">内容</div>
<!-- 实际应用场景 -->
<button :[btnType]="btnValue">动态按钮</button>
<!-- 表单动态name -->
<input :name="inputName" :placeholder="placeholder">
</div>
</template>
<script>
export default {
data() {
return {
attributeName: 'class', // 可以是 'id', 'data-xxx' 等
value: 'my-class',
btnType: 'type', // 可以是 'disabled', 'title' 等
btnValue: 'submit',
inputName: 'username',
placeholder: '请输入用户名'
}
}
}
</script>
4. 绑定对象
<!-- 绑定多个属性 -->
<template>
<div>
<!-- 传统写法 -->
<input
:id="inputId"
:name="inputName"
:placeholder="placeholder"
:disabled="isDisabled">
<!-- 使用对象语法(更简洁) -->
<input v-bind="inputAttrs">
<!-- 动态生成属性对象 -->
<img v-bind="imgProps">
</div>
</template>
<script>
export default {
data() {
return {
// 对象语法
inputAttrs: {
id: 'username',
name: 'username',
placeholder: '请输入',
disabled: false
},
imgProps: {
src: 'image.jpg',
alt: '描述',
title: '图片标题',
loading: 'lazy'
}
}
}
}
</script>
五、条件渲染
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 v-if="user.role === 'admin'">
<button>删除用户</button>
<button>修改权限</button>
</div>
<div v-else-if="user.role === 'editor'">
<button>编辑文章</button>
</div>
<div v-else>
<button>查看内容</button>
</div>
<!-- 配合template使用 -->
<template v-if="isLoading">
<p>加载中...</p>
<div class="spinner"></div>
</template>
</div>
</template>
2. v-show
<template>
<div>
<!-- v-show 只是切换 display: none -->
<div v-show="isVisible">可见内容</div>
<!-- 实际效果等同于 -->
<div style="display: none">隐藏内容</div>
</div>
</template>
3. v-if vs v-show
|
特性 |
v-if |
v-show |
|
首次渲染 |
惰性(条件为真才渲染) |
始终渲染 |
|
切换开销 |
高(销毁/重建DOM) |
低(只是CSS切换) |
|
适用场景 |
不常切换的场景 |
频繁切换的场景 |
|
生命周期 |
切换时会触发生命周期 |
只是显示/隐藏 |
<!-- 何时用哪个? -->
<template>
<div>
<!-- 用 v-if:不常切换 -->
<div v-if="user.isAdmin">
管理员面板(复杂组件,不常切换)
</div>
<!-- 用 v-show:频繁切换 -->
<div v-show="showToolbar">
工具栏(需要快速显示/隐藏)
</div>
<!-- 用 v-if:初始不需要 -->
<div v-if="dataLoaded">
数据表格(初始不加载)
</div>
</div>
</template>
六、列表渲染 – v-for
1. 遍历数组
<template>
<div>
<!-- 基本用法 -->
<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 + 1 }}. {{ item.name }}
</li>
</ul>
<!-- 实际应用:商品列表 -->
<div class="products">
<div v-for="product in products" :key="product.id" class="product-card">
<img :src="product.image" :alt="product.name">
<h3>{{ product.name }}</h3>
<p>价格:¥{{ product.price }}</p>
<button @click="addToCart(product)">加入购物车</button>
</div>
</div>
</div>
</template>
2. 遍历对象
<template>
<div>
<!-- 遍历对象 -->
<ul>
<li v-for="(value, key, index) in userInfo" :key="key">
{{ index + 1 }}. {{ key }}: {{ value }}
</li>
</ul>
<!-- 实际应用:显示用户信息 -->
<dl>
<div v-for="(value, key) in user" :key="key">
<dt>{{ formatKey(key) }}:</dt>
<dd>{{ value }}</dd>
</div>
</dl>
</div>
</template>
<script>
export default {
data() {
return {
userInfo: {
name: '张三',
age: 25,
city: '北京',
email: 'zhangsan@example.com'
}
}
},
methods: {
formatKey(key) {
const map = {
name: '姓名',
age: '年龄',
city: '城市',
email: '邮箱'
}
return map[key] || key
}
}
}
</script>
3. 遍历数字范围
<template>
<div>
<!-- 生成1-10 -->
<span v-for="n in 10" :key="n">{{ n }} </span>
<!-- 显示:1 2 3 4 5 6 7 8 9 10 -->
<!-- 生成星星评分 -->
<div class="rating">
<span v-for="star in 5" :key="star">
{{ star <= rating ? '★' : '☆' }}
</span>
</div>
</div>
</template>
4. 配合 v-if
<template>
<div>
<!-- ❌ 错误:不要在同一元素上用v-for和v-if -->
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
<!-- ✅ 正确:使用template或计算属性 -->
<!-- 方法1:使用template -->
<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>
<!-- 方法2:使用计算属性 -->
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</div>
</template>
<script>
export default {
computed: {
activeUsers() {
return this.users.filter(user => user.isActive)
}
}
}
</script>
5. 为什么需要 :key
<template>
<div>
<!-- 没有key(不推荐) -->
<li v-for="item in items">{{ item }}</li>
<!-- 有key(推荐) -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- key的用途 -->
<ul>
<!-- Vue通过key识别节点,高效更新DOM -->
<!-- 当数组顺序变化时,Vue能重用现有元素 -->
<!-- 使用唯一标识,不要用index! -->
<!-- ❌ 不好:index会变 -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
<!-- ✅ 好:用唯一id -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
七、事件处理 – v-on
1. 基本用法
<template>
<div>
<!-- 内联语句 -->
<button v-on:click="counter++">增加 {{ counter }}</button>
<!-- 方法名 -->
<button v-on:click="sayHello">打招呼</button>
<!-- 简写(最常用) -->
<button @click="sayHello">打招呼</button>
<!-- 调用方法并传参 -->
<button @click="say('你好', $event)">带参数</button>
<!-- 实际应用 -->
<form @submit="handleSubmit">
<input type="text" v-model="message">
<button type="submit">提交</button>
<button type="button" @click="resetForm">重置</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
message: ''
}
},
methods: {
sayHello() {
alert('Hello!')
},
say(message, event) {
console.log(message)
console.log('事件对象:', event)
},
handleSubmit(event) {
event.preventDefault() // 阻止默认提交行为
console.log('提交的内容:', this.message)
},
resetForm() {
this.message = ''
}
}
}
</script>
2. 事件修饰符
<template>
<div>
<!-- 阻止默认行为 -->
<a href="/about" @click.prevent="goToAbout">关于我们</a>
<!-- 阻止事件冒泡 -->
<div @click="parentClick">
<button @click.stop="childClick">点击我</button>
</div>
<!-- 只触发一次 -->
<button @click.once="init">只初始化一次</button>
<!-- 串联修饰符 -->
<form @submit.prevent.once="submitForm">
<!-- 只提交一次 -->
</form>
<!-- 实际应用:右键菜单 -->
<div @contextmenu.prevent="showContextMenu">
右键点击
</div>
<!-- 按键修饰符 -->
<input
@keyup.enter="submit"
@keyup.esc="cancel"
@keyup.ctrl.enter="specialSubmit">
</div>
</template>
3. 常用事件修饰符
|
修饰符 |
作用 |
示例 |
|
.stop |
阻止事件冒泡 |
@click.stop |
|
.prevent |
阻止默认行为 |
@submit.prevent |
|
.capture |
使用捕获模式 |
@click.capture |
|
.self |
只当事件是自身触发 |
@click.self |
|
.once |
只触发一次 |
@click.once |
|
.passive |
不阻止默认行为 |
@scroll.passive |
4. 按键修饰符
<template>
<div>
<!-- 常用按键 -->
<input @keyup.enter="submit">
<input @keyup.tab="nextField">
<input @keyup.delete="deleteItem">
<input @keyup.esc="cancel">
<input @keyup.space="toggle">
<!-- 组合键 -->
<input @keyup.ctrl.enter="quickSubmit">
<input @keyup.alt.delete="forceDelete">
<!-- 方向键 -->
<div @keyup.left="moveLeft" @keyup.right="moveRight">
用方向键移动
</div>
<!-- 自定义按键 -->
<input @keyup.f1="showHelp">
</div>
</template>
八、双向绑定 – v-model
1. 基本表单元素
<template>
<div>
<!-- 文本输入 -->
<input v-model="text" placeholder="请输入">
<p>输入的内容:{{ text }}</p>
<!-- 多行文本 -->
<textarea v-model="message" placeholder="多行文本"></textarea>
<!-- 复选框(单个) -->
<input type="checkbox" v-model="checked">
<label>是否同意</label>
<!-- 复选框(多个) -->
<div>
<input type="checkbox" value="vue" v-model="skills">
<label>Vue</label>
<input type="checkbox" value="react" v-model="skills">
<label>React</label>
<input type="checkbox" value="angular" v-model="skills">
<label>Angular</label>
</div>
<p>选择的技能:{{ skills }}</p>
<!-- 单选按钮 -->
<div>
<input type="radio" value="男" v-model="gender">
<label>男</label>
<input type="radio" value="女" v-model="gender">
<label>女</label>
</div>
<!-- 下拉选择 -->
<select v-model="selected">
<option value="">请选择</option>
<option value="A">选项A</option>
<option value="B">选项B</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
text: '',
message: '',
checked: false,
skills: [], // 多选框用数组
gender: '男',
selected: ''
}
}
}
</script>
2. 修饰符
<template>
<div>
<!-- .lazy - 输入完成才更新 -->
<input v-model.lazy="text">
<!-- 输入时不会立即更新,失去焦点时才更新 -->
<!-- .number - 转换为数字 -->
<input v-model.number="age" type="number">
<!-- 字符串转为数字 -->
<!-- .trim - 去除首尾空格 -->
<input v-model.trim="username">
<!-- 自动去除空格 -->
<!-- 实际应用:搜索框 -->
<input
v-model.trim.lazy="search"
@keyup.enter="doSearch"
placeholder="输入关键字搜索">
<!-- 实际应用:表单验证 -->
<input
v-model.number="price"
type="number"
min="0"
step="0.01">
</div>
</template>
3. 自定义组件使用 v-model
<!-- 父组件 -->
<template>
<div>
<CustomInput v-model="message" />
<p>父组件中的值:{{ message }}</p>
</div>
</template>
<script>
import CustomInput from './CustomInput.vue'
export default {
components: { CustomInput },
data() {
return {
message: ''
}
}
}
</script>
<!-- 子组件 CustomInput.vue -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)">
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
九、样式绑定
1. Class绑定
<template>
<div>
<!-- 对象语法(最常用) -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>
<!-- 数组语法 -->
<div :class="[activeClass, errorClass]"></div>
<!-- 混合使用 -->
<div :class="[{ active: isActive }, 'static-class', dynamicClass]"></div>
<!-- 实际应用:导航菜单 -->
<nav>
<a
v-for="item in menu"
:key="item.id"
:class="{ active: currentMenu === item.id }"
@click="currentMenu = item.id">
{{ item.name }}
</a>
</nav>
<!-- 实际应用:表单验证样式 -->
<input
:class="{
'form-control': true,
'is-valid': isValid,
'is-invalid': hasError
}"
v-model="username">
</div>
</template>
<script>
export default {
data() {
return {
isActive: true,
hasError: false,
activeClass: 'active',
errorClass: 'text-danger',
currentMenu: 'home',
menu: [
{ id: 'home', name: '首页' },
{ id: 'about', name: '关于' }
],
isValid: false
}
}
}
</script>
2. Style绑定
<template>
<div>
<!-- 对象语法 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<!-- 数组语法(多个对象) -->
<div :style="[baseStyles, overridingStyles]"></div>
<!-- 自动添加前缀 -->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
<!-- 实际应用:动态进度条 -->
<div class="progress-bar">
<div
class="progress"
:style="{ width: progress + '%', backgroundColor: color }">
</div>
</div>
<!-- 实际应用:元素位置 -->
<div
class="draggable"
:style="{
left: x + 'px',
top: y + 'px',
transform: `rotate(${rotation}deg)`
}">
可拖拽元素
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeColor: 'red',
fontSize: 16,
baseStyles: {
color: 'red',
fontSize: '14px'
},
overridingStyles: {
fontWeight: 'bold'
},
progress: 50,
color: '#42b983',
x: 100,
y: 200,
rotation: 45
}
}
}
</script>
十、特殊指令
1. v-cloak – 解决闪现问题
<!-- 在Vue编译完成前,隐藏未编译的模板 -->
<style>
[v-cloak] {
display: none;
}
</style>
<div v-cloak>
<!-- 页面加载时,这个区域会隐藏 -->
<!-- Vue编译完成后,会自动移除v-cloak属性 -->
{{ message }}
</div>
2. v-pre – 跳过编译
<!-- 显示原始双大括号,不进行编译 -->
<div v-pre>
<!-- 这里的内容不会被Vue编译 -->
{{ 这里的内容会原样显示 }}
<!-- 显示为:{{ 这里的内容会原样显示 }} -->
</div>
<!-- 用途:显示代码示例 -->
<pre v-pre>
{{ message }} <!-- 显示为:{{ message }} -->
<div v-if="show">内容</div>
</pre>
3. v-once – 一次性渲染
<!-- 只渲染一次,之后不会更新 -->
<div v-once>
<h1>{{ title }}</h1> <!-- 只渲染一次 -->
<p>{{ content }}</p> <!-- 只渲染一次 -->
</div>
<!-- 实际应用:静态内容 -->
<header v-once>
<h1>{{ siteTitle }}</h1>
<nav>导航菜单</nav>
</header>
十一、实战综合示例
商品列表页面
<template>
<div class="product-page">
<!-- 搜索区域 -->
<div class="search-box">
<input
v-model.trim.lazy="searchKeyword"
@keyup.enter="search"
placeholder="搜索商品...">
<button @click="search">搜索</button>
<button @click="resetFilter">重置</button>
</div>
<!-- 分类筛选 -->
<div class="filters">
<span
v-for="category in categories"
:key="category.id"
:class="['category-tag', { active: selectedCategory === category.id }]"
@click="selectCategory(category.id)">
{{ category.name }}
</span>
</div>
<!-- 排序选项 -->
<div class="sort-options">
<label>
<input
type="radio"
value="price_asc"
v-model="sortBy">
价格从低到高
</label>
<label>
<input
type="radio"
value="price_desc"
v-model="sortBy">
价格从高到低
</label>
</div>
<!-- 商品列表 -->
<div class="product-list">
<div
v-for="product in filteredProducts"
:key="product.id"
class="product-card"
:class="{ 'out-of-stock': product.stock === 0 }">
<!-- 商品图片 -->
<img
:src="product.image"
:alt="product.name"
@error="handleImageError"
@click="viewDetail(product)">
<!-- 商品信息 -->
<div class="product-info">
<h3>{{ product.name }}</h3>
<p class="price">¥{{ product.price.toFixed(2) }}</p>
<!-- 库存状态 -->
<p v-if="product.stock === 0" class="stock out">缺货</p>
<p v-else-if="product.stock < 10" class="stock low">仅剩{{ product.stock }}件</p>
<p v-else class="stock in">有货</p>
<!-- 评分 -->
<div class="rating">
<span
v-for="star in 5"
:key="star"
:class="['star', { active: star <= product.rating }]">
★
</span>
<span class="rating-text">({{ product.reviews }})</span>
</div>
</div>
<!-- 操作按钮 -->
<div class="actions">
<button
:disabled="product.stock === 0"
@click="addToCart(product)"
:class="{ disabled: product.stock === 0 }">
{{ product.stock === 0 ? '已售罄' : '加入购物车' }}
</button>
<button
@click="toggleFavorite(product)"
:class="{ favorite: product.isFavorite }">
{{ product.isFavorite ? '❤️ 已收藏' : ' 收藏' }}
</button>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="filteredProducts.length === 0" class="empty-state">
<p>没有找到相关商品</p>
<button @click="resetFilter">查看所有商品</button>
</div>
<!-- 加载更多 -->
<div v-show="hasMore" class="load-more">
<button
@click="loadMore"
:disabled="loading">
{{ loading ? '加载中...' : '加载更多' }}
</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
// 搜索关键词
searchKeyword: '',
// 选中的分类
selectedCategory: null,
// 排序方式
sortBy: 'price_asc',
// 商品数据
products: [],
categories: [],
// 分页
currentPage: 1,
pageSize: 10,
hasMore: true,
loading: false
}
},
computed: {
// 过滤和排序后的商品
filteredProducts() {
let filtered = this.products
// 按关键词过滤
if (this.searchKeyword) {
const keyword = this.searchKeyword.toLowerCase()
filtered = filtered.filter(product =>
product.name.toLowerCase().includes(keyword) ||
product.description.toLowerCase().includes(keyword)
)
}
// 按分类过滤
if (this.selectedCategory) {
filtered = filtered.filter(
product => product.categoryId === this.selectedCategory
)
}
// 排序
filtered.sort((a, b) => {
if (this.sortBy === 'price_asc') {
return a.price - b.price
} else if (this.sortBy === 'price_desc') {
return b.price - a.price
}
return 0
})
return filtered
}
},
methods: {
// 搜索
search() {
this.currentPage = 1
this.loadProducts()
},
// 选择分类
selectCategory(categoryId) {
this.selectedCategory =
this.selectedCategory === categoryId ? null : categoryId
this.currentPage = 1
this.loadProducts()
},
// 重置筛选
resetFilter() {
this.searchKeyword = ''
this.selectedCategory = null
this.sortBy = 'price_asc'
this.currentPage = 1
this.loadProducts()
},
// 添加购物车
addToCart(product) {
if (product.stock === 0) return
this.$emit('add-to-cart', {
id: product.id,
name: product.name,
price: product.price,
quantity: 1
})
// 添加动画效果
const button = event.target
button.classList.add('added')
setTimeout(() => {
button.classList.remove('added')
}, 1000)
},
// 切换收藏
toggleFavorite(product) {
product.isFavorite = !product.isFavorite
// 保存到本地存储
this.saveFavorites()
},
// 图片加载失败处理
handleImageError(event) {
event.target.src = '/images/default-product.jpg'
},
// 查看详情
viewDetail(product) {
this.$router.push(`/product/${product.id}`)
},
// 加载更多
loadMore() {
this.currentPage++
this.loadProducts()
},
// 加载商品
async loadProducts() {
this.loading = true
try {
const response = await this.$api.getProducts({
page: this.currentPage,
size: this.pageSize,
keyword: this.searchKeyword,
category: this.selectedCategory,
sort: this.sortBy
})
if (this.currentPage === 1) {
this.products = response.data
} else {
this.products.push(...response.data)
}
this.hasMore = response.hasMore
} catch (error) {
console.error('加载商品失败:', error)
} finally {
this.loading = false
}
}
},
mounted() {
this.loadProducts()
this.loadCategories()
}
}
</script>
<style scoped>
.product-page {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.search-box {
margin-bottom: 20px;
}
.filters {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.category-tag {
padding: 5px 15px;
border: 1px solid #ddd;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
}
.category-tag.active {
background-color: #42b983;
color: white;
border-color: #42b983;
}
.product-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.product-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
transition: transform 0.3s, box-shadow 0.3s;
}
.product-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.product-card.out-of-stock {
opacity: 0.6;
}
.stock.out {
color: #f56c6c;
}
.stock.low {
color: #e6a23c;
}
.stock.in {
color: #67c23a;
}
.rating {
margin: 10px 0;
}
.star {
color: #ddd;
font-size: 18px;
}
.star.active {
color: #f7ba2a;
}
.actions {
display: flex;
gap: 10px;
margin-top: 15px;
}
button {
flex: 1;
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
button.favorite {
background-color: #ffe6e6;
color: #f56c6c;
}
.empty-state {
text-align: center;
padding: 50px;
color: #909399;
}
.load-more {
text-align: center;
margin-top: 30px;
}
</style>
十二、最佳实践和技巧
1. 保持模板简洁
<!-- ❌ 不好:模板中逻辑太多 -->
<div>
{{ user.firstName + ' ' + user.lastName + ' (' + user.age + '岁)' }}
</div>
<!-- ✅ 好:使用计算属性 -->
<template>
<div>
{{ fullNameWithAge }}
</div>
</template>
<script>
export default {
computed: {
fullNameWithAge() {
return `${this.user.firstName} ${this.user.lastName} (${this.user.age}岁)`
}
}
}
</script>
2. 合理使用 v-if 和 v-show
<!-- 初始不需要的,用v-if -->
<div v-if="isAdmin">
<!-- 管理员才有的复杂组件 -->
<AdminPanel />
</div>
<!-- 频繁切换的,用v-show -->
<div v-show="showToolbar">
<!-- 常常显示/隐藏的工具栏 -->
<Toolbar />
</div>
<!-- 多个元素用template包裹 -->
<template v-if="isLoading">
<div class="spinner"></div>
<p>加载中...</p>
</template>
3. 列表渲染优化
<!-- 使用唯一的key -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- 大数据量使用虚拟滚动 -->
<virtual-list :size="50" :remain="10">
<div v-for="item in largeList" :key="item.id">
{{ item.content }}
</div>
</virtual-list>
4. 事件处理优化
<!-- 防抖处理 -->
<input @input="onSearch" placeholder="搜索...">
<script>
export default {
methods: {
onSearch: _.debounce(function(event) {
this.search(event.target.value)
}, 500)
}
}
</script>
5. 表单处理技巧
<!-- 表单验证 -->
<input
v-model="email"
:class="{ 'is-invalid': !isEmailValid }"
@blur="validateEmail">
<!-- 文件上传 -->
<input
type="file"
@change="handleFileUpload"
multiple
accept="image/*">
总结表格
|
语法 |
用途 |
示例 |
|
{{ }} |
文本插值 |
{{ message }} |
|
v-html |
原始HTML |
<span v-html=”rawHtml”></span> |
|
v-bind/ : |
属性绑定 |
:src=”imageUrl” |
|
v-if |
条件渲染 |
v-if=”isShow” |
|
v-show |
显示/隐藏 |
v-show=”isVisible” |
|
v-for |
列表渲染 |
v-for=”item in items” |
|
v-on/ @ |
事件监听 |
@click=”handleClick” |
|
v-model |
双向绑定 |
v-model=”input” |
|
:class |
类绑定 |
:class=”{ active: isActive }” |
|
:style |
样式绑定 |
:style=”{ color: textColor }” |
|
v-cloak |
隐藏未编译模板 |
[v-cloak] { display: none } |
|
v-pre |
跳过编译 |
<div v-pre>{{ 不会编译 }}</div> |
|
v-once |
一次性渲染 |
<div v-once>{{ 只渲染一次 }}</div> |
记住:Vue模板语法就像是给HTML赋予了”超能力”,让你能轻松实现数据的动态展示和交互!





