Skip to content

vue介绍与官网(Lesson2 1/3) ​

本页对应 Lesson02,共 5 段视频,约 1 小时 11 分。

学习目标 ​

  1. 能说明 key 在 Vue 复用重复元素时的作用,并通过为不同分支或列表项设置唯一 key,复现并修复切换登录方式后输入框内容残留的问题。
  2. 能解释 v-model 是 :value 与输入事件结合的语法糖,能分别用 v-model 和 :value + @input 实现双向绑定并比较两者写法。
  3. 能使用 v-model 为文本框、下拉框、单选框和多选框设置初始值,并在提交时正确获取表单内容,必要时使用 .number 将值转为数字。
  4. 能区分原生事件绑定与 Vue 事件绑定写法,能在模板中正确使用 @click 执行语句、调用方法或传递参数,并说明事件处理函数中 this 的指向。
  5. 能使用 .prevent 修饰符阻止链接跳转、表单提交和右键菜单等默认行为,并能说明其与 event.preventDefault() 的等价关系。

知识点 ​

本节资料与学习目标 ​

本节笔记未附图片,仅保留网盘地址:https://www.aliyundrive.com/s/2A6w17TNADy

这节笔记标题写作“一、vue介绍”,但按转写稿,实际内容是对 Vue 指令的复习与补充:key 管理重复元素、v-model 语法糖、表单初始值与数据收集、事件绑定注意事项、prevent 修饰符。学习时不要只记语法,要跟着转写稿里的调试过程理解“为什么这样写”。

从挂载开始:Vue 实例与容器 ​

为什么需要:先有容器和实例,后面的指令才有作用范围。
怎么想:页面里准备一个根元素,通常 id 为 app 或 root,再用 new Vue 挂载。
怎么做:

html
<div id="app"></div>
<script>
new Vue({
  el: '#app',
  data: { loginType: 'email' }
})
</script>

易错点:el 写选择器时要和页面 id 一致;配置对象里的 data、methods 拼写要细心。转写稿反复提醒“代码一模一样却没效果”,往往就是节点、选择器或方法名写错。

用 key 管理被复用的元素 ​

为什么需要:转写稿用“邮箱登录/手机登录”切换演示:两个 input 标签相同,Vue 的 diff 会尽量复用元素,只改属性,不重新创建,于是输入框里的值可能被保留。
怎么想:v-if/v-else 决定显示哪一块;当结构相似时,要告诉 Vue “它们不是同一个元素”。
怎么做:用 template 包裹,并给 input 加不同的 key:

html
<template v-if="loginType === 'email'">
  <label for="email">邮箱</label>
  <input id="email" placeholder="请输入邮箱" :key="'email'">
</template>
<template v-else>
  <label for="phone">手机</label>
  <input id="phone" placeholder="请输入手机号" :key="'phone'">
</template>
<button @click="toggleLoginType">切换登录方式</button>

切换时修改 loginType 即可。转写稿调试发现:不加 key,切换后手机号还在;加 key 后元素按不同身份重新创建。
易错点:key 要稳定且区分;转写稿中还尝试过 v-for 与 template 结合,出现问题后改用 div,说明指令组合要按实际结构调试。

v-model 语法糖:从单向到双向 ​

为什么需要:v-bind 只能把数据渲染到视图,用户在输入框里改内容,数据不会自动变。
怎么想:双向绑定本质是“绑定 value + 监听 input 事件并写回数据”。
怎么做:

html
<input v-model="username">
<!-- 等价理解 -->
<input :value="username" @input="username = $event.target.value">

转写稿还提到可写完整形式 v-model:value,一般省略 :value;修饰符可接在后面,如 .lazy、.number、.trim。
易错点:v-model 主要用于表单元素;v-bind 是单向。事件里 $event.target.value 才是输入内容,别把 $event 和 target 弄混。

表单初始值与数据收集 ​

为什么需要:登录、修改页都要“设置初值”和“提交时收集数据”。
怎么想:用 v-model 同时完成初值显示和输入收集;下拉、单选、多选的值常用数字,避免存汉字。
怎么做:

html
<select v-model.number="city">
  <option value="1">北京</option>
  <option value="2">上海</option>
</select>
<input type="radio" name="gender" value="1" v-model.number="gender">男
<input type="radio" name="gender" value="2" v-model.number="gender">女
<input type="checkbox" value="1" v-model="hobbies">学习
js
data: { city: 2, gender: 2, hobbies: [1, 2] }

易错点:只写 :value 能设初值,但输入后数据不更新;需要收集就用 v-model。数字值加 .number,否则可能收到字符串。多选绑定数组,初值写数字数组。

事件绑定:原生写法与 Vue 写法 ​

为什么需要:Vue 里既要能调用方法,也要能传参、拿事件对象。
怎么想:原生 onclick 属性值常是字符串;Vue 用 @click,值是函数或语句。
怎么做:

html
<button @click="fn">不传参,默认接收事件对象</button>
<button @click="fn()">调用函数,可手动传参</button>
<button @click="fn($event)">手动传事件对象</button>

易错点:不要把 @click 和 :onclick 混用;转写稿演示 :onclick="fn()" 会在渲染求值时调用函数,把结果赋给原生属性,和 @click 不是一回事。方法名、括号、$event 都要写清。

prevent 修饰符:阻止默认行为 ​

为什么需要:链接会跳转、表单会提交刷新、右键会出菜单,这些默认行为常常要阻止。
怎么想:原生用 event.preventDefault();Vue 提供 .prevent 修饰符。
怎么做:

html
<a href="https://www.baidu.com" @click.prevent="fn">跳转至百度</a>
<form @submit.prevent="handleSubmit">
  <input v-model="wd" name="wd">
  <button>搜索</button>
</form>
<div @contextmenu.prevent>右击不出现菜单</div>

易错点:修饰符写在事件名后,如 @click.prevent;表单提交用 @submit.prevent,不要只在按钮上点一下却忘了表单默认提交。转写稿强调 .prevent 使用频率很高,要能一眼看懂它等价于阻止默认行为。

先看官网 ​

本节笔记本身只给了官网入口,课堂转写稿实际围绕 v-if/v-show、key、v-model、表单取值和事件修饰符展开。先把官网放在这里,后面遇到 API 细节可回查:

