资讯详情

资讯详情

Vue实现增删改查面试题全解析:从响应式原理到组件通信

前端面试题里的“用Vue实现一个增、删、改、查的小demo”算是经典中的经典了我在帮A同学模拟面试的时候就反复拿这道题做例子。这道题听起来像新手练习实际上在真实的面试环节里它几乎能串起Vue的一大半核心知识点从响应式原理到事件处理从组件通信到代码组织甚至还能延伸出性能优化和工程化思路。这篇博客我就以自己的实战经验为主把这道题从表层需求到深层考点完整拆一遍顺便把实操过程中踩过的坑和面试官爱追问的点都整理出来给准备面前端岗的朋友一个可以照着练的完整路径。1. 面试题背后的考点——这道“入门题”到底在考什么1.1 表面需求与深层逻辑先看清楚题目本身实现一个列表可以新增一条数据可以删除一条数据可以编辑某一条已有数据还可以通过某种方式查询或筛选数据。这几乎是所有后台管理系统的原子操作所以面试官选这一题本质上是在用最小闭环考察候选人能不能完成一个“可用”的前端页面。但如果你只把它当做一个简单表格来做那就太浅了。面试官真正想看的是你知不知道数据驱动视图是怎么运转的能不能把状态和数据分清楚事件绑定、表单双向绑定、列表渲染、条件渲染这些基础能力是否扎实以及你在代码里有没有体现“数据改了页面自然就变了”这个Vue核心思想。举个实际例子很多候选人做新增时直接操作DOM往table里append一行这在Vue里是完全错误的做法因为你的数据源没有变页面状态和数据状态就脱节了。面试官一眼就能看出你对框架的理解停留在“jQuery思维”这非常致命。再往后说这道题还暗含着对工程能力和代码审美的考察。你的数据结构怎么设计方法是直接写在模板里还是抽到methods新增和编辑复用了哪些逻辑删除之后需不需要恢复上一步这些细节都没写在题目里但一个靠谱的面试官会顺着你的代码一步一步往下挖。1.2 题目适合谁为什么适合当面试题这道题覆盖的人群非常广对0到1年的初级前端它考察的是基础语法有没有掌握对1到3年的中级前端它考察的是响应式原理、生命周期和组件拆分对资深岗位它甚至可以继续追问虚拟列表、大数据量渲染、状态持久化这些进阶话题。这就让面试官有了极大的伸缩空间可以用同一个题目筛选不同层级的人这也是它高频出现在面试题库里的根本原因。我当时给A同学的建议是不要急着写代码先花两分钟把需求在脑子里转一遍列表数据从哪里来、有哪些字段、新增和编辑用同一套表单还是分开、删除需不需要确认、查询是模糊匹配还是精确匹配。这些需求都不算复杂但如果你能在动手之前说出个一二三和那些拿过来就噼里啪啦敲代码的人相比已经领先一个身位了。面试官看重的不是你敲键盘的速度而是你面对一个模糊需求时能不能主动思考和拆解。2. 项目准备与工程搭建——面试选对工具也是加分项2.1 Vue 2 还是 Vue 3选项式还是组合式现在都2025年了如果简历上还只写“熟悉Vue 2”那面试官心里多少会打点折扣。新的项目基本都是Vue 3面试环节这道增删改查demo更是用Vue 3组合式API来做比较稳妥既体现你紧跟技术栈又能把setup、ref、reactive、computed这些常用API放在一个真实场景里展示。当然也不排除一些老项目组还在维护Vue 2面试官会临时要求你用选项式写一版所以两种写法我都建议抽时间过一遍。两者核心区别在于数据和逻辑的组织方式我用一个简单的对比表格让你看得更清楚对比维度Vue 2 选项式Vue 3 组合式数据定义data函数返回对象ref定义基本类型reactive定义对象方法定义methodssetup中定义普通函数计算属性computed选项引入computed函数逻辑复用mixin容易命名冲突自定义组合函数清晰数组更新监听需要特别注意索引修改Proxy代理支持直接改索引面试时如果我问到这两个版本的区别十个人里有八个能说出“性能更好、体积更小”但真正让我满意的是能提到响应式系统的底层差异Vue 2用Object.defineProperty对属性做getter/setter劫持所以新增属性、通过索引修改数组都有天然缺陷Vue 3用Proxy代理整个对象规避了这些麻烦。这个知识点会在第5章重点展开你先有个印象。2.2 用脚手架初始化还是直接写一个HTML文件这题在面试现场通常有两种做法一是拍脑袋说用CDN引入Vue写一个单HTML文件二是用Vite或者Vue CLI初始化一个工程。我的建议非常明确只要面试环境允许就用脚手架因为工程化已经是前端日常你直接在单文件里摆一个Vue全局引用会让面试官觉得你的工作方式还停留在几年前的练习模式。用Vite初始化很轻快在终端里输入项目管理命令后选vue模板就行。不过要注意国内这些包管理器经常存在镜像源问题如果安装卡住半天可以先把源切到配置过的国内地址再重新安装这个属于实操的小经验面试不会考但你自己练习时候能省不少时间。初始化之后我通常会把目录结构精简一下src下面建一个components文件夹放子组件再建一个App.vue作为根组件其他无关文件删掉保持现场整洁。面试官在review代码时一个干净清晰的工程结构本身就是加分项。3. 核心功能拆解与实现——增删改查每一步都不简单3.1 数据设计从内存数组到本地存储点开这道题的第一步就是设计数据结构。以常见的“用户管理”为例我一般用一个reactive数组来放用户列表每个对象包含id、name、phone、email这几个字段。id必须要有这是后续增删改查操作里给每条数据做唯一标识的关键。如果你偷懒用数组下标当标识那删除一条之后再编辑第二条就会出现张冠李戴的诡异bug我后面会专门说这个坑。初期demo版本数据写死在代码里用三到五条假数据填充即可。紧接着你要考虑一个问题刷新页面数据就没了面试官如果问“能不能让数据刷新后还在”这就引出了localStorage。我建议直接在初次实现时就顺便把持久化做了代码量不大但展现出的工程思维完全不同。核心代码如下const STORAGE_KEY user-list const defaultList [ { id: 1, name: 张三, phone: 13800000001 }, { id: 2, name: 李四, phone: 13800000002 } ] const list reactive([]) function loadFromStorage() { const saved localStorage.getItem(STORAGE_KEY) if (saved) { const parsed JSON.parse(saved) list.splice(0, list.length, ...parsed) } else { list.splice(0, list.length, ...defaultList) } } function saveToStorage() { localStorage.setItem(STORAGE_KEY, JSON.stringify(list)) }这里用splice而不是直接list parsed是因为直接赋值会切断reactive代理页面不会更新。这个细节看起来很细但面试官极爱问。3.2 列表展示v-for配合key的正确姿势数据准备好后第一件事是把列表渲染出来。模板区域用v-for循环数组每一行展示姓名、电话、操作按钮。这里最大的考点是key你必须知道为什么不能用index以及用了index会引发什么问题。正常情况下代码是这种形态ul li v-foritem in list :keyitem.id {{ item.name }} - {{ item.phone }} button clickdeleteUser(item.id)删除/button button clickopenEdit(item)编辑/button /li /ul如果list是纯展示没有增删操作index当key没问题问题就在于这个demo有增删。假设你删掉了第一项index从0开始重新编号Vue会认为原来key为1的元素还在于是复用了旧DOM里面绑定的数据可能就错乱了。用item.id当key就能保证每个节点和具体数据一一对应让Vue的diff算法在精确位置上做更新。除了key还要考虑空列表的情况。列表没数据时可以给一个“暂无数据”的提示这是很多候选人会漏掉的边界体验但却是产品思维里很重要的一环。我一般用一个v-if来切换列表和空状态提示。3.3 新增表单与数据的接轨新增功能通常会做成一个表单弹窗也可以是顶部的一个横向表单区。题目没有限定形式我习惯用弹窗因为新增和编辑可以复用同一个组件体现代码复用的意识这也是面试官希望看到的。表单里的输入框通过v-model做双向绑定。我在这里踩过一次印象深刻把整个form对象直接赋给一个data字段但忘了表单数据也是响应式的结构结果点击新增页面没反应控制台也不报错。后来排查发现是绑定的字段名和list里字段名不一致phone写成了telphone。这种错误低级但很真实面试现场紧张时特别容易犯。所以我推荐把字段名统一定义成常量新建一个form对象保持字段列表和数据类型完全对齐。提交前一定要做校验。最简单的校验是每个字段不能为空复杂一点可以校验手机号格式。我不会在模板里堆一大堆判断而是单独写一个validateForm方法返回错误信息字符串或者布尔值配合一个错误提示区域。校验通过后再push进list同时调用saveToStorage持久化最后关闭弹窗并重置表单。还要注意一个问题如果新增的id用Date.now()生成快速连续点击两次提交时可能产生相同id所以更稳妥的做法是维护一个自增计数器或者基于已有列表的最大id加1。这个小坑我在模拟面试时专门拿来考过A同学他第一反应就是Date.now()我提醒之后他才恍悟。3.4 编辑回填与保存必须避开的浅拷贝陷阱编辑比新增复杂一点因为要把选中那一条的数据先回填到表单里。这里最大的坑就是直接赋值引发的引用共享问题。很多人会这么写function openEdit(user) { form user showDialog true }这个写法表面看着没问题弹出的表单也确实显示了当前数据但当你修改表单里的input时列表里对应的那行数据也同时变了因为你让form直接指向了同一个对象引用。这就是典型的浅拷贝陷阱。正确做法是使用扩展运算符复制一份function openEdit(user) { form { ...user } showDialog true editingId user.id }展开运算符只做了一层拷贝如果user对象里还有嵌套对象仍需要做深拷贝。这个demo的数据结构比较简单一层拷贝就够了但我面试时会顺带问一句“如果字段里还有address对象怎么办”能把深拷贝的思路说清楚也是加分项。保存时的逻辑也要理清楚如果editingId有值说明当前是编辑模式应该找到list里对应项把表单数据更新进去如果没有说明是新增。有两种实现方式一是用map生成新数组整体替换二是用splice定位替换两种都可以。我偏好先用findIndex找到索引再判断代码更直白也更符合面试官的阅读习惯。保存后同样要重新持久化并清除editingId。3.5 删除从单行删除到批量删除的思路演进最基础的删除就是在点击按钮时通过id找到对应项并从数组里移除function deleteUser(id) { const idx list.findIndex(item item.id id) if (idx -1) { list.splice(idx, 1) saveToStorage() } }这里要注意如果你一开始用list list.filter()的方式来删除同样会切断响应式。所以要么用splice要么把filter的结果作为实参传给splice再展开这与3.1节里的loadFromStorage写法是同一个原则。优秀一点的实现会加上删除确认避免用户误触。用window.confirm是最快的弹窗里确认也可以。面试如果只到这里顶多算及格想加分可以提一下批量删除列表前加一排checkbox选中的id收集到一个Set里顶部一个“批量删除”按钮遍历Set依次调用删除逻辑。批量删除不算复杂但它能展示你对常见业务场景的理解面试官会高看你一眼。还有一种思路值得聊就是“撤销删除”。实现方案是完全删除前先把快照存到一个变量里之后的恢复操作通过splice把快照重新插回去。这个功能虽然不是题目要求但如果面试时间充裕你主动提出来并实现绝对是一大亮点说明你不只关注功能完成还关注用户体验。4. 查询与筛选——把“查”从入门做到出彩4.1 computed实现实时模糊搜索题目里的“查”在不同面试官嘴里有不同的含义。有的人指的只是进入页面能读取出已有数据但大部分面试官会追加一个需求输入关键字列表实时过滤。我强烈建议你在demo里把这个功能直接做上因为用它才能引出Vue核心的computed特性。实现思路是在data里加一个searchText字段再通过computed生成一个过滤后的列表。模板里的v-for循环不再直接遍历原始list而是遍历这个计算属性filteredList。const searchText ref() const filteredList computed(() { const kw searchText.value.trim().toLowerCase() if (!kw) return list return list.filter(item { return item.name.toLowerCase().includes(kw) || item.phone.includes(kw) }) })关键字匹配时用toLowerCase比较稳妥否则用户输入大写字母时大小写敏感会过滤不到。手机号字段是纯数字字符串直接includes即可不需要转小写。面试官如果追问“为什么这里用computed而不是methods”你要能答出缓存机制computed会基于响应式依赖缓存计算结果只有searchText或list变化时才重新计算而methods每次渲染都会执行性能上有差异。这个差异在小demo里看不明显但换到大数据量列表上就很可观。4.2 多条件筛选与筛选状态保持如果能做模糊搜索那还可以再加几个筛选项比如下拉框按性别过滤、按部门过滤每个筛选项都是一个响应式字段computed里把所有条件串起来。这样做的好处是每个条件的处理逻辑都是相对独立的代码整体可读性也很好。筛选状态保持是容易被忽略的点。用户搜索了一个关键字然后点击编辑弹窗关闭后searchText还在列表依然处于过滤状态这是符合预期的。但如果用户在输入过程中误操作触发页面刷新searchText就丢了。想做得更完整可以把搜索关键字也同步到localStorage里刷新后重新读取。这个思路跟数据持久化一脉相承提出来会让面试官觉得你考虑问题很周全。还需要处理的结果是删除和筛选的嵌套关系。用户先输入“张”列表只有一行点删除后这一行消失了但关键字“张”还在于是页面上变成了“暂无数据”。很多候选人没注意这个交互以为出bug了其实逻辑是对的只是缺失了“清空筛选”的回退入口。所以在搜索框后面加一个“重置”按钮可以让这个体验闭环看似是一个小操作却是实打实的产品思维加分项。4.3 前端分页与虚拟滚动思路当数据量变大比如有上千条甚至上万条数据一次性渲染所有列表项会让页面卡顿。面试官很有可能顺着“查”的功能问你数据量大怎么处理。你需要准备两个层面的回答第一层是前端分页。把数据按每页10条或20条切成多页通过currentPage和pageSize计算当前页数据提供一个简单的上一页、下一页按钮。用computed来切片是标准做法。加上分页组件后demo的完整度会明显上升。第二层是虚拟滚动。如果数据量继续增大前端分页也不够用就需要只渲染可视区域内的那部分列表项配合固定行高计算start和end索引。Vue生态里有现成的第三方库也可以自己实现一个简版。面试时能把原理说出来就行监听滚动事件根据scrollTop计算起始索引只保留可视区域的DOM节点。我个人经验是把分页加上就够了虚拟滚动通过口述表达思路即可。不要在面试时为了炫技把demo写成巨型项目时间有限完成度比复杂度更重要。5. 常见问题与避坑实录——面试官会追问的细节都在这里5.1 高频问题速查表把这几年指导别人和自己在实际项目中遇到的问题整理成一张速查表面试前快速过一遍很有用。常见问题可能原因正确解法数组通过索引修改值页面不更新Vue 2响应式监听不到索引赋值用splice替换或升级Vue 3用Proxy给对象新增属性页面不更新Vue 2的Object.defineProperty无法感知新增keyVue 3直接支持Vue 2用$set删除后列表状态错乱key用的index节点复用导致数据绑定错位改用唯一id作为key点击编辑后列表数据跟着变form直接引用user对象浅拷贝用扩展运算符或深拷贝搜索框输入无反应过滤方法写成了methods且未绑定或者字段名大小写不一致改用computed并校验字段名刷新后数据丢失数据只存在内存数组里用localStorage持久化操作按钮事件失效v-for和v-show/v-if层级嵌套导致点击事件冒泡被阻断检查事件修饰符和DOM结构提交空数据也能成功缺少表单校验提交前校验非空和格式这张表里的问题每一个我都见过真实代码里的对应案例尤其是key和浅拷贝这两个坑面试现场翻车率最高。5.2 从“页面不更新”看Vue响应式原理“页面不更新”这个报错是面试官最喜欢往深处挖的点因为能从这里判断你是只知道API还是会讲原理。Vue 2的响应式本质是Object.defineProperty对对象的每个已有属性做getter/setter劫持所以当你给对象添加一个新属性时这个属性根本没有被劫持自然也就不具备响应式能力。数组的索引赋值也类似因为defineProperty无法监听“某个下标发生变化”这件事。Vue 3的响应式升级为Proxy它代理的是整个对象无论是新增属性还是修改数组索引都能通过get和set的拦截机制感知到所以很多Vue 2的hack操作到Vue 3里就不再需要了。回答到这个层面面试官基本就能判断你是真的理解而不是背概念。如果再追问“Vue 3为什么性能更好”除了Proxy本身效率更优外还有编译期的优化、静态树的提升、事件缓存等细节。你不用一次全说完但是要有话可讲这里特别能拉开差距。5.3 事件处理的几个隐蔽细节demo里的按钮都需要绑定click事件看起来简单但有几个隐蔽问题值得注意第一个是事件对象和参数同时传递的场景。比如你需要传item和$event模板里应该是clickdeleteUser(item.id, $event)不要把这个顺序搞反。第二个是事件修饰符的使用。表单框在弹窗里键盘按回车想触发提交可以用keyup.entersubmitForm如果不想让按钮的点击事件冒泡到父级用click.stop。这些细节在代码review时都很显眼。第三个问题是防抖。如果搜索框实时过滤用户打字很快时每次输入都触发computed重新计算。虽然计算量不大但如果你要配合请求后端接口就必须做防抖否则每敲一个字母就会发一个请求。我用过一个简单的手写防抖函数setTimeout加clearTimeout即可不需要引入第三方库。能主动提到并实现防抖面试官会认为你具备真实项目经验。6. 面试追问与加分项——从demo到offer的距离6.1 组件化拆分新增和编辑为什么可以共用一个表单我看到很多候选人写这个demo时新增一个组件编辑又一个组件代码重复率很高。更好的做法是拆成三个层级App.vue负责页面布局和数据状态UserForm.vue负责新增和编辑共用的表单UserTable.vue负责列表展示和操作按钮触发。子组件通过props接收数据通过自定义事件emit向父组件通信。拿编辑举例UserTable监听到编辑按钮点击后向父组件App发射一个edit事件App再把对应的user传回给UserForm子组件UserForm内部watch这个prop变化并把数据回填到表单。整个数据流是单向的页面状态清晰出了bug也好定位。面试时这种拆分方案一提出来就已经不是校招生的平均水平了。这里我额外说明vue的组件通信机制props是父传子emit是子传父如果需要跨多层组件就直接把关系理顺不要盲目引入状态管理工具。这个demo用不到Pinia或Vuex强行引入反而显得不够克制。6.2 生命周期与异步操作模拟真实接口请求真实项目中列表数据很少直接写在内存里多半是调后端接口获取。面试官可能会让你当场把demo改成模拟异步请求的版本。这里就要用到生命周期钩子onMounted在里面调用一个setTimeout或者Promise模拟请求然后赋值给list。我在模拟这种场景时有几个经常会问的点第一接口请求可能失败需要catch兜底并显示错误提示第二加载状态loading需要独立管理请求期间按钮要置灰防止用户疯狂点击第三如果组件销毁时请求还没返回会产生内存泄漏隐患需要配合onUnmounted清理定时器或中断请求。这些真实项目中必须面对的问题才是面试官想听的干货。在此基础上还可以把请求封装成一个获取列表的api函数把新增、编辑、删除也封装成对应的api函数。这样demo的结构就更像一个真实项目了面试官再追问“你怎么处理接口的loading状态”之类的问题你也可以根据封装方案直接展开。6.3 状态持久化与性能优化的扩展思路最后再聊聊这个demo可以做哪些扩展。持久化是我在前文就建议加上的如果你的时间实在不够至少把localStorage的读写代码写出来。后续还可以做一个批量导出JSON的功能用Blob生成文件下载这个功能代码量不大但在面试中效果极好。性能优化方面除了前面说的antishake和虚拟滚动还可以考虑使用v-memo或shallowRef来减少不必要的渲染父组件更新时子组件如果依赖的props没变可以用defineOptions里的inheritAttrs或配合memoization做局部优化。但要记住一点不要为了优化而优化你每提一个优化方案都要能说出它解决了什么场景下的什么问题。我在实际练习中最大的体会是这道增删改查demo的最终目标不是把功能写完而是让你在写的过程中把Vue的响应式原理、组件通信、生命周期、computed缓存机制、事件处理全部串成一条线。把这些点变成自己的语言储备再去面试你随口说出的任何一句技术细节都会被面试官视为“你真的做过”。希望这篇拆解能帮你在准备前端面试题的路上少走几个弯把这套经典demo做出自己的深度。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →