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 内部会根据"选中/取消"自动向数组 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>

代码思路解析

  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

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 条是学习和面试中最容易被问到的易混点,建议重点关注:

  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 === 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!

相关推荐
deli00710 分钟前
1 个汉字算几个 token?实测中英文 prompt 与代码的编码差异
前端
无限聚核17 分钟前
抖音 certificate.prepare 之后怎么验券?和美团 msconsume 差在哪,无限聚核
前端·微信小程序
2603_9658966226 分钟前
Grid 网格布局|CSS 二维页面排版体系
前端·css
有味道的男人42 分钟前
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案
服务器·前端·javascript
lovingsoft1 小时前
Chrome Lighthouse 详解
前端·chrome
Qlly2 小时前
全栈工程师怎么接需求?从一句「加个功能」聊到能开发、能验收
前端·后端
马可家的菠萝2 小时前
你的微信“文件传输助手”,是不是已经成了第二个网盘?
前端·后端·架构
略略略啦2 小时前
Vibe Coding 基于Spec DD和TDD的实践指南
前端
shmily麻瓜小菜鸡2 小时前
前端实现图片与PDF文字识别敏感信息打码方案
前端·pdf·状态模式
打工仔折腾 AI2 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战