为什么需要:官网是查指令、修饰符和版本差异的第一手资料。怎么想:先确认自己看的是 Vue2 还是 Vue3 文档,避免把不同版本写法混用。怎么做:遇到 v-model、v-on、key 等不确定点时,按当前项目版本回官网核对。易错点:中文站和 Vue2 站不是同一个入口,别把 Vue2 示例直接套到 Vue3 项目里。

用 key 管理被复用的元素 ​

为什么需要:课堂做了一个“邮箱登录/手机登录”切换案例。默认显示邮箱,点击按钮切到手机,再切回邮箱。调试时发现:在手机输入框里输入 15101125517,切回邮箱再切回来,手机号还在。原因不是数据没变,而是 Vue 的 Diff 算法在比较新旧虚拟 DOM 时,认为两个 input 是同类元素,复用了原来的 DOM,只替换了 id、placeholder 等属性,输入框里的 value 没有被重新生成。

怎么想:当 v-if / v-else-if 切换的两块内容结构相似,尤其都是 input 时,要意识到“元素可能被复用”。如果业务要求两块输入框完全独立,就要给它们不同的 key,让 Diff 认为它们不是同一个节点。

怎么做:用 template 包住一组元素,配合 v-if / v-else-if,并给 input 加不同 key:

html
<template v-if="loginType === 'email'">
  <label for="email">邮箱</label>
  <input id="email" placeholder="请输入邮箱" :key="'email'">
</template>
<template v-else-if="loginType === 'phone'">
  <label for="phone">手机</label>
  <input id="phone" placeholder="请输入手机号" :key="'phone'">
</template>
<button @click="toggleLoginType">切换登录方式</button>
js
data() {
  return {
    loginType: 'email'
  }
},
methods: {
  toggleLoginType() {
    this.loginType = this.loginType === 'email' ? 'phone' : 'email'
  }
}

易错点:v-show 和 v-if 不一样。课堂里把 v-if 改成 v-show 后,两个区域会同时存在,只是隐藏/显示,不是替换;而且 v-show 不能直接和 template 结合使用,演示中改成 div 才正常。key 要加在真正需要区分的元素上,不要以为随便加就能解决所有复用问题。

v-model 的语法糖 ​

为什么需要:表单里既要显示初始值,又要让用户输入后数据同步变化。只用 :value 是单向绑定:数据能驱动视图,但视图改数据不会自动回写。

怎么想:v-model 本质上是“:value + @input”的结合体。课堂演示了等价写法:

html
<input :value="userName" @input="userName = $event.target.value">

它和下面效果一样:

html
<input v-model="userName">

怎么做:普通文本框直接 v-model;需要延迟同步可用 .lazy,需要数字可用 .number。课堂还提到 v-model 可带修饰符,写完整时类似 v-model:value,但一般省略 :value。

易错点:v-model 主要面向表单元素;普通元素用 v-bind 更合适。@input 里 $event.target.value 才是输入框当前内容,不要写成 $event.value。课堂还提醒:模板里直接写 alert(...) 这类内置函数会报错,应在 methods 中封装后再调用。

表单初始值与获取表单内容 ​

为什么需要:登录、修改资料、提交搜索都要“设置初始值”和“收集表单内容”。课堂强调表单重点就两件事:怎么取值、怎么设初值。

怎么想:文本框、下拉、单选、多选都先让 v-model 绑定数据。下拉和单选常存数字编号,而不是汉字,因为数字占空间小、搜索快;从表单收集到的是字符串时,用 .number 转数字。

怎么做:

html
<select v-model.number="city">
  <option value="1">北京</option>
  <option value="2">上海</option>
  <option value="3">成都</option>
</select>

<label><input type="radio" name="sex" value="1" v-model.number="sex">男</label>
<label><input type="radio" name="sex" value="2" v-model.number="sex">女</label>

<label><input type="checkbox" v-model="hobby" value="1">学习</label>
<label><input type="checkbox" v-model="hobby" value="2">睡觉</label>
js
data() {
  return {
    city: 2,
    sex: 2,
    hobby: [1, 3]
  }
}

易错点:多选 v-model 要绑数组;数组里写数字还是字符串要和 value 保持一致。下拉、单选如果不加 .number,收集到的是字符串,后续比较或提交可能出错。表单提交默认会刷新/跳转,课堂用 @submit.prevent 或按钮事件里 event.preventDefault() 阻止。

事件写法与 .prevent ​

为什么需要:原生事件里常写 onclick="fn()",但 Vue 模板里事件值可以是函数名、函数调用语句或表达式。写错会“页面一加载就执行”,或者点击没反应。

怎么想:@click="fn" 是把函数交给事件;@click="fn()" 是模板编译时调用函数并把返回值当处理逻辑,容易立刻执行。需要传参时写 @click="fn(参数)",需要事件对象用 $event。

怎么做:阻止默认行为可用 .prevent:

html
<a href="https://www.baidu.com" @click.prevent="fn">跳转至百度</a>
<form @submit.prevent="handleSubmit">
  <input v-model="keyword" name="wd">
  <button>搜索</button>
</form>

易错点:课堂演示了 @click.prevent 可阻止链接跳转、表单提交、右键菜单等默认行为;但不要把所有逻辑都堆在模板表达式里,复杂逻辑放 methods。事件简写 @click 比 v-on:click 更常用,但要知道二者是一回事。

先认识 Vue:渐进式框架与作者 ​

笔记原文保留如下:

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
作者:尤雨溪

为什么需要:如果只用原生 DOM 写界面,数据一变就要手动找元素、改内容、处理表单和事件,页面一复杂就容易乱。Vue 的价值在于把“数据”和“视图”关联起来,让开发者更多关注数据与逻辑。

怎么想:“渐进式”可以理解为不必一上来就把整套工程化方案全用上。转写稿里也是从一个根元素、一个 Vue 实例、一个配置对象开始演示:先让 Vue 接管一块界面,再逐步加入指令、表单绑定、事件等能力。

怎么做:转写稿中出现的挂载写法可以整理为:

html
<div id="app"></div>
<script>
new Vue({
  el: '#app'
})
</script>

