ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Vue3仿美团外卖实战:组件拆分与状态管理全复盘

Vue3仿美团外卖实战:组件拆分与状态管理全复盘 简介从零到一完成一个可面试的Vue实战项目核心不在于像素级还原UI而在于理解前端工程化的关键环节。组件拆分决定了代码的可维护性状态管理则直接影响复杂交互的可控性。以仿美团外卖APP为例整个项目覆盖了组件通信、路由懒加载、SKU规格选择、购物车持久化、接口Mock与axios拦截器封装等高频技术点。通过合理设计目录结构、统一分页接口返回格式、使用Pinia管理跨组件数据同步你能在开发过程中逐步建立工程化思维。这类C端项目业务链路完整、面试官认可度高适合作为前端求职作品。本文从项目选型到调试优化拆解每个模块的设计依据与踩坑记录帮助你不仅会写代码更能讲清楚背后的原理真正在面试中脱颖而出。 很多前端新手都会卡在同一个问题上视频看了不少教程跟了好多套但让独立做一个完整项目时依然不知道从哪下手。我这两年面试新人简历里十有八九写着“仿美团外卖”“仿饿了么”但能扛住追问的不到三分之一。原因很简单——只是照着别人源码敲了一遍没搞懂背后的模块拆分、状态设计、接口约定这些工程问题。所以这次我把自己的Vue仿美团外卖APP实战项目从头到尾复盘一遍不贴全量源码那没有意义而是把项目拆成“为什么要这么做、核心链路怎么设计、实际操作中踩了哪些坑、面试时怎么讲”这四个维度。无论你是刚学完Vue基础想找第一个完整项目还是准备春招秋招需要拿得出手的简历项目这篇都能直接当参考。1. 为什么拿“仿美团外卖”开刀项目定位与选型逻辑1.1 一个练手项目值多少钱技术圈有个常见误区以为项目越“高级”越好于是去仿后台管理系统、仿电商中台结果做完发现全是CRUD表格核心价值没体现出来简历也写不出亮点。仿美团外卖这种C端应用恰恰相反。它麻雀虽小五脏俱全覆盖了前端日常开发里最常遇到的几类场景定位与城市选择、搜索防抖、无限滚动列表、Tab栏切换、购物车状态共享、规格弹窗、订单流程、用户登录态管理。把这些串起来等于把Vue的组件通信、路由、状态管理、生命周期、插槽、指令全部实战了一遍。更重要的是这类项目面试官都熟。你一说“仿美团外卖”对方脑子里立刻有一个预期模型知道应该问你什么也方便你主动引导话题——这是其他冷门项目比不了的。1.2 技术栈选型的真实依据这个项目我用的是Vue 3 Vite Vue Router Pinia Axios Sass。选这套组合的原因很简单Vue 3 Composition API现在企业新项目基本都切到Vue 3了组合式API在逻辑复用上比Options API强太多比如购物车逻辑抽成一个useCart()方法多个组件共用代码量直接少一半。Pinia而不是VuexPinia对TypeScript支持更好API更简洁写起来几乎没有模板代码。Vuex 4虽然也能用但从学习成本角度讲Pinia更适合作业周期短的项目。Vite而不是WebpackVite冷启动速度是秒级的开发时改代码热更新几乎无感。你要是用Webpack启动一次等半天心态容易崩。移动端样式处理上我用了rem flexible方案视觉稿按750px设计直接写px编译时自动转rem。如果你想把项目更快跑起来也可以直接用viewport单位。1.3 整体目录结构怎么搭目录结构不一定要多花哨但要保证“页面、组件、状态、接口、工具”五层清晰分离。我实际用的结构是这样的src/ api/ // 接口请求封装 assets/ // 静态资源 components/ // 公共组件 router/ // 路由配置 store/ // Pinia状态 utils/ // 工具函数 views/ // 页面级组件 App.vue main.js这里有个小建议不要在views里直接写请求逻辑。很多新人习惯在页面里直接import axios然后调接口短demo没问题但页面一多就乱了。统一走api/目录每个接口函数名字起得清晰一点如getShopDetail(shopId)后期维护和面试讲起来都舒服。2. 首页与店铺列表组件拆分决定开发效率2.1 首页的楼层结构首页是整个App的门面也是最容易做成“一坨”的地方。我的做法是把它拆成四层每一层独立成组件顶部搜索栏固定在页面顶部点击跳转搜索页中间加了一个防抖的实时搜索建议。轮播图用swiper实现请求接口拿banner列表渲染。金刚区导航就是那种一排排图标的快捷入口比如美食、外卖、超市、药房。这里用grid布局两行数据从接口返回图标用字体图标。推荐店铺列表一个垂直滚动列表拉到接近底部时自动加载下一页。轮播图有个特别容易踩的坑swiper版本不同初始化方式差异很大。老项目用swiper 4/5是new Swiper(.swiper-container, {...})新版swiper 8/9是模块化引入还要单独引入Pagination等模块。如果你直接复制网上旧代码大概率白屏半天才发现是版本问题。2.2 店铺卡片组件的取舍店铺卡片是首页高频复用的组件我做成了ShopCard.vue接收一个shop对象展示店名、评分、月售、起送价、配送费、距离、活动标签。这里有三种实现思路我实际对比下来差异很大方案优点缺点结论写死在首页组件里简单直接无法复用代码爆炸不推荐抽取ShopCard组件props传值复用性好逻辑清晰需要预先设计props结构推荐用render函数动态生成灵活适合复杂场景难维护模板可读性差不推荐我在实际项目中采用第二种。props传值时有个细节美团接口返回的店铺活动是个数组每条有type枚举值满减、折扣、新客立减等和content描述文案。页面上需要用不同的背景色标识不同类型。这个映射逻辑放在ShopCard内部用computed处理而不是在父组件里拼好再传这样卡片保持高内聚其他页面拿过来直接用。2.3 骨架屏和滚动加载的体验优化首页接口慢的情况下直接白屏会让人觉得App卡死。我在店铺列表和商品列表里加了骨架屏不是UI框架那种复杂库就是自己写个简单Skeleton组件放几张灰色的占位块等接口返回后给一个v-if切换。列表滚动我用的是onMounted里绑定window的scroll事件计算scrollTop clientHeight scrollHeight - 200时触发加载更多。这里有一个很多人忽略的性能问题scroll事件触发频率极高必须做节流。我用throttle(fn, 200)处理了一下下拉加载瞬间的重复请求问题也顺手解决了。如果不做节流快速滑动时可能一次性发出七八个重复请求接口被刷爆不说页面还会出现重复数据。3. 点餐页与购物车状态管理才是这个项目的心脏3.1 双栏联动的页面设计点餐页是仿美团外卖里最有技术含量的一页也是面试官最爱深挖的一页。它分成左栏分类列表和右栏商品列表左右滚动需要联动。经典做法是左侧分类列表监听右侧滚动位置根据当前滚动到的商品所属分类高亮左侧对应的分类项。右侧商品列表点击左侧某个分类右侧滚动到对应分类的锚点位置。右侧滚动定位我用的是scrollIntoView简单直接。但这里有个坑——scrollIntoView会滚动到浏览器窗口的最近滚动祖先如果你的外层容器不是真正的滚动容器它会带着整个页面一起滚表现很诡异。我当时排查了半天才定位到是滚动容器嵌套问题。解决方案是给右侧列表设overflow-y: auto并且给要滚动的商品分组元素加id然后document.getElementById(category-${index}).scrollIntoView({ behavior: smooth, block: start })如果是复杂项目建议用better-scroll这类库处理它内部对滚动容器做了很完善的兼容处理省去不少踩坑时间。但要注意使用库也要想清楚它的滚动原理否则出了问题更不好排查。3.2 购物车状态的数据结构设计购物车是这个项目最核心的状态我的做法是用Pinia维护一个cart模块核心数据结构长这样{ shopId: 12345, // 当前购物车所属店铺 items: { sku_001: { // 商品SKU作为key方便查找 productId: p001, skuId: sku_001, name: 原味奶茶, price: 12, count: 2, specs: { 温度: 少冰, 糖度: 五分甜 }, image: xxx.jpg } }, totalCount: 2, totalPrice: 24 }这里有几个需要认真思考的问题为什么用对象而不是数组购物车变动非常频繁加、减、改规格如果用数组每次要find去遍历复杂度O(n)用对象以skuId为key增删改都是O(1)性能更好代码也简洁。渲染时再用Object.values(cart.items)转成数组完全够用。加购逻辑怎么写每次加购时先判断skuId是否存在存在就count1不存在就新建一条。同时要判断当前商品的库存上限加到上限后Toast提示。购物车携带店铺信息做什么美团这类App的购物车是有“店铺归属”的切换店铺后原购物车清空。用shopId字段做隔离后续加购物车商品时先判断shopId是否一致不一致就先清空再添加。这个逻辑经常被初学者漏掉但它在真实业务里非常重要。要不要存localStorage我的答案是要。用户加了几样商品切到别的页面再回来购物车状态还在体验会好很多。用store.$subscribe监听变化自动写入localStorage刷新后重新hydrate核心代码就几行收益却很大。3.3 规格弹窗与SKU选择的细节点餐时选规格份量、温度、糖度是一个典型的SKU场景。我做了一个半屏弹窗组件点商品时先判断hasSpecs字段没有规格就直接加购有规格才弹出选规格。规格选择里的坑主要在两个地方一是规格联动。比如选了“大杯”糖度选项里可能有几个不可选因为大杯默认无糖选了“热饮”冰度选项直接置灰。这种联动的核心是配置规则我在项目里用了一个简化方案——每个商品配置一个disables字段描述哪些SKU组合不可选组件内部维护一个selectedSpecs对象每次选择后遍历规则校验不可选的置灰。二是弹窗滚动穿透。蒙层弹窗打开后背后的页面依然可以滚动体验很差。解决方法是给body加overflow:hidden关闭时去掉。要在onMounted和onUnmounted里分别处理别写成全局一次性就完事。4. 接口请求与Mock数据没有后端也能跑通完整链路4.1 为什么用json-server Mock.js组合真实面试里很多项目死在“没有后端接口”上前端只能写死数据。我建议的做法是本地起一个json-server模拟RESTful接口再用Mock.js生成随机数据。json-server读取一个db.json文件自动生成/api/shops、/api/shops/:id、/api/products这类RESTful接口支持分页参数_page和_limit。Mock.js负责随机生成店名、评分、月售、商品图片等数据避免你手动造几十条假数据。这样做的好处是你的项目从开发第一天起就是“走接口”的换到真实后端时只需要改axios的baseURL其他业务代码一行不用动。很多小公司前端就是这么跟后端联调的你提前熟悉这套流程入职就能直接上手。4.2 axios封装与拦截器的必要性如果只用axios默认配置请求/响应处理逻辑会散落在各个页面。我封装了一个request.js核心做了三件事baseURL统一配置开发环境指向JSON Server生产环境指向真实域名。请求拦截器自动从localStorage读取token加到请求头Authorization字段。响应拦截器统一解包数据只返回res.data.data捕获403、500等状态码统一弹Toast提示并跳转登录页。有人会觉得“这点东西封装个啥”但这不是炫技而是工程习惯。你的项目如果每个页面都自己处理错误提示、自己存token面试官问“并发请求里一个token失效其他请求怎么办”时你只能说“不知道”——因为你在真实项目里没踩过这个坑。4.3 接口数据结构设计的经验谈前端做Mock时最容易忽略的就是数据结构设计。我一开始犯过懒直接把商品列表返回成数组后面加规格、加活动时发现没法扩展被迫重构了一次。建议所有列表接口统一返回分页结构{ code: 0, message: success, data: { list: [], total: 100, page: 1, pageSize: 10 } }单个实体接口统一返回{ code: 0, message: success, data: { ... } }这样做的价值是无论ShopCard还是订单列表、搜索结果拿到数据后的处理逻辑完全一致代码高度统一不容易出bug。结构设计的功夫花在前面后面能帮你省两三天的联调时间。5. 从能跑到能面试调试、性能与项目包装的差距5.1 开发调试中的几个必查项项目能跑起来只是第一步开发调试过程中的规范性决定了你上线后会不会被线上bug折磨。我从这个项目里学到最痛的一课是移动端点击延迟和300ms点击穿透。PC端预览时一切正常一放到手机真机预览点店铺卡片经常没反应要么点一下弹了两次。解决方案是引入fastclick或使用touchstart事件配合处理。现在是2026年了大部分现代浏览器已经修复了这个300ms延迟问题但如果你还在用老版本内核的WebView那就必须处理。另一个必查项是iOS安全区。iPhone X以后的机型底部有Home Indicator页面底部如果放了一个“去结算”固定按钮就会被Home Indicator挡住一块。处理方式是用viewport-fitcover env(safe-area-inset-bottom).settle-bar { padding-bottom: env(safe-area-inset-bottom); }这个细节在PC上根本看不出来但真机一打开就非常明显面试时主动提这个很能体现你做过移动端适配。5.2 顺手能做掉的性能优化我在这个项目里做了几处不复杂但很加分的性能优化路由懒加载component: () import(/views/Home.vue)首屏只加载首页需要的JS其他页面按需加载。Vite天然支持收益很大一行代码的事。图片懒加载店铺列表和商品列表的图片用v-lazy指令滚动进入视口才加载。用VueUse的useIntersectionObserver也行本质都是IntersectionObserver。列表项复用v-for渲染时一定要带:key且key要用唯一稳定的值不要用index。用了index做key列表中间插一条数据时Vue复用了错误的DOM节点会出现输入框内容串位、图片闪烁等诡异问题。计算属性缓存购物车总价、总数量这类派生数据用computed而不是在模板里调方法。模板里调方法每次渲染都会重新计算computed有缓存性能好得多。最后一个组件级缓存首页和点餐页之间来回切换时希望页面滚动位置不丢失我给这两个路由页面包了一层keep-alive。但注意加缓存后onMounted只在第一次进入时触发后续进入需要监听onActivated来刷新数据。这里踩过一次坑——缓存了详情页结果每次进去都是旧数据后来在onActivated里重新拉接口才解决。5.3 面试时项目经验怎么讲项目做完了简历也写了“Vue仿美团外卖APP”但面试时不能只扔出去一句“我用Vue写了一个仿美团的外卖项目”。面试官追问的第一个问题八成是“这个项目的难点是什么你怎么解决的”我的建议是准备两到三个深度技术点每个都能讲两分钟以上。这个项目里我挑的点是购物车模块的状态设计为什么用对象不用数组、怎么处理店铺隔离、怎么做localStorage持久化。这个点能体现你数据结构设计和状态管理能力。双栏滚动联动滚动容器怎么定位、scrollIntoView的坑、为什么不用复杂库。这个点能体现你对DOM和布局的理解。移动端适配处理rem/flexible、安全区适配、click 300ms延迟。这个点能体现你做过真实移动端兼容。还有一个实战建议把项目的Git提交记录留好。面试官问“你项目开发了多久”时如果你说“两周”但Git记录只有五六条提交对方很容易怀疑项目含金量。养成按功能点拆分提交的习惯每次提交写清楚message比如“feat: 完成点餐页双栏联动”“fix: 修复购物车店铺切换数据残留”这本身就是工程素养的体现简历和面试都会用到。最后再分享一个小技巧仿项目的精髓不是复制像素级还原而是把业务链路跑完整。美团外卖里最难的不是UI是“点餐页加了购物车购物车角标要同步更新商品详情页改了规格列表页的SKU信息也要跟着变下单成功后购物车自动清空”——这些跨组件状态同步的思路才是你做完这个项目后真正沉淀下来的核心资产。能把这些说清楚这个仿项目的价值就完全体现出来了。本文还有配套的精品资源点击获取
返回列表