文章目录
-
- [一、v-model 双向绑定](#一、v-model 双向绑定)
-
- [1.1 基础作用:快速获取/设置表单值](#1.1 基础作用:快速获取/设置表单值)
- [1.2 v-model 三大修饰符](#1.2 v-model 三大修饰符)
- 二、指令修饰符三大分类
- [三、样式绑定 :class 与 :style](#三、样式绑定 :class 与 :style)
-
- [3.1 :class 动态类名绑定](#3.1 :class 动态类名绑定)
- [3.2 :style 行内样式绑定](#3.2 :style 行内样式绑定)
- [四、计算属性 computed](#四、计算属性 computed)
-
- [4.1 基础简写(只读)](#4.1 基础简写(只读))
- [4.2 完整写法 get + set(可读写)](#4.2 完整写法 get + set(可读写))
- [4.3 computed vs 普通函数](#4.3 computed vs 普通函数)
- [五、侦听器 watch](#五、侦听器 watch)
-
- [5.1 基础语法](#5.1 基础语法)
- [watch vs computed 速记表](#watch vs computed 速记表)
- 六、易混点汇总
- 七、结尾
-
- [7.1 全文总结](#7.1 全文总结)
- [7.2 核心知识点复盘](#7.2 核心知识点复盘)
- [7.3 常见问题 / 避坑指南](#7.3 常见问题 / 避坑指南)
阅读指引 :本文围绕 Vue3 中最核心的五个知识点------
v-model双向绑定、指令修饰符、:class/:style样式绑定、computed计算属性、watch侦听器------展开系统讲解。全文按"概念 → 原理 → 实战 → 避坑"四层递进,适合初学者体系化学习,也适合有经验的同学查漏补缺。
一、v-model 双向绑定
1.1 基础作用:快速获取/设置表单值
v-model 是 Vue 中最常用的指令之一,它能在表单控件(<input>、<textarea>、<select>、<radio>、<checkbox>)上同时完成两件事:
- 将响应式数据的值绑定到表单元素(数据 → 视图)
- 监听用户的输入回写到响应式数据(视图 → 数据)
这就是"双向绑定"------数据和视图之间任意一方变化,另一方自动同步。
本质:语法糖
vue
<input v-model="msg" />
本质上等价于:
vue
<input :value="msg" @input="msg = $event.target.value" />
面试重点 :
v-model=:value+@input的语法糖。不同的表单元素绑定的属性和事件有所不同(如 checkbox 绑定的是checked而非value),但思想一致。
不同表单元素的使用方式
下表总结了各类表单元素与 v-model 的配合方式:
| 表单元素 | v-model 绑定内容 | 注意事项 |
|---|---|---|
<input type="text"> / <textarea> |
绑定 value(字符串) |
最基础用法,输入即同步 |
<select> |
绑定选中项的 value |
<option> 必须有 value 属性 |
<input type="radio"> |
绑定选中项的 value |
必须手动添加 value 属性,同一组 radio 绑定同一个变量 |
单个 <input type="checkbox"> |
绑定布尔值,关联 checked |
用于"同意协议"等开关场景 |
多个 <input type="checkbox"> |
绑定数组,关联 value |
选中的 value 会 push 进数组,取消选中会移除 |
完整实战代码
html
<script setup>
import { ref } from 'vue'
// textarea:自我介绍
const intro = ref('')
// select:城市选择(默认上海)
const city = ref('SH')
// radio:血型(默认 ab 型),必须手动添加 value 属性
const blood = ref('ab')
// 单个 checkbox:是否同意协议(布尔值)
const isAgree = ref(false)
// 多个 checkbox:爱好收集(数组,默认已选中'足球'和'跑步')
const hobby = ref(['ZQ', 'PB'])
</script>
<template>
<div>
<!-- 文本域:绑定 value -->
<textarea
v-model="intro"
cols="30"
rows="4"
placeholder="请输入自我介绍"
></textarea>
<!-- 下拉菜单:绑定选中项的 value -->
<select v-model="city">
<option value="BJ">北京</option>
<option value="SH">上海</option>
<option value="SZ">深圳</option>
<option value="HZ">杭州</option>
</select>
<!-- 单选框:同一组绑定同一个变量,各自需要 value -->
<input type="radio" value="a" v-model="blood" />A
<input type="radio" value="b" v-model="blood" />B
<input type="radio" value="ab" v-model="blood" />AB
<input type="radio" value="o" v-model="blood" />O
<!-- 单个复选框:绑定布尔值 -->
<input type="checkbox" v-model="isAgree" />是否同意用户协议
<!-- 多个复选框:绑定同一个数组 -->
<input v-model="hobby" type="checkbox" value="LQ" />篮球
<input v-model="hobby" type="checkbox" value="ZQ" />足球
<input v-model="hobby" type="checkbox" value="YMQ" />羽毛球
<input v-model="hobby" type="checkbox" value="PB" />跑步
</div>
</template>
代码思路解析
ref(''):声明响应式字符串,作为表单控件的"数据容器"。- radio 的
value属性 :这是高频易错点------忘了加value,Vue 不知道每个选项代表什么值,导致绑定异常。 - 多选 checkbox 绑定数组 :Vue 内部会根据"选中/取消"自动向数组
push或splice对应的value。
1.2 v-model 三大修饰符
修饰符是 Vue 提供的"语法糖后缀",以 . 连接在指令后面,用于改变默认行为。
vue
<input v-model.trim="msg" /> <!-- 自动去除首尾空格 -->
<input v-model.number="num" /> <!-- 尝试转为数字类型 -->
<input v-model.lazy="msg" /> <!-- 失去焦点才同步,不再实时更新 -->
| 修饰符 | 作用 | 底层原理 | 典型场景 |
|---|---|---|---|
.trim |
自动去除用户输入的首尾空格 | 内部对 $event.target.value 调用 .trim() |
用户名、搜索关键字输入 |
.number |
自动将字符串转为数字 | 内部调用 parseFloat(),转换失败则保留原值 |
价格、数量、年龄等数字输入 |
.lazy |
不再实时同步,改为 change 事件触发(失去焦点时) |
底层将 @input 换成 @change |
表单内容较长、需要减少更新频率的场景 |
实战代码
html
<script setup>
import { reactive } from 'vue'
const goods = reactive({
name: '',
price: ''
})
</script>
<template>
<div>
<!-- .trim 修饰符:自动去首尾空格 -->
名称:<input type="text" v-model.trim="goods.name" />
<!-- .number 修饰符:自动转为数字 -->
价格:<input type="text" v-model.number="goods.price" />
</div>
</template>
⚠️ 注意 :
.number修饰符只在用户输入以数字开头 时有效。如果输入"abc123",parseFloat返回NaN,Vue 会保留原始字符串"abc123"。
二、指令修饰符三大分类
Vue 的指令修饰符可以归纳为三大类,它们都以 . 形式连接在指令后面:
| 分类 | 常用修饰符 | 配合指令 | 作用 |
|---|---|---|---|
| 按键修饰符 | .enter、.esc、.delete、.space 等 |
@keydown / @keyup |
限定仅特定按键才触发事件 |
| 事件修饰符 | .prevent、.stop、.once、.self |
@click / @submit 等 |
阻止默认行为、阻止冒泡、只触发一次等 |
| v-model 修饰符 | .trim、.number、.lazy |
v-model |
改变双向绑定的数据处理行为 |
按键修饰符示例
html
<!-- 仅当按下 Enter 键时才触发 onKeyDown 函数 -->
<input type="text" @keydown.enter="onKeyDown" />
常用内置按键别名:.enter、.tab、.delete、.esc、.space、.up、.down、.left、.right。
事件修饰符示例
html
<script setup>
const onDivClick = () => console.log('div 被点击')
const onPClick = () => console.log('p 被点击')
</script>
<template>
<div @click="onDivClick">
<!-- .prevent:阻止 <a> 标签的默认跳转行为 -->
<a href="https://baidu.com" @click.prevent>百度一下</a>
<!-- .stop:阻止事件冒泡,点击 p 不会触发 div 的点击事件 -->
<p @click.stop="onPClick">点我不会冒泡到外层 div</p>
<!-- 修饰符链式调用:同时阻止冒泡和默认行为 -->
<a href="https://baidu.com" @click.stop.prevent>百度一下</a>
</div>
</template>
记忆口诀:按键修饰符管"按了什么键",事件修饰符管"事件怎么传",v-model 修饰符管"数据怎么处理"。
三、样式绑定 :class 与 :style
Vue 中控制元素样式有两种推荐方式::class 动态类名绑定(声明式,最常用)和 :style 行内样式绑定(适合动态计算的值)。
3.1 :class 动态类名绑定
:class 的核心思想是------通过 JS 数据来控制 CSS 类名的有无 ,而非手动操作 DOM。
写法一:三元表达式
html
<template>
<!-- flag 为 true 时添加 active,否则添加 default -->
<div :class="flag ? 'active' : 'default'">三元表达式写法</div>
</template>
适用场景:两组类名二选一。
写法二:对象写法(最常用,推荐)
html
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
</script>
<template>
<!-- 对象语法:键 = 类名,值 = 布尔值;true 就加,false 就去掉 -->
<p :class="{ active: isActive }">对象写法</p>
<!-- 可以同时绑定多个类名 -->
<p :class="{ active: isActive, bold: hasBold, 'text-large': isLarge }">
多类名绑定
</p>
<!-- 静态 class 与动态 :class 可以共存,二者自动合并 -->
<p class="item" :class="{ active: isActive }">
静态 + 动态共存
</p>
</template>
<style scoped>
.active {
color: red;
}
</style>
规则速记 :
{ 类名: 布尔条件 }。条件为true→ 添加类名;false→ 移除类名。
实战案例:可折叠面板的边框控制
html
<script setup>
import { ref } from 'vue'
const visible = ref(true)
</script>
<template>
<div class="panel">
<!-- 面板展开时去掉底部边框,收起时显示边框 -->
<div class="title" :style="{ borderBottomColor: visible ? 'transparent' : '#ccc' }">
<h4>自由与爱情</h4>
<span class="btn" @click="visible = !visible">
{{ visible ? '收起' : '展开' }}
</span>
</div>
<div class="container" v-show="visible">
<p>生命诚可贵,</p>
<p>爱情价更高。</p>
<p>若为自由故,</p>
<p>两者皆可抛。</p>
</div>
</div>
</template>
这个案例中,
:class和:style配合使用,点击按钮切换visible,面板内容显隐 + 边框颜色同步联动。
3.2 :style 行内样式绑定
:style 适合需要动态计算精确样式值的场景(如拖动定位、进度条宽度等)。
写法一:直接在模板中写对象
html
<script setup>
import { ref } from 'vue'
const colorStr = ref('red')
const bgColor = ref('pink')
</script>
<template>
<!-- 直接写样式对象,属性用小驼峰 -->
<p :style="{ color: colorStr, background: bgColor }">你好</p>
</template>
写法二:在 JS 中定义样式对象,模板中引用
html
<script setup>
import { reactive } from 'vue'
const styleObj = reactive({
color: 'blue',
background: 'yellowgreen'
})
</script>
<template>
<!-- 模板直接引用 JS 中定义好的样式对象 -->
<p :style="styleObj">大梦想</p>
</template>
⚠️ 关键规则 :CSS 属性名必须改成小驼峰命名 (camelCase)。
font-size→fontSize,background-color→backgroundColor。原因是 Vue 的样式绑定本质上是 JS 对象,JS 对象的 key 中不能包含连字符-。
四、计算属性 computed
计算属性是 Vue 响应式系统中最高频使用的 API 之一 。它的核心价值在于------基于已有响应式数据,通过同步计算派生出新数据,并且自带缓存。
4.1 基础简写(只读)
html
<script setup>
import { computed, ref } from 'vue'
// 商品列表(原始数据)
const goodsList = ref([
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '书籍', num: 2 }
])
// 基于 goodsList 计算得到总数量
// goodsList 是 totalNum 的"依赖"------goodsList 不变,totalNum 也不会重新计算
const totalNum = computed(() => {
return goodsList.value.reduce((prev, item) => prev + item.num, 0)
})
</script>
<template>
<h3>礼物清单</h3>
<table>
<thead>
<tr><th>名字</th><th>数量</th></tr>
</thead>
<tbody>
<tr v-for="item in goodsList" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}</td>
</tr>
</tbody>
</table>
<!-- 模板中直接使用计算属性,注意不需要 .value -->
<p>礼物总数:{{ totalNum }} 个</p>
</template>
代码思路解析
- 原始数据 :
goodsList只有每个商品的num,没有"总数"。 - 计算逻辑 :使用
reduce累加所有item.num。 - 结果使用 :模板中
{``{ totalNum }}(不用.value,模板自动解包);JS 中读取必须写totalNum.value。
✅ 缓存特性(面试高频):依赖不变,多次使用只执行一次计算函数。依赖改变,才会重新执行。这就是 computed 和普通函数的核心差异。
4.2 完整写法 get + set(可读写)
简写形式的 computed 只能读取,不能赋值 。当需要"既能读也能写"的计算属性时,使用 get + set 完整写法。
html
<script setup>
import { computed, ref } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 完整写法:get = 读取时触发,set = 赋值时触发
const fullName = computed({
get() {
// 获取计算属性值时触发,必须 return
return firstName.value + lastName.value
},
set(val) {
// 给计算属性赋值时触发,val 是赋予的新值
// 例如 fullName.value = '李四',则 val = '李四'
firstName.value = val[0] // '李'
lastName.value = val.slice(1) // '四'
}
})
</script>
使用规则 :
get必须return值;set接收一个参数val,代表赋过来的新值。
实战案例:全选 / 反选
这是一个经典的计算属性 get + set 实战场景------全选复选框的状态由所有子项推导,同时点击全选又需要修改所有子项:
html
<script setup>
import { computed, ref } from 'vue'
const planList = ref([
{ id: 12, name: '跑步', done: false },
{ id: 76, name: '看书', done: false },
{ id: 31, name: '撸码', done: false },
{ id: 49, name: '追剧', done: false }
])
// 全选状态 ------ 一个"可读写"的计算属性
const isAll = computed({
get() {
// 自动推导:所有子项 done === true 时,全选为 true
return planList.value.every((item) => item.done)
},
set(val) {
// 点击全选时,val 是复选框的新状态(true/false)
// 将该状态同步到每一个子项
planList.value.forEach((item) => (item.done = val))
}
})
// 反选:遍历取反即可
const onToggle = () => {
planList.value.forEach((item) => (item.done = !item.done))
}
</script>
<template>
<p>
<span>
<input v-model="isAll" type="checkbox" id="all" />
<label for="all">全选</label>
</span>
<button @click="onToggle">反选</button>
</p>
<ul>
<li v-for="item in planList" :key="item.id">
<input type="checkbox" v-model="item.done" />
<span :class="{ completed: item.done }">{{ item.name }}</span>
</li>
</ul>
</template>
代码思路解析
get():用Array.every()检测是否所有项的done都为true。全部打钩 → 全选自动勾上;任意一个取消 → 全选自动取消。set(val):点击全选复选框时触发,val是新的选中状态(布尔值)。用forEach把每个子项的done设成和val一样。- 为什么用 computed 而不是 watch? :因为"全选状态"是"所有子项状态"的同步推导结果,不需要异步操作,正适合 computed 的缓存 + 声明式写法。
4.3 computed vs 普通函数
这是面试的超级高频考点。两者看起来都能"计算数据",但底层行为完全不同:
| 对比维度 | computed(计算属性) | 普通函数 |
|---|---|---|
| 缓存机制 | ✅ 有缓存,依赖不变只执行 1 次 | ❌ 无缓存,每次调用都重新执行 |
| 触发时机 | 依赖变化时自动重新计算 | 每次渲染都执行(无论依赖是否变化) |
| 使用方式 | 模板中 {``{ total }}(不加括号) |
模板中 {``{ calcTotal() }}(必须加括号) |
| 赋值能力 | 简写只读;完整写法可读可写 | 只能返回结果,不能"被赋值" |
| 异步操作 | ❌ 不能写异步请求 | ✅ 可以写异步代码 |
| 适用场景 | 数据推导、格式转换、列表过滤/统计 | 事件处理、异步请求、任意 JS 逻辑 |
缓存效果验证代码
html
<script setup>
import { computed, ref } from 'vue'
const goodsList = ref([
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '书籍', num: 2 }
])
const totalNum = computed(() => {
console.log('1-computed 执行了') // 依赖不变,只打印 1 次
return goodsList.value.reduce((prev, item) => prev + item.num, 0)
})
const calcTotalNum = () => {
console.log('2-function 执行了') // 每次渲染都打印,这里 3 次
return goodsList.value.reduce((prev, item) => prev + item.num, 0)
}
</script>
<template>
<!-- 3 次使用 computed → 只计算 1 次 -->
<p>礼物总数:{{ totalNum }} 个</p>
<p>礼物总数:{{ totalNum }} 个</p>
<p>礼物总数:{{ totalNum }} 个</p>
<hr />
<!-- 3 次调用函数 → 执行 3 次 -->
<p>礼物总数:{{ calcTotalNum() }} 个</p>
<p>礼物总数:{{ calcTotalNum() }} 个</p>
<p>礼物总数:{{ calcTotalNum() }} 个</p>
</template>
控制台输出:
1-computed 执行了只打印 1 次,而2-function 执行了打印 3 次。在大量数据或复杂计算场景下,computed 的性能优势会非常明显。
五、侦听器 watch
watch 和 computed 是 Vue 响应式系统的"双子星"------前者负责 "当数据变化时,我要做什么" (副作用),后者负责 "基于已有数据,计算出新数据"(派生)。
5.1 基础语法
js
import { watch } from 'vue'
watch(数据源, (newVal, oldVal) => {
// newVal:变化后的新值
// oldVal:变化前的旧值
// 在此执行异步操作、DOM 操作、复杂逻辑...
})
适用场景
- 数据变化后发异步接口请求(如搜索框输入关键词调后端搜索)
- 数据变化后操作 DOM(如滚动到指定位置)
- 数据变化后设置定时器、操作 localStorage 等副作用
实战代码:监听搜索关键词 + 监听响应式对象
html
<script setup>
import { reactive, ref, watch } from 'vue'
// 1. 监听基本类型 ref
const keyword = ref('')
watch(keyword, (newVal, oldVal) => {
console.log('旧值:', oldVal, '→ 新值:', newVal)
// 实际场景:这里发送搜索请求
// fetch(`/api/search?q=${newVal}`)
})
// 2. 监听 reactive 响应式对象
const obj = reactive({
username: '',
password: ''
})
watch(obj, (newVal, oldVal) => {
console.log(newVal, oldVal)
// ⚠️注意:监听 reactive 对象时,newVal 和 oldVal 指向同一个对象引用
console.log(newVal === oldVal) // true
console.log(newVal === obj) // true
})
</script>
<template>
<div>
<input type="text" v-model="keyword" placeholder="请输入关键字" />
<input type="text" v-model="obj.username" placeholder="请输入用户名" />
<input type="text" v-model="obj.password" placeholder="请输入密码" />
</div>
</template>
代码思路解析
- 监听
ref:watch(keyword, callback),newVal和oldVal是不同的值,正常使用。 - 监听
reactive对象 :newVal === oldVal结果为true------两者指向同一个 Proxy 对象 。这是因为 reactive 返回的是代理对象,其引用地址不变。如需获取旧值,需要手动深拷贝或用watch的getter函数形式。 - 为什么不用
computed? :因为"搜索"是异步副作用 (发请求),不是"同步计算出一个新值"。这就是watch和computed的本质分野。
watch vs computed 速记表
| 维度 | computed | watch |
|---|---|---|
| 本质 | 声明一个"派生值" | 监听变化 + 执行"副作用" |
| 缓存 | ✅ 有缓存 | ❌ 无缓存 |
| 异步 | ❌ 不支持 | ✅ 核心使用场景 |
| return | 必须 return | 不需要 return |
| 使用方式 | 模板中 {``{ xxx }} / JS 中 xxx.value |
在回调中写任意逻辑 |
六、易混点汇总
以下 6 条是学习和面试中最容易被问到的易混点,建议重点关注:
-
v-model.lazy= 取消实时同步,失去焦点才更新- 等价于把内部的
@input事件换成了@change事件。适用于表单内容长、不需要实时更新的场景。
- 等价于把内部的
-
@input是原生 DOM 事件,watch是 Vue 响应式 API@input绑定在 DOM 元素上,由浏览器触发;watch监听的是响应式变量的变化,由 Vue 响应式系统触发。- 两者可以实现相似效果,但
@input更适合做输入时的即时交互,watch更适合做数据变化后的异步处理。
-
<form @submit.prevent>用来拦截表单默认跳转- 原生
<form>提交后浏览器会刷新页面。在 SPA 单页应用中,必须用.prevent阻止这个行为,改用 JS(axios/fetch)发送 AJAX 请求。
- 原生
-
:class对象语法:{ 类名: 布尔条件 };:style属性必须用小驼峰:class="{ active: isActive }"------键是要加的类名,值是控制加/去的布尔条件。:style="{ fontSize: '20px' }"------不能写font-size,JS 对象 key 不允许含连字符。
-
computed一定要return;简写形式只读;需要set才能赋值get()是读取入口,必须有return。- 简写形式只定义了
get,相当于只读。 - 完整写法通过
set(val)接收新值,实现双向可读写。
-
computed有缓存,普通函数没有缓存;大量重复渲染优先用computed- 这是两者最本质的区别。
- 模板中引用 3 次 computed → 计算函数只执行 1 次;引用 3 次函数 → 函数执行 3 次。
七、结尾
7.1 全文总结
本文系统讲解了 Vue3 中五个核心知识点,它们之间层层递进、互相配合:
v-model是连接视图和数据的桥梁,本质是:value+@input的语法糖,配合.trim/.number/.lazy修饰符可灵活控制数据处理行为。- 指令修饰符 分为按键、事件、v-model 三类,以
.连接,是 Vue 模板语法中"花小力气办大事"的设计。 :class/:style通过 JS 数据驱动样式,替代了手动 DOM 操作,:class对象写法最常用。computed用同步派生数据,自带缓存,是性能优化的利器。简写只读,get + set可读写。watch监听数据变化后执行异步副作用,和computed形成互补。@submit.prevent是表单提交的标准姿势------阻止默认刷新 + JS 异步提交。
整体而言,"双向绑定处理输入 → 修饰符精准控制行为 → 计算属性派生新数据 → 侦听器处理副作用"形成了一条完整的 Vue 响应式数据处理流水线,掌握这五个知识点,日常开发中的绝大多数数据流转场景都能从容应对。
7.2 核心知识点复盘
| 知识点 | 一句话核心 | 权重 |
|---|---|---|
v-model |
:value + @input 语法糖,表单控件双向绑定 |
⭐⭐⭐⭐⭐ |
.trim / .number / .lazy |
v-model 的三个"微调"修饰符 | ⭐⭐⭐ |
@submit.prevent |
阻止表单默认刷新,实现 AJAX 提交 | ⭐⭐⭐⭐ |
| 按键修饰符 | 限定特定按键触发事件(如 .enter) |
⭐⭐ |
事件修饰符 .stop / .prevent |
阻止冒泡 / 阻止默认行为 | ⭐⭐⭐ |
:class 对象语法 |
{ 类名: 布尔条件 },true 加 false 去 |
⭐⭐⭐⭐ |
:style 小驼峰 |
CSS 属性改写为 camelCase | ⭐⭐ |
computed 简写 |
基于依赖同步派生新值,自带缓存 | ⭐⭐⭐⭐⭐ |
computed 完整写法 |
get() 读 + set(val) 写,可双向绑定 |
⭐⭐⭐⭐ |
computed vs 函数 |
有缓存 vs 无缓存,这是本质区别 | ⭐⭐⭐⭐⭐ |
watch |
监听数据变化,执行异步副作用 | ⭐⭐⭐⭐ |
watch vs computed |
副作用 vs 派生值;无缓存 vs 有缓存 | ⭐⭐⭐⭐⭐ |
7.3 常见问题 / 避坑指南
Q1:v-model 绑定 radio 没反应?
✅ 检查是否给每个
<input type="radio">手动添加了value属性。radio 的 value 不是浏览器自带的,必须显式声明。
Q2:.number 修饰符输入 "abc" 后得到的还是 "abc"?
✅ 这是预期行为。
.number内部用parseFloat(),转换失败时会保留原始字符串。如果需要严格校验,建议配合表单验证库(如 VeeValidate)。
Q3:为什么 watch 监听 reactive 对象时 newVal === oldVal 为 true?
✅ 因为 reactive 返回的是 Proxy 代理对象,对象内容变了但引用地址没变。如果需要拿到旧值的"快照",使用 getter 函数写法:
watch(() => ({ ...obj }), callback)先做浅拷贝。
Q4:computed 里面可以写异步吗?
✅ 不可以 。computed 内部必须是同步计算逻辑。需要异步操作(如请求后端数据)的场景,请用
watch或直接在方法中处理。
Q5:什么时候用 computed,什么时候用 watch?
✅ 核心判断标准:"我是在派生一个新值,还是在对变化做出反应?" 如果是前者 → computed;如果是后者(发请求、操作 DOM、设定时器)→ watch。
Q6:模板中引用 computed 需要 .value 吗?
✅ **不需要。**模板中 Vue 会自动对 ref 和 computed 做"解包",直接
{``{ totalNum }}即可。但在<script setup>的 JS 代码中,必须写totalNum.value。
Q7:@submit 表单内没有 type="submit" 按钮,按回车能提交吗?
✅ 能。当表单内只有一个文本输入框时,按回车会触发表单提交。多个输入框时,建议显式放置
<button type="submit">确保行为可控。
本文到此结束。 如果你正在学习 Vue3,建议将文中的每个代码示例亲自运行一遍,边看边改,加深理解。文中所有代码均为完整可运行的独立
.vue单文件组件,复制到 Vite + Vue3 项目中即可直接使用。Happy Coding!