这里的 el 是 element 的缩写,用来指定 Vue 接管的容器;转写稿中也提到容器 id 叫 root 或 app 都可以,示例里用的是 #app。

易错点:读音是 /vjuː/,类似 view;el 要写选择器字符串;不要把“渐进式”误解成“只能做小页面”,它强调的是可以按需引入、逐步扩展。

从“数据驱动”理解 Vue 的更新方式 ​

为什么需要:转写稿反复强调“数据驱动”:数据一旦改变,会驱动视图重新执行。如果不理解这一点,就容易直接改输入框里的 value,却奇怪为什么数据没变。

怎么想:Vue 更新视图不是把整块 DOM 全部推倒重来,而是会比较新旧虚拟 DOM,也就是转写稿里说的 diff 算法,尽量找出不同点再更新。

怎么做:在登录方式切换的例子中,转写稿用 loginType 这样的数据控制显示邮箱还是手机。点击按钮时改的是数据,视图随之变化,而不是手动去替换 DOM。

易错点:转写稿里有一个关键调试现象:输入框里输入了内容,再切换登录方式,输入框内容还保留。原因是 diff 认为前后都是 input,复用了元素,只改了部分属性,并没有重新生成输入框。解决思路是给元素加不同的 key,让 Vue 认为它们是不同元素。

条件渲染:v-if、v-show、template 与 key ​

为什么需要:登录方式、弹层、表单区域经常需要“二选一”显示。用 v-if 或 v-show 可以让显示由数据条件决定。

怎么想:v-if 更偏向条件渲染,v-show 更偏向显示/隐藏。转写稿中还提到 template 只是一个包裹标签,可以和 v-if 结合使用,避免多包一层真实 DOM。

怎么做:转写稿中的思路可整理为:

html
<template v-if="loginType === 'email'">
  <label for="email">邮箱</label>
  <input id="email" placeholder="请输入邮箱">
</template>
<template v-else>
  <label for="phone">手机</label>
  <input id="phone" placeholder="请输入手机号">
</template>

当出现元素被复用时,可加 key:

html
<input :key="'email'" id="email" placeholder="请输入邮箱">
<input :key="'phone'" id="phone" placeholder="请输入手机号">

易错点:转写稿中特别提醒,v-show 不能和 template 结合使用,改成 div 才行;v-if 则可以和 template 结合。还有,key 不要写成相同值,否则仍然会被复用。

v-model 是语法糖:表单初始值与内容收集 ​

为什么需要:表单提交、登录、修改资料都要“设置初始值”和“获取表单内容”。转写稿把这两点列为表单场景的重点。

怎么想:v-model 可以理解为 :value 加输入事件的结合体,是语法糖。它主要用于表单元素,能实现双向绑定;而 v-bind 更偏单向数据绑定,并且可加修饰符。

怎么做:转写稿中的文本框、下拉、单选、多选可整理为:

html
<input v-model="userName">
<select v-model.number="city">
  <option value="1">北京</option>
  <option value="2">上海</option>
</select>
<input type="radio" value="1" v-model.number="gender">男
<input type="radio" value="2" v-model.number="gender">女
<input type="checkbox" value="1" v-model="hobby">学习
<input type="checkbox" value="2" v-model="hobby">睡觉

易错点:只用 :value 设置初始值,输入后数据不会自动变;要收集变化后的内容,用 v-model 更直接。下拉、单选若需要数字,要加 .number,否则拿到的是字符串。多选绑定数组时,v-model 会判断值是否在数组中。转写稿还提到 .lazy、.number、.trim 等修饰符。

事件绑定与 .prevent 修饰符 ​

为什么需要:按钮点击、表单提交、链接跳转、右键菜单都需要事件处理。Vue 中既要能写事件,也要能阻止默认行为。

怎么想:转写稿对比了原生 onclick 和 Vue 的 @click。Vue 里事件值可以写函数名,也可以写语句;@click="fn" 是函数引用,@click="fn()" 是调用。需要事件对象时可用 $event。

怎么做:阻止默认行为可用 .prevent:

html
<a href="https://www.baidu.com" @click.prevent="fn">跳转至百度</a>
<form @submit.prevent="handleSubmit">
  <input v-model="keyword">
  <button>搜索</button>
</form>

转写稿还演示了右键菜单 contextmenu 也能用 .prevent 阻止。

易错点:转写稿中提醒,@click 里不要随意直接调用内置函数,容易报错;更稳妥的是写自定义方法。阻止表单提交、链接跳转、右键菜单时,.prevent 使用频率很高。还要区分“写函数”和“调用函数”的差别,否则可能在渲染阶段就执行了。

MVVM:先理解“数据驱动视图”为什么需要 ​

本节笔记第一条是“遵循 MVVM 模式”。为什么需要:如果像原生 DOM 那样手动找元素、改内容、读表单值,代码会散落在事件回调里,数据和界面容易不同步。MVVM 的目标是把“数据”和“视图”分开:数据变,视图跟着变;视图输入,数据也能被收集。怎么想:把 data 看作唯一数据源,模板负责声明“哪里显示什么”,Vue 负责在数据变化后更新视图。转写稿里反复说“数据驱动”“数据一旦发生改变,会驱动视图重新执行”,并用登录方式切换演示 v-if/v-else:loginType 是数据,邮箱/手机表单是视图,改数据就切换显示。怎么做:先声明配置对象和挂载点,再把模板中的显示与 data 建立绑定。例如转写稿中的思路可简化为:

html
<div id="app">
  <template v-if="loginType === 'email'">
    <label for="email">邮箱</label>
    <input id="email" placeholder="请输入邮箱">
  </template>
  <template v-else>
    <label for="phone">手机</label>
    <input id="phone" placeholder="请输入手机号">
  </template>
  <button @click="toggle">切换登录方式</button>
</div>

易错点:不要把 MVVM 理解成“完全不用管 DOM”。转写稿里调试登录切换时发现:v-if 切换后输入框内容还在,原因是 diff 认为前后都是 input,复用了元素;解决是给元素加 key,让 Vue 知道这是不同元素。这里纠正同音字:转写稿里的“低付算法”是 diff 算法,“微干货”是 v-for,“微杠艺术”是 v-if,“微杠S”是 v-else。

编码简洁、体积小、效率高:靠语法糖和 diff 落地 ​

