Vue3 表单双向绑定与响应式核心 API 技术详解

文章目录

    • [一、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>)上同时完成两件事

  1. 将响应式数据的值绑定到表单元素(数据 → 视图)
  2. 监听用户的输入回写到响应式数据(视图 → 数据)

这就是"双向绑定"------数据和视图之间任意一方变化,另一方自动同步。

本质:语法糖

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 内部会根据"选中/取消"自动向数组 pushsplice 对应的 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-sizefontSizebackground-colorbackgroundColor。原因是 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>

代码思路解析

  1. 原始数据goodsList 只有每个商品的 num,没有"总数"。
  2. 计算逻辑 :使用 reduce 累加所有 item.num
  3. 结果使用 :模板中 {``{ 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

watchcomputed 是 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>

代码思路解析

  • 监听 refwatch(keyword, callback)newValoldVal 是不同的值,正常使用。
  • 监听 reactive 对象newVal === oldVal 结果为 true------两者指向同一个 Proxy 对象 。这是因为 reactive 返回的是代理对象,其引用地址不变。如需获取旧值,需要手动深拷贝或用 watchgetter 函数形式。
  • 为什么不用 computed :因为"搜索"是异步副作用 (发请求),不是"同步计算出一个新值"。这就是 watchcomputed 的本质分野。

watch vs computed 速记表

维度 computed watch
本质 声明一个"派生值" 监听变化 + 执行"副作用"
缓存 ✅ 有缓存 ❌ 无缓存
异步 ❌ 不支持 ✅ 核心使用场景
return 必须 return 不需要 return
使用方式 模板中 {``{ xxx }} / JS 中 xxx.value 在回调中写任意逻辑

六、易混点汇总

以下 6 条是学习和面试中最容易被问到的易混点,建议重点关注:

  1. v-model.lazy = 取消实时同步,失去焦点才更新

    • 等价于把内部的 @input 事件换成了 @change 事件。适用于表单内容长、不需要实时更新的场景。
  2. @input 是原生 DOM 事件,watch 是 Vue 响应式 API

    • @input 绑定在 DOM 元素上,由浏览器触发;watch 监听的是响应式变量的变化,由 Vue 响应式系统触发。
    • 两者可以实现相似效果,但 @input 更适合做输入时的即时交互,watch 更适合做数据变化后的异步处理。
  3. <form @submit.prevent> 用来拦截表单默认跳转

    • 原生 <form> 提交后浏览器会刷新页面。在 SPA 单页应用中,必须用 .prevent 阻止这个行为,改用 JS(axios/fetch)发送 AJAX 请求。
  4. :class 对象语法:{ 类名: 布尔条件 }:style 属性必须用小驼峰

    • :class="{ active: isActive }"------键是要加的类名,值是控制加/去的布尔条件。
    • :style="{ fontSize: '20px' }"------不能写 font-size,JS 对象 key 不允许含连字符。
  5. computed 一定要 return;简写形式只读;需要 set 才能赋值

    • get() 是读取入口,必须有 return
    • 简写形式只定义了 get,相当于只读。
    • 完整写法通过 set(val) 接收新值,实现双向可读写。
  6. 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 === oldValtrue

✅ 因为 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!

相关推荐
郝亚军2 小时前
使用Vue 3和Nginx打包和部署Vue.js项目的一般步骤
前端·vue.js·nginx
SmartBoyW2 小时前
CSS弹性布局(Flexbox)学习笔记:从零开始搞懂弹性布局
前端·javascript
用户69371750013842 小时前
从代码生产者到 AI 协作者:软件工程师的角色重构
android·前端·后端
嘟嘟07173 小时前
搞懂 useState 从原生 DOM 到 React 惰性初始化的完整链路
前端·react.js·编程语言
牧艺3 小时前
cos-design RippleWater & SmokeFog:水面涟漪与烟雾雾气怎么做
前端·canvas·视觉设计
用户938515635073 小时前
写了这么久 useState,你真的知道它在干什么吗?
前端·javascript
慢功夫3 小时前
💡第七篇:VSCode语言服务中,代码跳转和诊断是怎么做的?
前端·visual studio code
Csvn3 小时前
✨ TypeScript `satisfies` 操作符实战——5 个让代码更「聪明」的生产场景
前端
Healer9183 小时前
vue3页面缓存-keepAlive
前端