一、写在前面
学到这里,你应该已经会这些基础能力了:
-
用
{``{ }}显示数据 -
用
v-bind绑定属性 -
用
@click绑定事件 -
用
v-model绑定输入框 -
用
v-if控制显示隐藏 -
用
v-for渲染列表 -
用
ref、reactive定义响应式数据 -
用
computed、watch处理派生值和监听逻辑
但在真正写页面的时候,很多人会很快碰到新的问题:
-
一个按钮选中和未选中,样式怎么动态切换?
-
行内样式怎么根据数据变化?
-
点击事件里,怎么拿到事件对象?
-
提交表单时,为什么页面会刷新?
-
输入回车时怎么直接触发搜索?
-
复选框、单选框、下拉框的绑定到底怎么写?
-
为什么代码能跑,但总觉得写法不规范?
这些问题,表面看起来是零碎细节,
实际上它们非常重要,因为它们决定了你是不是能把页面写得真正"像回事"。
所以这一篇文章,我们就集中解决 Vue3 模板层最常见的一批进阶问题。
二、为什么模板进阶很重要?
很多新手会有一个误区:
觉得只要核心逻辑会了,模板细节问题不大。
其实恰恰相反。
真实项目开发里,很多时间并不是花在"会不会 ref",
而是花在:
-
样式怎么跟状态联动
-
表单怎么更规范处理
-
事件怎么正确拦截和传参
-
页面交互细节怎么做得自然
也就是说,模板进阶部分不是"边角料",而是你把基础知识真正用起来的重要一步。
如果前面的内容解决的是:
页面能不能跑
那么这一篇更偏向解决:
页面能不能写得更顺、更稳、更接近真实开发
三、动态 class 绑定:为什么页面样式要跟数据联动?
先从最常见的需求开始。
很多页面样式不是固定的,而是跟状态变化有关。比如:
-
当前按钮是否选中
-
当前任务是否完成
-
当前标签页是否激活
-
某一项是否高亮
-
某个文本是否显示为红色警告
这时候,你不能把 class 写死。
因为 class 本身也要由数据来决定。
这就是动态 class 绑定要解决的问题。
四、class 绑定的基本写法
Vue 里最常用的是 :class。
1. 字符串写法
<template>
<div :class="className">内容区域</div>
</template>
<script setup>
const className = 'box'
</script>
这表示:
-
div的 class 不再写死 -
而是由
className这个变量决定
这种方式最简单,但灵活度一般。
2. 对象写法
这是实际开发中非常常见的方式。
<template>
<div :class="{ active: isActive, danger: isDanger }">内容区域</div>
</template>
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const isDanger = ref(false)
</script>
这段代码的意思是:
-
如果
isActive为true,就加上active -
如果
isDanger为true,就加上danger
这种写法特别适合"根据布尔状态切换样式"。
3. 数组写法
<template>
<div :class="[classA, classB]">内容区域</div>
</template>
<script setup>
const classA = 'box'
const classB = 'active'
</script>
这表示:
- 同时绑定多个 class
数组写法适合"多个类名组合"的场景。
五、动态 class 最常见的实战场景
1. 按钮选中状态
<template>
<button :class="{ active: isSelected }" @click="toggleSelect">
点我切换状态
</button>
</template>
<script setup>
import { ref } from 'vue'
const isSelected = ref(false)
const toggleSelect = () => {
isSelected.value = !isSelected.value
}
</script>
<style scoped>
.active {
background-color: skyblue;
color: white;
}
</style>
这个例子很经典,因为它直接体现了:
样式不是固定写死的,而是跟状态绑定的。
2. 列表项高亮
<template>
<ul>
<li
v-for="item in list"
:key="item.id"
:class="{ current: currentId === item.id }"
@click="currentId = item.id"
>
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const currentId = ref(1)
const list = ref([
{ id: 1, name: '首页' },
{ id: 2, name: '课程' },
{ id: 3, name: '我的' }
])
</script>
<style scoped>
.current {
color: red;
font-weight: bold;
}
</style>
这就是标签页、导航栏、菜单高亮的常见写法。
六、动态 style 绑定:行内样式也能跟着数据变
除了 class,样式也可以直接动态绑定。
Vue 里用的是 :style。
1. 对象写法
<template>
<div :style="{ color: textColor, fontSize: fontSize + 'px' }">
动态样式文本
</div>
</template>
<script setup>
const textColor = 'blue'
const fontSize = 24
</script>
这里表示:
-
文字颜色由
textColor控制 -
字体大小由
fontSize控制
2. 适合哪些场景?
style 绑定更适合这种情况:
-
某个具体样式值是动态变化的
-
不太适合专门去定义 class
-
需要根据数据直接计算样式值
比如:
-
进度条宽度
-
字体大小
-
颜色切换
-
显示/隐藏某些样式数值
例如进度条:
<template>
<div class="bar">
<div class="inner" :style="{ width: progress + '%' }"></div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const progress = ref(60)
</script>
这里 inner 的宽度会随着 progress 变化。
七、class 和 style 应该怎么选?
这是个很实际的问题。
你可以先记住一个简单原则:
优先用 class
如果只是"几种样式状态切换",更推荐用 class。
因为:
-
结构更清晰
-
样式更容易维护
-
更符合常规开发习惯
用 style
如果是"某个具体样式值随数据变化",可以用 style。
比如:
-
宽度 73%
-
字体大小 18px
-
背景色来自变量
所以:
-
状态切换型样式 :更偏
class -
数值驱动型样式 :更偏
style
八、事件对象 $event 是什么?
前面你已经会写:
<button @click="handleClick">按钮</button>
但很快你就会遇到更具体的问题:
-
点击的是哪个元素?
-
鼠标事件里有哪些信息?
-
输入事件里如何拿到输入值?
-
我既想传参数,又想拿事件对象怎么办?
这时候就要理解事件对象。
1. 默认事件对象
在原生 JS 里,事件触发时通常都会产生一个事件对象。
Vue 也一样。
例如:
<template>
<button @click="handleClick">点我</button>
</template>
<script setup>
const handleClick = (event) => {
console.log(event)
}
</script>
这里的 event 就是事件对象。
它里面会包含很多信息,比如:
-
触发事件的元素
-
鼠标位置
-
键盘按键
-
默认行为相关信息
2. 模板里显式传 $event
如果你还想额外传参数,就可以这样写:
<template>
<button @click="handleClick('张三', $event)">点我</button>
</template>
<script setup>
const handleClick = (name, event) => {
console.log(name)
console.log(event)
}
</script>
这里的意思是:
-
第一个参数是你手动传的
'张三' -
第二个参数是当前事件对象
所以 $event 可以理解成:
Vue 模板里当前事件对应的原生事件对象。
九、为什么有时候不直接用 event.target.value?
很多输入框示例里,你会看到这种写法:
<input @input="handleInput" />
const handleInput = (event) => {
console.log(event.target.value)
}
这当然没问题。
但在 Vue 里,如果你的目标只是做"输入框值和数据同步",
通常更推荐直接用 v-model。
因为:
-
v-model更简洁 -
可读性更高
-
更符合 Vue 思路
所以你可以这样理解:
-
想自己细控制输入事件,可以用事件对象
-
只是做值绑定 ,优先考虑
v-model
十、事件修饰符:为什么 Vue 要提供这些语法?
这部分是模板进阶里的高频内容。
很多事件处理,本来需要你手动写一堆原生逻辑。
Vue 为了让模板更简洁,提供了 事件修饰符。
它们本质上是在帮你做一些常见事件处理动作。
最常见的有:
-
.stop -
.prevent -
.enter
十一、.stop:阻止事件冒泡
先看一个场景:
<template>
<div @click="parentClick">
父盒子
<button @click="childClick">子按钮</button>
</div>
</template>
<script setup>
const parentClick = () => {
console.log('点击了父盒子')
}
const childClick = () => {
console.log('点击了子按钮')
}
</script>
如果你点击子按钮,通常会发现:
-
子按钮事件触发了
-
父盒子事件也触发了
这是因为事件会冒泡。
如果你不想让子按钮点击继续冒泡到父元素,就可以写:
<button @click.stop="childClick">子按钮</button>
.stop 的意思就是:
阻止事件继续向外冒泡。
这是写弹窗、按钮组、嵌套点击区域时非常常见的需求。
十二、.prevent:阻止默认行为
有些 HTML 元素本身带默认行为。
例如:
-
表单提交会刷新页面
-
链接点击会跳转
但很多时候你并不想让这个默认行为发生。
1. 表单提交场景
<template>
<form @submit.prevent="handleSubmit">
<input v-model="username" />
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const handleSubmit = () => {
console.log('提交内容:', username.value)
}
</script>
这里的 .prevent 表示:
阻止表单原本的刷新提交行为。
这在 Vue 项目里非常常见,因为前端通常想自己控制提交逻辑,而不是让浏览器直接刷新页面。
2. 链接点击场景
<a href="https://example.com" @click.prevent="handleClick">点击</a>
这会阻止默认跳转,而改为先执行你自己的逻辑。
十三、.enter:按下回车触发事件
这个修饰符在搜索框、登录框里特别常见。
例如:
<template>
<input v-model="keyword" @keyup.enter="search" placeholder="请输入关键词" />
</template>
<script setup>
import { ref } from 'vue'
const keyword = ref('')
const search = () => {
console.log('开始搜索:', keyword.value)
}
</script>
这段代码的意思是:
-
正常输入时不触发搜索
-
只有按下 Enter 键时才执行
search
所以 .enter 本质上是:
键盘事件的快捷条件筛选。
这会让模板写法比你手动判断 keyCode 更清晰。
十四、表单处理为什么是 Vue 新手高频场景?
因为表单几乎无处不在:
-
登录
-
注册
-
搜索
-
添加学生
-
提交评论
-
创建任务
-
编辑资料
而 Vue 的一个强项,就是特别适合处理表单和数据联动。
所以表单处理一定是初学阶段要掌握的重点。
十五、输入框、文本域、下拉框的基础绑定
1. 文本输入框
<template>
<input v-model="username" placeholder="请输入用户名" />
<p>当前输入:{{ username }}</p>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
</script>
2. 文本域
<template>
<textarea v-model="content"></textarea>
<p>{{ content }}</p>
</template>
<script setup>
import { ref } from 'vue'
const content = ref('')
</script>
3. 下拉框
<template>
<select v-model="city">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
<p>当前城市:{{ city }}</p>
</template>
<script setup>
import { ref } from 'vue'
const city = ref('北京')
</script>
这些都属于 Vue 表单处理中最基础、最常见的写法。
十六、复选框和单选框怎么理解?
这部分新手也很容易写乱。
1. 单个复选框
<template>
<input type="checkbox" v-model="isAgree" />
<span>是否同意协议:{{ isAgree }}</span>
</template>
<script setup>
import { ref } from 'vue'
const isAgree = ref(false)
</script>
这里 isAgree 会是布尔值:
-
勾选为
true -
不勾选为
false
2. 单选框
<template>
<input type="radio" value="男" v-model="gender" /> 男
<input type="radio" value="女" v-model="gender" /> 女
<p>当前性别:{{ gender }}</p>
</template>
<script setup>
import { ref } from 'vue'
const gender = ref('男')
</script>
这里 gender 会始终是当前选中的那个值。
3. 多个复选框
<template>
<input type="checkbox" value="篮球" v-model="hobbies" /> 篮球
<input type="checkbox" value="足球" v-model="hobbies" /> 足球
<input type="checkbox" value="游戏" v-model="hobbies" /> 游戏
<p>爱好:{{ hobbies }}</p>
</template>
<script setup>
import { ref } from 'vue'
const hobbies = ref([])
</script>
这里 hobbies 会是一个数组,
记录当前勾选了哪些内容。
这一点很重要:
多个复选框配合同一个
v-model时,通常绑定的是数组。
十七、表单提交时,推荐怎么写?
一个比较标准的新手写法是:
<template>
<form @submit.prevent="handleSubmit">
<input v-model="username" placeholder="请输入用户名" />
<input v-model="password" type="password" placeholder="请输入密码" />
<button type="submit">登录</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const password = ref('')
const handleSubmit = () => {
console.log('用户名:', username.value)
console.log('密码:', password.value)
}
</script>
这个写法有几个优点:
-
数据来源清晰
-
表单行为可控
-
不会刷新页面
-
后面扩展校验逻辑也方便
这是你以后写登录框、搜索框、录入表单时的常见基础模板。
十八、一个综合案例:搜索框 + 动态样式 + 回车触发
下面给你一个综合点的例子,把这一篇的核心内容串起来。
<template>
<div class="search-box">
<input
v-model="keyword"
@keyup.enter="search"
:class="{ active: keyword.length > 0 }"
placeholder="请输入搜索关键词"
/>
<button @click="search" :style="{ backgroundColor: keyword ? 'skyblue' : '#ccc' }">
搜索
</button>
<p v-if="result">搜索内容:{{ result }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const keyword = ref('')
const result = ref('')
const search = () => {
if (!keyword.value.trim()) return
result.value = keyword.value
}
</script>
<style scoped>
.search-box {
width: 300px;
margin: 20px auto;
}
.active {
border: 2px solid skyblue;
}
</style>
这个例子里已经包含了很多模板进阶能力:
-
v-model绑定输入框 -
@keyup.enter回车触发 -
动态 class 控制输入框激活样式
-
动态 style 控制按钮背景色
-
@click触发搜索 -
v-if控制结果显示
如果你能把这种例子真正看懂并自己敲出来,
那你的模板层能力就已经明显进阶了。
十九、新手最常见的高频错误
这一部分非常重要,因为很多错误都不是"不会",而是细节意识还没建立。
1. class 和 style 都想靠字符串硬拼
比如把所有动态样式都塞进字符串里,写得非常乱。
前期更推荐优先使用:
-
:class="{ active: isActive }" -
:style="{ width: progress + '%' }"
这种结构清晰的写法。
2. 表单提交忘记 .prevent
结果一点击提交,页面直接刷新。
新手写表单时,这几乎是最高频错误之一。
3. 想拿输入值,结果忘了 v-model
本来只想做个简单输入联动,却绕去手动找事件对象。
如果目标只是同步值,直接 v-model 往往更合适。
4. 事件传参后,拿不到事件对象
比如你写:
<button @click="handleClick('张三')">按钮</button>
然后在函数里还想拿 event,结果发现拿不到。
这时候要显式传 $event:
<button @click="handleClick('张三', $event)">按钮</button>
5. 样式逻辑和业务逻辑混在一起太乱
比如模板里一口气写非常复杂的判断表达式。
前期建议尽量把模板写清爽,把复杂逻辑留到脚本区或计算属性里处理。
二十、这一篇学完后,你应该达到什么程度?
如果你把这篇真正理解并上手练过,至少应该能做到:
-
会用
:class动态切换样式 -
会用
:style绑定动态样式值 -
知道什么时候更适合用 class,什么时候更适合用 style
-
会在事件处理中拿到事件对象
-
知道
$event的作用 -
会使用
.stop、.prevent、.enter -
会处理输入框、文本域、下拉框、单选框、复选框
-
会写一个基础但规范的表单提交逻辑
只要这些点打通了,你的 Vue 模板层能力就不再只是"会基础语法",而是开始接近真实页面开发了。
二十一、总结
这一篇文章,我们把 Vue3 模板层中最实用、最容易出问题的一批进阶内容系统讲了一遍。
核心内容可以浓缩成下面几组能力:
-
动态 class:适合做状态切换类样式
-
动态 style:适合做数值驱动类样式
-
事件对象:让你拿到更细的交互信息
-
事件修饰符:让常见事件处理更简洁
-
表单处理:是 Vue3 页面开发里的高频核心场景
如果说前面的模板基础,让你开始"能写一个 Vue 页面",
那么这一篇的作用,就是让你开始学会:
把页面写得更灵活、更规范、更接近真实项目。
这一步非常关键,因为你后面学组件化时,很多组件本质上就是这些模板能力的组合和复用。