为什么需要:项目里大量是表单、列表、条件显示和事件处理,如果每次都用原生写法,代码冗长且易错。Vue 提供指令和语法糖,让常见操作更短。怎么想:简洁不是“少写几行”而已,而是把重复逻辑交给框架。转写稿里用 v-model 讲语法糖:它本质上是 :value 加输入事件收集值的结合体,所以一个指令就能完成双向绑定。效率高则来自数据变化后的 diff 比较:不是整页重渲染,而是尽量找不同、复用相同节点。怎么做:表单初始值和收集用 v-model:

html
<input v-model="username">
<select v-model.number="city">
  <option value="1">北京</option>
  <option value="2">上海</option>
</select>

事件用 @click、@submit,需要阻止默认行为时用修饰符:

html
<a href="https://www.baidu.com" @click.prevent="fn">跳转至百度</a>
<form @submit.prevent="handleSubmit">...</form>

易错点:第一,v-model 主要面向表单元素,转写稿提醒它和 v-bind 不同:v-bind 是单向绑定,v-model 才有表单双向收集的意义;第二,数字选择要加 .number,否则收集到的是字符串;第三,事件里不要直接写 alert 这类内置函数名当处理函数,转写稿调试时点击报错,原因是 Vue 把它当自定义函数查找,应该写成 @click="fn" 后在方法里调用,或用语句形式 @click="alert('...')";第四,v-show 是显示/隐藏,不会销毁重建,和 v-if 的替换逻辑不同,转写稿里用 v-show 时两个表单会同时保留在 DOM 中。

只关注界面,可配合第三方库:边界清楚才好扩展 ​

为什么需要:一个项目不只有界面,还有请求、路由、状态管理等。Vue 本身只关注视图层,边界清楚,才方便按需引入第三方库。怎么想:把 Vue 当“视图层工具”,不要指望它包办所有业务能力;表单收集、事件处理、条件渲染、列表渲染是它擅长的,Ajax、路由、复杂状态可以另配库。转写稿中表单提交、登录方式切换、下拉/单选/多选收集,都是“界面层”的典型任务;收集到数据后再提交给服务端。怎么做:先用 Vue 管理模板和数据,再把请求等逻辑放到方法中。例如提交时收集 this.username、this.city,再调用接口。易错点:不要把“Vue 只关注界面”误解为“不能做项目”,而是“不要把所有职责都塞进 Vue 实例”;另外转写稿里 v-if 与 v-show 混用、key 缺失导致输入框被复用,都是边界和渲染机制没想清楚的表现。记住:数据变视图变,但 diff 会复用节点;需要区分元素时用 key,需要阻止默认行为时用 .prevent。

先看本节笔记:Vue 周边库地图 ​

本节笔记给出的是 Vue 周边库清单:

  • vue-cli(Vue 脚手架)
  • axios
  • vue-router(路由)
  • vuex(状态管理)
  • element-ui(UI 组件库)
  • ……

为什么需要:学到组件、工程化、请求、页面切换和状态共享时,单靠 Vue 核心不够,需要这些库配合。怎么想:先把它们当成“地图”,知道各自解决什么问题,不急着背 API。怎么做:后续分别学习;本节先结合转写稿复习指令基础。易错点:不要把周边库当成核心语法,也不要在本节提前编造未讲的配置细节。

key 管理被复用的元素 ​

为什么需要:v-if / v-show 切换同类元素时,Vue 的 diff 算法会尽量复用已有 DOM。怎么想:同类 input 标签相同时,框架可能只改属性,不重建元素,导致输入框里的值被保留。怎么做:在转写稿的登录方式切换案例中,给两段结构加不同 key:

html
<div v-if="loginType === 'email'" key="email">
  <label for="email">邮箱</label>
  <input id="email" placeholder="请输入邮箱">
</div>
<div v-else key="phone">
  <label for="phone">手机</label>
  <input id="phone" placeholder="请输入手机号">
</div>

调试过程:先切换邮箱/手机,发现 placeholder、id 变了,但输入框内容还在;加上不同 key 后,元素被重新创建。易错点:key 要稳定且唯一;v-show 是显示隐藏,不是替换,不能把它和 v-if 的复用问题混为一谈。

v-model 语法糖 ​

为什么需要:表单需要“数据变视图变、视图变数据也变”。怎么想:v-model 本质是 :value 加 input 事件的组合。怎么做:

html
<input v-model="username">
<!-- 等价于 -->
<input :value="username" @input="username = $event.target.value">

易错点:v-model 主要面向表单元素;修饰符如 .lazy、.number 要写对;不要把它和普通 :value 单向绑定混淆。

设置表单初始值与获取表单内容 ​

为什么需要:登录、修改页都要“回填初始值”和“提交前收集数据”。怎么想:用 v-model 同时完成回填与收集。怎么做:

html
<form @submit.prevent="handleSubmit">
  <input v-model="username">
  <select v-model.number="city">
    <option value="1">北京</option>
    <option value="2">上海</option>
  </select>
  <input type="radio" name="gender" value="1" v-model.number="gender">男
  <input type="checkbox" value="1" v-model="hobbies">学习
  <button>提交</button>
</form>

调试过程:转写稿中先只用 :value,输入后提交拿到的仍是旧数据;改成 v-model 后数据随输入更新。城市、性别用 .number 可避免拿到字符串。易错点:复选框绑定数组;下拉框的值常存数字而不是汉字;提交表单要阻止默认跳转。

事件处理注意事项 ​

为什么需要:Vue 事件既要能写语句,也要能调方法、传参数。怎么想:@click 后可以写表达式,也可以写函数名。怎么做:

html
<button @click="str += '!'">加叹号</button>
<button @click="fn">调用方法</button>
<button @click="fn($event)">传事件对象</button>

易错点:转写稿中演示了在模板里直接调用 alert 会报错,说明模板表达式里调用的函数要能在组件实例上找到;复杂逻辑放 methods。需要事件对象时用 $event,不要误写成 event 或漏传。

修饰符 prevent ​

为什么需要:链接跳转、表单提交、右键菜单都有默认行为。怎么想:prevent 相当于在事件里调用 event.preventDefault()。怎么做:

