第6次 · 事件处理与表单双向绑定:v-on 与 v-model
第6次 · 事件处理与表单双向绑定:v-on 与 v-model
第1/7页 · 先知道今天要做什么
到上一课为止,页面只会“看”,不会“动”——所有数据都是你在代码里写好的。今天补上另一半:用户点一下、输一行、勾一个框,数据就跟着变。这件事靠两个东西完成:v-on(也叫 @)负责“听事件”,v-model 负责“双向绑定”。
v-model 是 Vue 里性价比最高的一个语法糖:一行代码顶掉“读值 + 监听输入 + 写回数据”三件事。今天要把它在文本框、下拉框、单选、多选、开关上的用法全部过一遍,这是后面所有表单类页面的地基。
今天的场景
一个“课程报名表”。用户填姓名、手机号、班级、报名方向(可多选)、期望课时、是否接收通知。你在旁边实时把收集到的数据以 JSON 形式打印出来。这样每次输入,你都能立刻看到“数据到底变成了什么”,而不是靠猜。
下课时拿出这三样结果
| 做到什么 | 在哪里看 | 完成的证据 |
|---|---|---|
会用 v-on / @ 绑定事件 |
按钮与表单 | 点按钮能执行函数,函数里能拿到事件对象,也能收到自定义参数。 |
会用 v-model 处理各种表单控件 |
报名表 | 输入框、下拉、单选、多选、开关都能改到数据,且右侧 JSON 实时同步。 |
| 会用三个常用修饰符 | 输入与提交 | .trim 去掉首尾空格、.number 把输入转成数字、.lazy 让数据在失焦时才更新。 |
你只需要带着这些基础来
会 ref、computed、v-if、v-for;第 5 课的工程还能跑。
本课时 45 分钟怎么上
| 时间 | 老师带着做 | 学生留下什么 |
|---|---|---|
| 0—5 分钟 | 提问:不用 v-model,怎么把一个输入框的值存下来? |
明白“取事件 → 读 value → 写回变量”三步很啰嗦。 |
| 5—18 分钟 | 讲 v-on:绑定方式、传参、事件对象、常用修饰符。 |
笔记里写下 .stop 和 .prevent 各挡什么。 |
| 18—34 分钟 | 讲 v-model:在各类控件上的写法,多选框为什么绑数组,三个修饰符。 |
能说出 v-model 展开成哪两件事。 |
| 34—42 分钟 | 粘贴本课 App.vue,逐个控件操作,看右侧 JSON 变化。 |
所有控件都能改到数据。 |
| 42—45 分钟 | 抽查:问“多选框为什么必须绑数组”。 | 全员提交。 |
从哪里开始
复制第 5 课的工程,改名 lesson-vue-06。不需要新增依赖。
今天先掌握到这里
今天不拆分组件,所有逻辑都在一个 App.vue 里。props / emits 是第 7 课的内容。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档
第2/7页 · 把新词说清楚
新词与本课含义
| 名字 | 用熟悉的话理解 | 本课里长什么样 |
|---|---|---|
v-on |
给元素挂一个“监听器”,事件发生时执行一段代码。 | v-on:click="fn",简写 @click="fn" |
| 事件对象 | 浏览器传给你的“这次事件的详情”:点在哪、按了哪个键。 | @click="fn($event)" 里的 $event |
| 修饰符 | 挂在指令后面的小后缀,用来免写常见的 preventDefault、stopPropagation。 |
@click.stop、@submit.prevent |
v-model |
双向绑定:数据 → 界面,界面 → 数据,自动来回。 | <el-input v-model="form.name" /> |
| 语法糖 | 看起来是一个写法,实际上会被翻译成几行代码。 | v-model 会被展开成“绑定值 + 监听更新” |
| 受控 / 非受控 | 界面上的值由数据说了算(受控),还是由浏览器自己管(非受控)。 | v-model 就是让控件“受数据控制” |
v-on 的四种常见写法
<!-- 1. 直接写一句表达式 -->
<button @click="count++">加一</button>
<!-- 2. 调一个函数(不传参) -->
<button @click="reset">重置</button>
<!-- 3. 调函数并传参 -->
<button @click="say('你好')">打招呼</button>
<!-- 4. 既要自定义参数,又要事件对象:用 $event 显式传进去 -->
<button @click="say('你好', $event)">打招呼并看事件</button>
一个必须记住的细节:写 @click="say('你好')" 时括号里的内容在事件发生时才会执行。而如果你写 @click="say"(不带括号),Vue 会把函数本身交给它,事件发生时自动调用并自动把事件对象作为第一个参数传进去。
常用事件修饰符
| 修饰符 | 作用 | 典型场景 |
|---|---|---|
.stop |
阻止事件继续向上冒泡 | 卡片整体可点击,里面的按钮不想触发卡片 |
.prevent |
阻止浏览器默认行为 | 表单提交时不让页面刷新 |
.once |
只触发一次 | “立即领取”按钮 |
.self |
只有点在元素自身上才触发 | 点击遮罩关闭弹窗,但点弹窗内部不关 |
.passive |
提升滚动流畅度,告诉浏览器“我不会阻止默认行为” | 滚动容器 |
v-model 到底做了什么
<input v-model="name" />
等价于:
<input :value="name" @input="name = $event.target.value" />
: 那半句负责“数据 → 界面”,@ 那半句负责“界面 → 数据”。这就是“双向”的来源。理解了这一点,后面组件上的 v-model 就不神秘了。
不同控件,v-model 绑的类型不一样
| 控件 | 绑定的数据类型 | 说明 |
|---|---|---|
| 文本框 / 多行文本 | 字符串 | 默认。 |
| 复选框(单个) | 布尔值 | 勾选是 true。 |
| 复选框(一组) | 数组 | 勾中的那些值组成的数组。 |
| 单选框 | 字符串 / 数字 | 选中项的值。 |
| 下拉框(单选) | 字符串 / 数字 | 选中项的值。 |
| 下拉框(多选) | 数组 | 需要加 multiple。 |
| 开关 | 布尔值 | true / false。 |
| 数字输入框 | 数字 | Element Plus 的 el-input-number 直接给数字。 |
这是最容易出错的地方:多选一定要绑数组,否则 Vue 不知道该往里放什么。
三个常用修饰符
| 修饰符 | 作用 | 例子 |
|---|---|---|
.trim |
自动去掉首尾空格 | <el-input v-model.trim="form.name" /> |
.number |
把输入转成数字类型 | <el-input v-model.number="form.age" /> |
.lazy |
输入时不更新,失去焦点或回车时才更新 | 适合“搜索”这类不想每次按键都请求的输入 |
用 Element Plus 的组件时,这三个修饰符都可以照常写。
只看这几行:绑定与取值的对比
<script setup>
import { ref } from 'vue'
const name = ref('')
const way = 'v-model'
</script>
<template>
<!-- 啰嗦版:手动取、手动写 -->
<input :value="name" @input="name = $event.target.value" />
<!-- 同一件事,v-model 一行搞定 -->
<input v-model="name" />
<p>当前值:{{ name }}</p>
</template>
先口头预测
如果 v-model 绑的是一个数组,而控件是一个普通文本框,会发生什么?
页面能显示(数组会被转成字符串),但一输入就出错或行为异常,因为文本框只能写回字符串,Vue 不会把它变成数组。绑定类型必须和控件匹配,这是本课要养成的第一直觉。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档
第3/7页 · 先看一个小例子
完整代码:App.vue(课程报名表)
<script setup>
import { ref, reactive, computed } from 'vue'
import {
ElCard, ElForm, ElFormItem, ElInput, ElInputNumber, ElSelect, ElOption,
ElRadioGroup, ElRadio, ElCheckboxGroup, ElCheckbox, ElSwitch, ElSlider,
ElButton, ElTag, ElDivider, ElMessage
} from 'element-plus'
import 'element-plus/dist/index.css'
const form = reactive({
name: '',
phone: '',
cls: '',
gender: '保密',
directions: [], // 多选 → 数组
hours: 30, // 数字
notify: true,
remark: ''
})
const directionOptions = ['Vue 前端', 'Java 后端', 'Python 数据分析', '数据库运维', '测试开发']
// 实时预览:把当前数据以 JSON 形式显示出来
const preview = computed(() => JSON.stringify(form, null, 2))
// 用 v-model.trim / v-model.number / v-model.lazy 的示范项
const searchKey = ref('')
const age = ref(null)
const lazyText = ref('')
const canSubmit = computed(() =>
form.name.trim().length >= 2 && /^1\d{10}$/.test(form.phone)
)
function submit() {
if (!canSubmit.value) {
ElMessage.warning('请填写姓名(≥2 字)和 11 位手机号')
return
}
ElMessage.success('报名已提交:' + form.name)
console.log('提交的数据:', JSON.parse(JSON.stringify(form)))
}
function reset() {
form.name = ''
form.phone = ''
form.cls = ''
form.gender = '保密'
form.directions = []
form.hours = 30
form.notify = true
form.remark = ''
ElMessage.info('已重置')
}
</script>
<template>
<div class="page">
<h1 class="title">课程报名表</h1>
<div class="layout">
<!-- 左侧:表单 -->
<el-card class="panel">
<template #header>填写信息(每改一处,右侧 JSON 立刻变化)</template>
<el-form label-width="96px" @submit.prevent>
<el-form-item label="姓名">
<el-input v-model.trim="form.name" placeholder="首尾空格会被自动去掉" clearable />
</el-form-item>
<el-form-item label="手机号">
<el-input v-model.trim="form.phone" placeholder="11 位手机号" maxlength="11" clearable />
</el-form-item>
<el-form-item label="班级">
<el-select v-model="form.cls" placeholder="请选择" style="width: 100%">
<el-option label="计算机1班" value="计算机1班" />
<el-option label="计算机2班" value="计算机2班" />
<el-option label="计算机3班" value="计算机3班" />
</el-select>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender">
<el-radio value="男">男</el-radio>
<el-radio value="女">女</el-radio>
<el-radio value="保密">保密</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="报名方向">
<!-- 多选:必须绑数组 -->
<el-checkbox-group v-model="form.directions">
<el-checkbox v-for="d in directionOptions" :key="d" :value="d">{{ d }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="期望课时">
<el-input-number v-model="form.hours" :min="1" :max="120" />
<el-slider v-model="form.hours" :min="1" :max="120" style="width: 200px; margin-left: 16px" />
</el-form-item>
<el-form-item label="接收通知">
<el-switch v-model="form.notify" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="3" show-word-limit maxlength="100" />
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="!canSubmit" @click="submit">提交报名</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 右侧:实时数据预览 + 修饰符演示 -->
<div class="side">
<el-card class="panel">
<template #header>实时数据(form)</template>
<pre class="json">{{ preview }}</pre>
<el-tag v-if="canSubmit" type="success" size="small">校验通过,可提交</el-tag>
<el-tag v-else type="danger" size="small">姓名或手机号还没填对</el-tag>
</el-card>
<el-card class="panel">
<template #header>三个修饰符的对比实验</template>
<p class="hint">1)<b>.trim</b>:下面输入框首尾打空格,看数值是否被去掉</p>
<el-input v-model.trim="searchKey" placeholder=" 前后加空格试试 " />
<p class="now">当前值:<code>"{{ searchKey }}"</code></p>
<el-divider />
<p class="hint">2)<b>.number</b>:用普通输入框输入数字,看类型是不是数字</p>
<el-input v-model.number="age" placeholder="输入 18" />
<p class="now">值:{{ age }} 类型:<code>{{ typeof age }}</code></p>
<el-divider />
<p class="hint">3)<b>.lazy</b>:输入时不更新,失焦(点别处)或回车才更新</p>
<el-input v-model.lazy="lazyText" placeholder="边打字边看下面变化" />
<p class="now">已同步的值:<code>"{{ lazyText }}"</code></p>
</el-card>
</div>
</div>
</div>
</template>
<style>
html, body, #app {
margin: 0; padding: 0; max-width: none; width: auto; height: auto;
display: block; place-items: initial; text-align: left; background: #f5f7fa;
}
</style>
<style scoped>
.page { max-width: 1120px; margin: 28px auto; padding: 0 16px; }
.title { font-size: 22px; color: #303133; margin: 0 0 18px; }
.layout { display: grid; grid-template-columns: 1fr 380px; gap: 16px; align-items: start; }
.panel { margin-bottom: 16px; }
.json {
background: #1f2430; color: #a9dc76; padding: 12px; border-radius: 6px;
font-size: 12px; line-height: 1.6; margin: 0 0 10px; overflow: auto; max-height: 320px;
}
.hint { font-size: 13px; color: #606266; margin: 0 0 8px; }
.now { font-size: 13px; color: #303133; margin: 8px 0 0; }
code { background: #f0f2f5; padding: 1px 5px; border-radius: 4px; color: #c7254e; }
@media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
</style>
预期看见什么
- 左边一列表单项,右边两张卡片:一张实时显示
form的 JSON,一张做三个修饰符的对比实验。 - 在姓名框里敲字,右侧 JSON 里
name的值同步变化。 - 勾选“报名方向”里的两项,JSON 里
directions变成["Vue 前端", "Java 后端"]这样一个数组。 - 拖动“期望课时”的滑块,数字框和 JSON 一起变。
- 姓名填两个字以上、手机号填 11 位,右下角标签从红灯变绿灯,提交按钮从灰变蓝。
- 在
.lazy那个框里打字,下面的“已同步的值”不动;点一下页面别处,值才一次跳过去。
先猜一猜,读完代码再回答
<el-button type="primary" :disabled="!canSubmit" @click="submit">提交报名</el-button>
<el-form @submit.prevent>
这里为什么写了 @submit.prevent,虽然按钮没写 type="submit"?
因为原生 <form> 在按回车时会尝试提交并刷新整页。Element Plus 的 el-form 内部就是一个真实 <form>,如果用户在输入框里按回车,页面会刷新,所有状态丢失。加上 .prevent 就挡住了这个默认行为——这正是 .prevent 修饰符的标准用途。这也是为什么 .prevent 和 .stop 要背下来。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档
第4/7页 · 一步一步做出来
第1步:先感受没有 v-model 的麻烦
<script setup>
import { ref } from 'vue'
const name = ref('')
</script>
<template>
<input :value="name" @input="e => name = e.target.value" />
<p>你输入了:{{ name }}</p>
</template>
能跑,但要写两件事。预期:功能正常,但代码啰嗦。
第2步:换成 v-model
<input v-model="name" />
预期:行为完全一样,代码少一半。这就是语法糖的价值。
第3步:看清 v-model 展开成了什么
在 <script setup> 里加一句打印:
const name = ref('')
console.log('初始值:', name.value)
然后在输入框里打字,观察页面下面那行文字实时跟着变。这一步的目的是建立“数据变了界面就变、界面变了数据就变”的直觉。
第4步:玩一次事件对象
<button @click="show($event)">点我,看事件对象</button>
<script setup>
function show(e) {
console.log('点在了哪里:', e.target.tagName, e.clientX, e.clientY)
}
</script>
打开 Console,点按钮。预期:能看到事件对象里的坐标、目标元素等信息。
第5步:体验修饰符的差别
先看不加修饰符的情况:
<div class="outer" @click="ElMessage.info('外层被点了')">
<button @click="ElMessage.info('按钮被点了')">点我</button>
</div>
点按钮。预期:两条提示都会弹(按钮的事件冒到了外层)。
改成 <button @click.stop="...">。预期:只弹一条。
第6步:整段替换为本课完整版
把第 3 页的完整 App.vue 覆盖 src/App.vue。逐个控件操作一遍,每操作一次就对照右侧 JSON,确认:
- 文本框 → 字符串
- 多选框组 → 数组
- 开关 → 布尔值
- 数字框 → 数字
本课文件速查
| 位置 | 本次作用 |
|---|---|
form(reactive) |
整个表单的数据源,字段与控件一一对应。 |
form.directions |
唯一一个数组,绑定到 el-checkbox-group。 |
canSubmit |
computed 出的“能否提交”,控制按钮禁用状态。 |
@submit.prevent |
挡住表单的默认刷新行为。 |
.trim / .number / .lazy |
三个修饰符的对比实验区。 |
完成修改后检查
| 操作或输入 | 预期观察 |
|---|---|
在姓名框输入 张三 |
JSON 里存的是 张三(空格被去掉)。 |
| 勾选 3 个报名方向 | directions 是长度为 3 的数组。 |
| 关闭“接收通知” | JSON 里 notify 变成 false。 |
| 姓名只填 1 个字 | 提交按钮是禁用的。 |
在 .lazy 框里打字后按 Tab |
值才更新。 |
| 在任意输入框按回车 | 页面不刷新。 |
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档
第5/7页 · 换成自己的代码
你的任务
- 换成自己的表单:把报名表改成你真实会用到的一张表(例如“图书馆借书登记”或“社团活动报名”),保留至少 6 个字段。
- 多选换成数组:至少保留一处必须绑数组的控件(多选框组或
multiple下拉),并在 JSON 里确认它确实是数组。 - 加一个“实时字数”:备注框下方显示“已输入 X / 100”。
- 加一个联动:当“接收通知”关闭时,把“手机号”输入框设为不可编辑(
el-input上加:disabled="!form.notify"),并在旁边显示原因说明。 - 换一个修饰符实验:把
.lazy那一组换成@keyup.enter的使用——在搜索框里按回车才执行“搜索”,并打印搜索关键字。
按这张操作单完成
| 步骤 | 改哪里 | 运行后应该看到什么 |
|---|---|---|
| 1 换表单 | form 对象与模板 |
6 个自己的字段,JSON 实时同步。 |
| 2 数组绑定 | 多选控件 | JSON 里显示成数组。 |
| 3 实时字数 | 备注下方加一行 | 打字时计数增加,到 100 封顶。 |
| 4 联动禁用 | 手机号 :disabled |
关掉开关后手机号框变灰且不可输入。 |
| 5 回车搜索 | @keyup.enter |
只有按回车才触发搜索打印。 |
最低完成标准:先把这三项做实
| 检查项 | 通过条件 |
|---|---|
| 双向绑定 | 页面上至少 5 个控件能真正改到数据(在 JSON / 预览里能看到变化)。 |
| 类型正确 | 多选是数组、开关是布尔、数字框是数字,能在界面上验证。 |
| 事件与修饰符 | 至少用到一个修饰符(.trim / .number / .lazy / .stop / .prevent / @keyup.enter)。 |
在本地写练习记录
- 写出
v-model展开后的等价写法(:value+@input)。 - 记录三个修饰符各自的实验现象。
- 写下你的表单里哪个字段绑的是数组、为什么。
最后交什么
提交 src/App.vue 与 练习记录.md。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档
第6/7页 · 出错时从哪里查
把现象和检查动作对上
| 看到什么现象 / 报错 | 先检查哪里 | 修好后怎样确认 |
|---|---|---|
| 输入框能打字,但数据没变 | v-model 拼错了(例如写成 v-modle);或绑的是一个不可写的值(如 computed 的只读值)。 |
拼写正确后 JSON 跟着变。 |
| 多选框勾选时报错或值变成字符串 | 绑的不是数组。多选框组必须绑数组。 | 改成数组后正常。 |
| 数字框得到的却是字符串 | 用了 el-input 而不是 el-input-number,或忘了 .number。 |
用 typeof 打印确认类型。 |
| 表单里的回车刷新了页面 | 没写 @submit.prevent。 |
加上后不再刷新。 |
| 点按钮时外层的点击也触发了 | 需要 .stop。 |
加上后只触发一次。 |
.lazy 好像“没反应” |
.lazy 是失焦才更新,这是预期行为。 |
点页面别处或按回车后再看。 |
| 修改表单后页面不更新 | 用 reactive 的对象整体替换了(例如 form = {...}),丢掉了响应性。 |
改成逐字段赋值,或用 Object.assign。 |
排错口诀
改了没反应,先看 v-model 拼写; 多选绑数组,开关绑布尔,数字用 number; 冒泡串了加 .stop,刷新页面加 .prevent; .lazy 要失焦,不是坏了是没走。
不要用这些办法“修复”
- 不要因为数据不更新就在
@input里再加一份手动赋值——两套逻辑同时改一个数据,早晚冲突。 - 不要为了绕过类型问题把数字字段直接当字符串用,后面算总和时会得到
"1" + "2" = "12"。 - 不要给
el-input-number再套.number,它本来就是数字,多余的修饰符只会让人困惑。
需要老师帮忙时交这些
出问题那个控件的完整标签代码、<script setup> 里对应的数据定义、你敲了什么、JSON 里实际变成了什么、你期望变成什么。
老师巡视时只问三句话
v-model展开成哪两件事?(能答出:value+@input)- 你这个多选框绑的是什么类型?怎么验证的?(能指出在 JSON 里看到是数组)
- 这里为什么加了修饰符?不加会怎样?(能说出具体后果)
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档
第7/7页 · 用自己的话讲一遍
回到第 3 页:你刚才猜对了吗?
为什么没写 type="submit" 也要加 @submit.prevent?
参考解释:el-form 内部渲染的是真实 <form> 元素。在表单的输入框里按回车,浏览器默认会“提交表单”,默认动作就是刷新页面。加 .prevent 是拦住浏览器的默认动作,跟按钮是不是 submit 无关。
回答本课的两个问题
| 问题 | 参考解释 |
|---|---|
v-model 是语法糖,它到底做了哪两件事? |
一是把数据的值绑定给控件(:value),二是监听控件的输入事件并把新值写回数据(@input)。 |
| 为什么多选框必须绑数组? | 因为“多选”的结果天生可能不只一个值,只有数组能同时装下若干个选项。 |
五个必须能答上来的问题
| 考考自己 | 可以这样通俗回答 |
|---|---|
@click="fn" 和 @click="fn()" 的区别? |
前者自动把事件对象作为第一个参数传进去;后者按你写的参数来传,想要事件对象要写 $event。 |
.stop 和 .prevent 分别挡什么? |
.stop 挡事件继续往外冒;.prevent 挡浏览器的默认动作。 |
.lazy 有什么用? |
让 v-model 不在每次输入时更新,改在失焦或回车时更新,适合搜索框这类不想频繁触发的场景。 |
.trim 和 .number 分别怎么用? |
前者去首尾空格,后者把输入转成数字类型,都直接写在 v-model 后面。 |
表单数据为什么常用 reactive 而不是一堆 ref? |
字段多的时候,一个对象更整洁,模板里写 form.name 也比 name.value 直观。 |
一分钟现场演示指南
- 展示双向:在姓名框打字,指着右侧 JSON 说“数据跟着界面走”。
- 展示多选:勾两个方向,指出 JSON 里变成了数组,“这是多选的关键”。
- 展示修饰符:在
.lazy框打字不动、失焦才动;在.trim框输入前后空格,指出值被清理。 - 收尾一句:“事件负责听,v-model 负责来回同步,两个一起才让页面真正能操作。”
交作业前最后检查
src/App.vue与练习记录.md已保存。- 至少 5 个控件能改到数据,且类型正确。
- 输入框里按回车页面不刷新。
- Console 无红色报错。
课后再做一个小对照
把本课报名表里最复杂的一个字段(例如 directions 多选),改成用 @change 手动维护数组的写法:<el-checkbox-group @change="val => form.directions = val">。两种写法都能用,写下一句话总结:什么情况下你会选择不用 v-model(提示:当“界面值”和“数据值”需要不一致时,例如只格式化不改原值)。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(事件处理 / 表单输入绑定章节)· 组件依据 Element Plus 2.8 官方文档