html
<a href="https://www.baidu.com" @click.prevent="fn">跳转至百度</a>
<form @submit.prevent="handleSubmit">...</form>
<div @contextmenu.prevent>右击我</div>

易错点:prevent 要写在事件修饰符位置;阻止默认行为不等于阻止冒泡,后者用 .stop。转写稿反复强调:这个修饰符使用频率很高,但不要和普通事件处理逻辑混写。

本节要解决什么问题 ​

这一节笔记只做一件事:把 Vue 运行起来。转写稿里反复说“首先把我的 Vue 引用进来”,这里的“VOGS/will”是口误,按 Vue 理解;“开个对象”指 Vue 的配置对象,“EL”指 el,“井号 APP”指 #app。这些说法都说明:环境配置不是背命令,而是先让页面能拿到 Vue,再让 Vue 实例挂到某个容器上。

为什么需要:浏览器本身不认识 Vue 的模板语法和指令,必须先引入 Vue 的脚本,或者通过 NPM 安装后交给构建工具处理。
怎么想:学习阶段优先选“直接引入”或“CDN 引入”,工程化项目再选 NPM。
怎么做:按笔记保留三种方式。
易错点:Vue 2 和 Vue 3 的 CDN 地址不同;开发版和生产版用途不同;NPM 安装后不是直接双击 HTML 就能用,通常还要配合构建工具。

直接引入:把 Vue 下载到本地 ​

为什么需要:本地文件不依赖外网,断网也能练习,版本也可控。
怎么想:先去官网下载 vue.js,再用 script 标签引入本地文件。笔记给的是下载地址,不是完整 HTML。
怎么做:保留笔记中的下载地址:

shell
# 下载地址
https://v2.cn.vuejs.org/v2/guide/installation.html

下载后按你保存的路径引入,例如把文件放在项目里的 js 目录,再用 script 指向它;具体路径以实际保存位置为准。
易错点:不要凭记忆写 CDN 地址;本地文件名和路径大小写要一致;Vue 2 页面不要误引 Vue 3 文件。

CDN 引入:开发版与生产版要分清 ​

为什么需要:不想下载文件时,CDN 可以快速做单页练习。
怎么想:开发阶段用带警告和调试信息的版本,上线再用删掉警告的生产版。
怎么做:保留笔记代码:

html
<!-- 开发环境版本,包含完整的警告和调试模式 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 生产环境版本,删除了警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

易错点:开发版体积更大但报错清楚;生产版删除了警告,初学时排错困难。CDN 还受网络影响,课堂练习打不开时先检查网络和地址是否写全。

NPM 安装:面向工程化项目 ​

为什么需要:真实项目通常有模块化、打包、依赖管理需求,不能只靠一个 script。
怎么想:NPM 安装的是包,后续通过构建工具或模块语法使用。
怎么做:保留笔记命令:

shell
# 最新稳定版
$ npm install vue

易错点:这条命令默认安装当前稳定版,学习 Vue 2 时要确认版本;安装后需要项目里有 package.json 和后续构建配置,不能把“安装完成”等同于“页面已经能运行”。

引入后的最小验证:根元素、挂载与配置对象 ​

转写稿视频 1 给了很好的调试线索:先引用 Vue,再指定一个元素作为“根元素/容器”,一般 id 叫 root 或 app,然后 new Vue,传入配置对象,用 el: '#app' 挂载。这里要纠正转写稿里的同音字:“VOGS/will”是 Vue,“开个对象”是配置对象,“EL Elements”是 el,“井号 APP”是 #app。

为什么需要:只引入脚本不代表 Vue 已经工作,必须让实例挂到页面容器上。
怎么想:先有容器,再有实例;el 的选择器要和容器 id 对应。
怎么做:按转写稿顺序检查:引入 Vue 脚本 → 写容器 → new Vue({ el: '#app' }) → 放到 HTML 页面中运行。
易错点:#app 写成 app;容器 id 和 el 不一致;脚本顺序放错;页面还没加载容器就执行实例。转写稿后续关于 v-model、事件修饰符的内容属于指令部分,本节只取“先引入 Vue、再挂载实例”的调试线索,不强行并入环境配置。

本节标题虽然包含 JS、Vue、React 操作 DOM,但本次转写内容集中在 Vue 的数据驱动 DOM 操作:key、v-model、表单初始值与内容获取、事件绑定和 prevent 修饰符。React 的 DOM 操作不在本次转写中展开,下面不编造补充。

本节定位:从直接操作 DOM 转向数据驱动 DOM ​

为什么需要:原生 JS 操作 DOM 往往要先 querySelector 拿到元素,再手动改 value、改属性、加事件;表单一多,代码就散。Vue 的思路是“改数据,视图跟着更新”,但更新时不是整块重绘,而会走 Diff 算法比较新旧虚拟 DOM。理解这一点,才能理解后面的 key 复用、v-model 和事件写法。

怎么想:把“我要改哪个 DOM”换成“我要改哪个数据”。例如切换登录方式,不是手动隐藏两个 input,而是改 loginType;输入框内容不是手动取 DOM.value,而是让 v-model 同步到数据。

怎么做:先准备容器和 Vue 实例:

html
<div id="app"></div>
<script>
new Vue({
  el: '#app',
  data: {},
  methods: {}
})
</script>

易错点:不要把 Vue 当成“更方便的 querySelector”。一旦用 v-model、@click,就应该让数据驱动视图;否则容易出现“数据变了但视图没变”或“视图变了但数据没变”。

key 管理重复元素 ​

为什么需要:转写中的登录案例默认显示邮箱,点击按钮切换为手机。两个 label + input 结构相似,Vue 的 Diff 会认为它们标签相同,只更新属性,复用原来的 input。结果就是:输入过的手机号在切换后仍留在输入框里,因为 input 节点没有被替换。

怎么想:Diff 是“找不同”。标签一样、结构相似时,Vue 倾向于复用节点;只有属性不同就改属性。要告诉 Vue“这是两个不同身份的元素”,就用 key。v-show 只是显隐,不替换节点,所以不加 key 也可能保留输入值。

怎么做:

html
<div id="app">
  <template v-if="loginType === 'email'">
    <label for="email">邮箱:</label>
    <input id="email" placeholder="请输入邮箱" key="email">
  </template>
  <template v-else>
    <label for="phone">手机:</label>
    <input id="phone" placeholder="请输入手机号" key="phone">
  </template>
  <button @click="toggle">切换登录方式</button>
</div>
<script>
new Vue({
  el: '#app',
  data: { loginType: 'email' },
  methods: {
    toggle() {
      this.loginType = this.loginType === 'email' ? 'phone' : 'email'
    }
  }
})
</script>

易错点:key 要唯一、稳定,不要两个分支写同一个 key;template 可以和 v-if 结合使用,但它只是包裹标签,不能和 v-show 混用。

v-model 语法糖 ​

为什么需要:表单需要双向绑定:数据能显示到输入框,输入框内容也能写回数据。手写 :value 加 @input 可以,但重复。

怎么想:v-model 本质是 :value 与 @input 的结合体。转写里说“语法糖”,就是这个意思。它主要针对表单元素,不是所有元素都适合。

怎么做:

html
<input v-model="username">
<!-- 等价于 -->
<input :value="username" @input="username = $event.target.value">
js
data: { username: '张三' }

修饰符:.lazy 在失焦后更新;.number 把输入转数字;.trim 去首尾空格。

易错点:v-bind:value 只负责显示初始值,用户输入不会自动写回数据;要收集就用 v-model。v-model 不要和 :value 重复写。

设置表单初始值与获取内容 ​

为什么需要:登录、修改资料、搜索都离不开表单:既要给表单设置初始值,又要在提交时收集内容。

怎么想:在 Vue 里,v-model 同时承担两件事:绑定初始值、收集用户输入。下拉、单选、多选要注意值类型。转写强调:城市、性别这类数据入库常用数字,不用汉字,因为数字占用小、搜索快;所以要用 .number 或 :value 保证收集到数字。

怎么做:

html
<form @submit.prevent="submit">
  <input v-model.trim="username" placeholder="用户名">
  <select v-model.number="city">
    <option :value="1">北京</option>
    <option :value="2">上海</option>
  </select>
  <label><input type="radio" :value="1" v-model.number="gender">男</label>
  <label><input type="radio" :value="2" v-model.number="gender">女</label>
  <label><input type="checkbox" :value="1" v-model="hobbies">学习</label>
  <label><input type="checkbox" :value="2" v-model="hobbies">睡觉</label>
  <button>提交</button>
</form>
js
data: {
  username: '张三',
  city: 2,
  gender: 2,
  hobbies: [1, 3]
},
methods: {
  submit() {
    console.log(this.username, this.city, this.gender, this.hobbies)
  }
}

易错点:select、radio 不加 .number 可能收集到字符串;checkbox 多选要绑定数组;提交表单要阻止默认跳转,可用 @submit.prevent。

事件注意事项与 prevent 修饰符 ​

为什么需要:Vue 事件和原生事件写法不同。原生常写 onclick="fn()";Vue 用 @click。另外,链接跳转、表单提交、右键菜单都有默认行为,很多业务需要阻止。

怎么想:@click="fn" 传方法引用,事件触发时调用,默认能收到事件对象;@click="fn()" 是内联语句,点击时执行,但不会自动把事件对象传进去,需要写 @click="fn($event)"。.prevent 等价于 event.preventDefault()。

怎么做:

html
<a href="https://www.baidu.com" @click.prevent="show">跳转至百度</a>
<form @submit.prevent="submit">
  <input v-model="wd">
  <button>搜索</button>
</form>
<div @contextmenu.prevent>右击我</div>
js
methods: {
  show() { alert('百度') },
  submit() { console.log(this.wd) }
}

易错点:模板表达式作用域有限,像 alert 这类全局函数直接写在模板里可能报错,建议放到 methods 中调用 window.alert;需要事件对象时用 $event;.prevent 在表单提交、链接跳转、右键菜单中频率很高。

Vue 模板语法与本节定位 ​

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

为什么需要:直接操作 DOM 写登录切换、表单收集会重复、易漏;模板语法把“数据长什么样”声明出来。怎么想:先写数据,再用指令把数据映射到视图;数据变,视图按 diff 结果最小更新。怎么做:围绕 v-if/v-show/v-for/v-model/v-on 练一个登录切换和表单。易错点:不要以为数据一变就整块重建 DOM,正因 Vue 会复用元素,才需要理解 key。

用 key 管理被复用的元素 ​

为什么需要:转写稿的调试场景是登录方式切换:邮箱输入框和手机输入框用 v-if/v-else-if 二选一,切换后输入过的手机号还留在输入框里。怎么想:Vue 初次渲染只渲染满足条件的一块;切换时 diff 发现上下都是 label + input,标签相同,就复用原元素、只改 id/placeholder 等属性,所以 input 的 value 没被清掉。怎么做:给两个 input 加不同 key,让 Vue 认为不是同一元素,从而重建。

html
<div id="app">
  <template v-if="loginType === 'email'">
    <label for="email">邮箱</label>
    <input id="email" placeholder="请输入邮箱" key="email">
  </template>
  <template v-else-if="loginType === 'mobile'">
    <label for="mobile">手机</label>
    <input id="mobile" placeholder="请输入手机号" key="mobile">
  </template>
  <button @click="toggleLogin">切换登录方式</button>
</div>
js
new Vue({
  el: '#app',
  data: { loginType: 'email' },
  methods: {
    toggleLogin() {
      this.loginType = this.loginType === 'email' ? 'mobile' : 'email'
    }
  }
})

易错点:条件里的字符串要写单引号,如 loginType === 'email';template 只是包裹标签,可与 v-if/v-else-if 结合;转写稿还验证了 v-show 是显示隐藏、不销毁元素,且 v-show 不能和 template 结合使用,需要改成普通 div。key 不要重复。

v-model 是语法糖 ​

为什么需要:表单既要显示初始值,又要收集用户输入。怎么想:v-model 本质是 :value 加 @input 的结合体;它主要面向表单元素,并支持 .lazy/.number/.trim 修饰符。怎么做:

html
<input v-model="username">
<input :value="username" @input="username = $event.target.value">
<input v-model.lazy="username">

易错点:只写 :value 是单向绑定,输入后数据不变;$event.target.value 才是输入内容;.lazy 是失去焦点/变更后更新,.number 转数字,.trim 去首尾空格。转写稿强调 v-bind 可用于任意元素,v-model 主要用于表单,二者不要混。

设置表单初始值和收集表单内容 ​

为什么需要:登录、修改页都要“回填初始值”和“提交时取数据”。怎么想:初始值就是 data 的初值,收集就是提交时读 this.xxx;用 v-model 把两者统一。怎么做:

html
<form @submit.prevent="handleSubmit">
  <input v-model.trim="username" placeholder="用户名">
  <select v-model.number="city">
    <option value="1">北京</option>
    <option value="2">上海</option>
    <option value="3">成都</option>
  </select>
  <label><input type="radio" name="gender" value="1" v-model.number="gender">男</label>
  <label><input type="radio" name="gender" value="2" v-model.number="gender">女</label>
  <label><input type="checkbox" value="1" v-model.number="hobby">学习</label>
  <label><input type="checkbox" value="2" v-model.number="hobby">睡觉</label>
  <button type="submit">提交</button>
</form>
js
data: {
  username: '张三',
  city: 2,
  gender: 2,
  hobby: [1, 2]
},
methods: {
  handleSubmit() {
    console.log(this.username, this.city, this.gender, this.hobby)
  }
}

易错点:转写稿中只用 :value 设初值,输入后提交拿到的还是旧数据;select/radio/checkbox 的 value 默认是字符串,需要数字时加 .number;多选框绑定数组;form 默认提交会刷新,用 @submit.prevent。

事件绑定与 prevent 修饰符 ​

为什么需要:点击、提交、右键菜单都有默认行为,不阻止会跳转或刷新。怎么想:原生 onclick 属性小写、值是字符串;Vue 用 v-on 或 @,值可以是语句、函数名、函数调用。怎么做:

html
<a href="https://www.baidu.com" @click.prevent="show">百度</a>
<form @submit.prevent="handleSubmit">...</form>
<div @contextmenu.prevent>右键菜单被阻止</div>
<button @click="fn($event, '参数')">传参</button>

易错点:.prevent 等价于 event.preventDefault(),常和表单提交、链接跳转、contextmenu 一起用;需要事件对象时用 $event;不要依赖模板里直接调用内置函数,转写稿中测试会报错,放进 methods 更稳。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

示例一:用 key 管理被复用的元素(v-if 切换登录方式) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>key 管理可复用元素</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <!-- template 只是包裹标签,可以和 v-if / v-else 结合使用 -->
    <template v-if="loginType === 'email'">
      <label for="email">邮箱:</label>
      <input id="email" type="text" placeholder="请输入邮箱" key="email">
    </template>

    <template v-else>
      <label for="phone">手机:</label>
      <input id="phone" type="text" placeholder="请输入手机号" key="phone">
    </template>

    <button @click="changeLoginType">切换登录方式</button>
    <p>当前登录方式:{{ loginType }}</p>
  </div>

  <script>
    new Vue({
      el: '#app',                       // 指定 Vue 的根容器(挂载点)
      data: {
        loginType: 'email'              // 默认邮箱登录
      },
      methods: {
        changeLoginType() {
          this.loginType = this.loginType === 'email' ? 'phone' : 'email';
        }
      }
    });
  </script>
</body>
</html>

关键行与易错点

  • template 只是包裹标签,可以和 v-if / v-else 结合使用,但不能和 v-show 结合使用。
  • v-if / v-else 必须相邻书写,中间不能插入其它元素。
  • label 的 for 要与 input 的 id 对应,点击文字才能聚焦输入框。
  • 核心坑点:不加 key 时,Vue 的 diff 算法认为上下两个都是 <input>,是"同一个元素",只会替换 id、placeholder 等属性,不会重新创建 DOM,所以你在邮箱框里输入的 123 会残留在手机框里。
  • 加上不同的 key(key="email" / key="phone")后,Vue 判断为不同元素,会重新创建,输入内容就不会被带过去。
  • 条件表达式里 'email' 要加引号,写成 loginType === email 会把 email 当成变量,报错找不到。

示例二:v-model 语法糖与修饰符 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>v-model 语法糖</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <h3>1. v-model 双向绑定</h3>
    <input type="text" v-model="userName">
    <p>数据:{{ userName }}</p>

    <h3>2. 等价写法(手写 :value + @input)</h3>
    <input type="text" :value="userName" @input="userName = $event.target.value">

    <h3>3. 只写 :value —— 单向绑定,视图改数据不变</h3>
    <input type="text" :value="userName">

    <h3>4. 修饰符</h3>
    <p>lazy(失去焦点才同步):<input type="text" v-model.lazy="userName"></p>
    <p>trim(自动去首尾空格):<input type="text" v-model.trim="userName"></p>
    <p>number(转数字):<input type="text" v-model.number="age"></p>
    <p>age = {{ age }},类型:{{ typeof age }}</p>
  </div>

  <script>
    new Vue({
      el: '#app',
      data: {
        userName: '张三',
        age: 18
      }
    });
  </script>
</body>
</html>

关键行与易错点

  • v-model 的本质就是 :value + @input 的结合体,官方称之为语法糖,两种写法效果完全一致。
  • 第 3 个输入框只写了 :value,属于单向数据绑定:改数据视图会变,但改视图数据不会变。
  • v-model 只能用在表单元素上(input / textarea / select),而 v-bind(:value)什么元素都能用,这是二者的核心区别。
  • @input="userName = $event.target.value" 中 $event 是事件对象,target 就是当前这个 input 元素。
  • .lazy 让同步时机从 input 事件改为 change 事件(失去焦点才更新)。
  • .number 是把输入值转成数字,不写则收集到的是字符串。

示例三:设置表单初始值、获取表单内容与 .prevent 修饰符 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>表单初始值与数据收集</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <!-- @submit.prevent 阻止表单默认提交(跳转/刷新) -->
    <form @submit.prevent="handleSubmit">
      <p>
        用户名:
        <input type="text" v-model.trim="userName">
      </p>

      <p>
        所在城市:
        <select v-model.number="city">
          <option :value="1">北京</option>
          <option :value="2">上海</option>
          <option :value="3">成都</option>
          <option :value="4">西安</option>
        </select>
      </p>

      <p>
        性别:
        <label><input type="radio" name="gender" :value="1" v-model.number="gender">男</label>
        <label><input type="radio" name="gender" :value="2" v-model.number="gender">女</label>
      </p>

      <p>
        爱好:
        <label><input type="checkbox" :value="1" v-model="hobby">学习</label>
        <label><input type="checkbox" :value="2" v-model="hobby">睡觉</label>
        <label><input type="checkbox" :value="3" v-model="hobby">吃饭</label>
      </p>

      <button type="submit">提交</button>
    </form>

    <pre>{{ { userName: userName, city: city, gender: gender, hobby: hobby } }}</pre>

    <hr>
    <a href="http://www.baidu.com" @click.prevent="showTip">跳转至百度(不会跳)</a>
    <div class="box" @contextmenu.prevent>右击我,不会弹出右键菜单</div>
  </div>

  <script>
    new Vue({
      el: '#app',
      data: {
        userName: '张三',   // 初始值
        city: 2,            // 默认选中上海(数字 2)
        gender: 2,          // 默认选中女
        hobby: [1, 2]       // 多选绑定数组,默认选中学习、睡觉
      },
      methods: {
        handleSubmit() {
          // 这里收集到的就是最新数据,可以直接发给服务端
          console.log(JSON.stringify({
            userName: this.userName,
            city: this.city,
            gender: this.gender,
            hobby: this.hobby
          }));
        },
        showTip() {
          alert('我被点击了,但没有跳转');
        }
      }
    });
  </script>

  <style>
    .box {
      width: 200px;
      height: 100px;
      background: #f5f5f5;
      margin-top: 10px;
      line-height: 100px;
      text-align: center;
    }
  </style>
</body>
</html>

关键行与易错点

  • v-model 既负责设置初始值,又负责收集数据;如果只写 :value,能设置初始值但收集不到用户修改后的内容。
  • 下拉框、单选框的选项值要用 :value="1"(数字) 而不是 value="1"(字符串),否则收集到的是字符串,需要再手动转换。
  • 单选框必须加相同的 name 属性才能互斥,否则男女可以同时选中。
  • 复选框多选时 v-model 要绑定数组,Vue 会自动判断每个 :value 是否在数组中,在就选中。
  • @submit.prevent 等价于在方法里写 e.preventDefault(),作用是阻止表单的默认提交行为(默认会跳转到当前页或 action 指定地址)。
  • 注意:@click.prevent 中 prevent 后面不要加括号,它是修饰符不是函数。
  • 常见易错:在 @click 里写 alert('xx') 会报错,因为 Vue 把 alert 当作自定义方法去 methods 里找;要么在 methods 中定义方法,要么用 window.alert(...)。

常见错误 ​

  1. 现象:切换邮箱/手机登录方式后,输入框里的内容仍残留。原因:v-if/v-else 分支结构相似,Vue 复用同类型元素,未强制替换。修法:给两个分支元素加唯一 key,如 key="email"、key="phone"。

  2. 现象:提交表单时拿到的是旧值。原因:只写了 :value 单向绑定,输入内容没有写回 data。修法:改用 v-model,或使用 :value + @input 手动更新数据。

  3. 现象:@click="fn()" 后拿不到事件对象,或参数变成 undefined。原因:模板中写的是表达式,不是函数引用;事件对象未显式传入。修法:需要事件对象时写 @click="fn($event)";不需要参数时优先写 @click="fn",并确保 fn 在 methods 中定义。

  4. 现象:点击提交按钮或搜索按钮后页面刷新/跳转。原因:表单默认提交行为未阻止。修法:用 @submit.prevent="handleSubmit",或按钮事件加 .prevent,如 @click.prevent。

  5. 现象:下拉框、单选、多选收集到的值类型不对,数字变成字符串。原因:表单值默认按字符串处理,v-model 未做类型转换。修法:数字场景使用 v-model.number,或给选项使用 :value 绑定数字;多选初值必须是数组。

  6. 现象:v-for 渲染的输入框在删除/插入中间项后内容错位。原因:key 缺失或使用不稳定的 index。修法:使用业务唯一 id 作为 key,不要用数组下标承担业务标识。

  7. 现象:模板里直接写 @click="alert(...)" 不生效或报错。原因:模板表达式作用域不是任意全局函数,内置函数不能直接按自定义方法调用。修法:在 methods 中封装处理函数,模板只调用实例上的方法。

练习 ​

  1. 实现“邮箱登录/手机登录”切换:默认显示邮箱,点击按钮切换;在两个输入框分别输入内容后反复切换,要求输入内容不串。请说明你用什么方式解决复用问题。

  2. 实现一个表单,包含用户名、城市下拉、性别单选、爱好多选;要求设置初始值,点击提交后输出一个对象。城市和性别必须是数字类型,爱好必须是数组。

  3. 实现一个链接点击后弹出链接文本但不跳转,再实现一个搜索表单提交时阻止默认提交。要求分别使用事件对象和 .prevent 修饰符各写一次。

  4. 不用 v-model,只用 :value 和 @input 实现输入框双向绑定;在此基础上增加 .lazy 和 .number 的效果对比。

  5. 用 v-for 渲染 4 个输入框并支持删除中间项;先不加 key 观察现象,再加唯一 id 作为 key,记录两次现象差异。

原视频对照 ​

#视频时长
1key管理重复元素20:24
2v-model语法糖09:51
3设置表单的初始值以及获取表单内容16:11
4事件注意事项16:40
5修饰符prevent08:37

小结 ​

  1. 结构相似的分支元素切换时,优先考虑复用问题;需要彻底替换时用唯一 key。

  2. 表单“设初值”和“收数据”要同时成立,单向绑定只能设初值,不能可靠收集用户输入。

  3. 表单提交、链接跳转、右键菜单等默认行为,优先用 .prevent 处理,代码更集中。

  4. 事件绑定的核心是区分“函数引用”和“表达式调用”;需要事件对象时显式传 $event。

  5. 下拉、单选、多选的值类型要在数据层明确,数字场景不要依赖默认字符串。

  6. v-for 的 key 应稳定且唯一;用 index 只适合静态且不重排的列表。

  7. 模板中不要依赖任意全局函数,业务动作统一收敛到 methods,便于调试和复用。