尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue3 + Vite 实战:从脚手架到部署的现代化前端工程化指南

Vue3 + Vite 实战:从脚手架到部署的现代化前端工程化指南 这几年不管是新开项目还是老项目重构我基本都绕不开同一个选择用 Vue3 还是 Vue2用 Vite 还是继续啃 Webpack。去年我们团队把一套维护了两年的后台管理系统从 Vue2 Vue CLI 迁移到 Vue3 Vite冷启动从原来的 20 多秒直接掉到 2 秒左右热更新基本是毫秒级响应整个研发体验完全换了一档。这也是我今天想认真聊聊 Vue3 Vite 搭建现代化前端项目的原因。这篇文章主要面向三类读者一是刚接触 Vue3、想从零搭一个可用工程的初学者二是打算从 Vue2 和 Webpack 迁移到新工具链的团队三是正在准备 Vue3 面试或接手开源后台项目想对整体工程体系做一次系统梳理的开发。我会把脚手架搭建、工程化配置、状态管理、路由懒加载、组件场景、部署上线、常见问题都过一遍期间穿插我踩过的坑和排查思路保证内容能直接落地到你自己的项目里。1. 从 Vue CLI 到 Vite为什么我换掉 Webpack 方案1.1 冷启动慢的痛点与 Vite 的取舍以前用 Vue CLI 创建的项目Webpack 在 dev server 启动时会从入口开始把所有模块全部编译一遍。项目小还好一旦组件和路由上了规模随便一个中后台项目冷启动十几秒是常态改一行代码触发热更新也要等一两秒连续几次保存下来整个人的节奏都被打断了。Vite 的出现把这个问题基本解决了。它开发模式下利用浏览器原生的 ES Module先通过 esbuild 把 node_modules 里的依赖预构建成缓存业务代码则不做整体打包而是按需编译。浏览器请求哪个模块Vite 就实时编译哪个模块所以冷启动特别快热更新也精准到文件级。打个比方Webpack 像把整本书先全文翻译好再拿给你读Vite 则是翻到哪一页译哪一页前期省掉大量无用功。这一点在大项目中感受尤其明显。不过 Vite 也不是银弹。开发模式下如果项目模块依赖图特别大首次打开页面时浏览器要并发请求很多小模块可能出现一段“请求风暴”表现为首屏白屏时间比 Webpack 还长。解决办法也比较常规配置依赖预构建、推动路由懒加载、拆分页面组件避免把无关代码塞进同一个入口。1.2 现代化前端项目的“标配能力”所谓“现代化”在我理解里并不是工具的堆砌而是有一套能明显提升开发和协作效率的工程组合。现在我们在 Vue3 项目里常规配齐的能力大概是这样的Vue3 组合式 API 组合式函数Composables做逻辑复用Vite 承担开发服务器和构建替代 WebpackVue Router 4 管路由和页面级懒加载Pinia 管全局状态替代 VuexTypeScript 按需引入至少把接口和公共类型定义起来ESLint Prettier 统一代码风格按需引入 UI 组件库比如 Element Plus、Ant Design Vue、Naive UI。这套组合的好处是组合式 API 让跨组件逻辑可以抽成独立的函数复用Pinia 去掉了 Vuex 里繁琐的 mutationsVite 又让开发阶段跑得飞快。三者配合写业务代码的体验比两三年前顺太多了。如果你团队里没人用过 TypeScript不必一步到位可以先用 JS 搭好工程后续在接口层和工具函数上逐步加类型。强行全量上 TS 反而容易在初期拖垮节奏。1.3 适用场景与不适用场景Vue3 Vite 最适合的场景首先是全新的中后台管理系统其次是 PC 端 H5 和电商前台这类 Vue3 生态覆盖很好的项目还有一些文档站、小工具站也完全够用。但有些情况需要谨慎。比如项目里依赖大量老旧的 CommonJS 插件且这些插件长期不维护Vite 构建时可能反复报警告再比如业务要求兼容 IE 或很老的 WebView那就需要额外引入 vitejs/plugin-legacy 做降级构建产物会明显变大。超大型单体仓库如果团队完全没有 ESM 经验迁移成本也要认真评估。所以我个人的建议是全新项目直接上 Vue3 Vite老项目如果只是维护需求就别折腾迁移如果有重构窗口迁移的收益会非常明显。2. 环境准备与脚手架搭建2.1 Node 版本与包管理器选择搭建 Vue3 Vite 项目前先确认本机 Node 环境。目前 Vite 的高版本普遍要求 Node 18 及以上建议直接安装最新的 LTS 版本。如果你需要同时维护多个 Node 版本Windows 上可以用 nvm-windowsmacOS 或 Linux 用 nvm切换版本很方便不需要反复卸载安装。包管理器方面npm、pnpm、yarn 都行。我个人更推荐 pnpm它通过全局内容寻址存储和项目内硬链接依赖安装速度快磁盘占用低。不过 pnpm 对依赖的“幽灵依赖”问题管控更严格偶尔会遇到一些设计不规范的包在安装或运行时出问题。如果你不想在依赖处理上耗时间直接用 npm 也完全没问题Vite 对 npm 的支持同样很成熟。2.2 使用 create-vue 初始化项目Vue 官方推荐使用 create-vue 来创建项目命令如下npm create vuelatest my-project执行后会出现一系列交互选项比如是否使用 TypeScript、是否启用 Vue Router、Pinia、ESLint、Prettier 等。建议按项目实际需要选择中后台项目一般把 Router、Pinia、ESLint 和 Prettier 都开上TypeScript 看你团队情况。这里要区分 create-vue 和 create-vite。create-vue 是 Vue 官方定制的脚手架集成了 Vue Router、Pinia、ESLint 等最佳实践create-vite 是 Vite 官方更基础的模板拿到手只有 Vite Vue 的最小骨架路由和状态管理都要自己装。如果你希望从一开始就有一个结构完整、适合团队协作的工程直接用 create-vue 更省事。项目创建完成后cd my-project npm install npm run dev默认访问 http://localhost:5173 第一次启动时如果控制台出现 Optimizing dependencies说明 Vite 正在预构建依赖这是正常现象。依赖预构建完成后后续启动速度会更快。2.3 目录结构与基础文件解析create-vue 生成的目录结构非常清晰my-project ├─ index.html ├─ vite.config.ts ├─ src │ ├─ main.ts │ ├─ App.vue │ ├─ assets │ ├─ components │ ├─ router │ ├─ stores │ └─ views很多人第一次接触 Vite 会觉得奇怪为什么入口不是 src/main.ts而是一个放在根目录的 index.html因为 Vite 是以 index.html 作为应用的入口浏览器先加载这个 HTML再由里面的script typemodule src/src/main.ts拉起整个应用。这意味着你可以在 index.html 里直接添加第三方 CDN 脚本、设置页面 title、引入系统字体等灵活度很高。main.ts 里最核心的是这一串逻辑import { createApp } from vue import { createPinia } from pinia import router from ./router createApp(App) .use(createPinia()) .use(router) .mount(#app)createApp 创建应用实例后通过 .use 注册 Pinia 和 Router最后挂载到 id 为 app 的 DOM 上。如果后续还要接 UI 组件库、国际化插件也都在这个入口里统一注册。3. 工程化配置别名、环境变量与开发代理3.1 路径别名别再写一串 ../../../组件层级一深相对路径引用会变成噩梦。比如在src/views/system/user/components/UserForm.vue里去引入src/utils/format.ts写../../../utils/format确实能跑但项目一改目录结构整个文件里的 import 路径全断。Vite 的别名配置集中在 vite.config.tsimport { defineConfig } from vite import vue from vitejs/plugin-vue import { fileURLToPath, URL } from node:url export default defineConfig({ plugins: [vue()], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } } })后面所有文件里就可以用/utils/format代替那串相对路径了。需要注意如果项目用了 TypeScript还要在 tsconfig.json 里同步配置 paths否则 IDE 和构建阶段会报找不到模块{ compilerOptions: { baseUrl: ., paths: { /*: [src/*] } } }每次配置完别名记得重启 dev serverVite 对配置文件的变更不会自动热更新。3.2 环境变量.env.development 与 .env.production 的正确用法不同环境使用不同接口地址是前端项目最基础的需求。Vite 约定根目录下可以放.env、.env.development、.env.production文件启动脚本不同Vite 会自动加载对应文件。比如.env.developmentVITE_APP_TITLE开发环境 VITE_API_BASE_URL/api.env.productionVITE_APP_TITLE生产环境 VITE_API_BASE_URLhttps://api.example.com代码里通过import.meta.env.VITE_API_BASE_URL读取打包时会自动替换成对应环境的变量值。这里最关键的一条规则是只有以 VITE_ 开头的变量才会暴露到客户端。这套机制是为了防止你把服务端密钥、数据库地址等敏感配置误打进前端包。只要变量名不带 VITE_ 前缀就只能在 vite.config.ts 这类 Node 环境里使用。如果你还需要测试环境可以用vite build --mode test同时创建.env.test。这个模式参数同时会决定构建时 NODE_ENV 和加载的环境文件是 Vue3 项目里区分预发布和生产构建的常用做法。3.3 开发代理与 Spring Boot 后端联调前后端联调时最常见的问题就是跨域。前端跑在 5173Spring Boot 后端跑在 8080直接发请求会被浏览器拦截。正规做法是开发阶段统一走代理生产阶段由 Nginx 反向代理而不是在 axios 里写死一个完整的后端地址。配置 Vite 代理server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } }如果后端的接口实际路径是/users/list前端统一请求/api/users/list开发代理会把/api前缀去掉后转发给 8080Spring Boot 收到的是/users/list不用改后端任何代码。记录一个非常常见的协作场景前端请求/api/users/list得到 404后端同事说接口明明通着。这时候先看代理是否生效、请求落到了哪个端口别急着怪后端。前端可以在 Network 面板看请求 URL如果显示的还是 localhost:5173/api/users/list说明代理配置没有生效或者 dev server 没有重启。生产环境就不要再依赖 Vite 代理了那是纯开发阶段的便利设施。部署时由 Nginx 做同样的事情前端代码里的 baseURL 可以保持/api不变。3.4 构建优化与产物分析当项目进入发布阶段npm run build会把 Vue 项目打包成 dist 目录。但构建不只是敲一个命令那么简单几个配置直接影响线上性能和出问题的概率。第一路由组件必须懒加载。把页面级组件拆成独立的 chunk用户访问哪个页面才加载哪个页面首屏体积会小很多。第二第三方包可以做分块把 vue 全家桶、组件库、图表库分开避免所有依赖挤进一个超大 JS 文件。Vite 配置示例build: { rollupOptions: { output: { manualChunks: { vue: [vue, vue-router, pinia], ui: [element-plus], charts: [echarts] } } } }不过不要为了拆而拆HTTP/2 下过多的 chunk 反而增加请求数。我的经验是把更新频率明显不同的依赖拆开就行具体按你项目的实际体积来。第三部署在子目录时必须设置base: /subpath/否则打包出来的资源路径会是绝对的 /assets/xxx放到子目录下全部 404。另外可以使用 rollup-plugin-visualizer 生成构建产物体积报告哪个模块占了几百 KB一眼就能看出来。后面第 6 章我会专门聊部署细节。4. 状态管理与路由入场Pinia 和 Vue Router4.1 Pinia 为什么比 Vuex 更适合新项目Vuex 用了这么多年核心流程是 state - getters - mutations - actions强制同步提交 mutation逻辑严谨但写起来繁琐。Pinia 直接砍掉了 mutations你可以在 actions 里直接修改 state代码量少了心智负担也小很多。Pinia 另一个优势是设计更扁平不再有 module 嵌套的概念。每一个 store 都是独立的 defineStore 定义你可以按业务模块拆分比如用户信息、权限、购物车、订单每个 store 各管一摊组件里按需引用// stores/user.ts import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: , name: }), actions: { setToken(token: string) { this.token token } } })组件里这样使用import { useUserStore } from /stores/user import { storeToRefs } from pinia const userStore useUserStore() const { name } storeToRefs(userStore)注意解构 state 时要用 storeToRefs直接const { name } userStore会让 name 失去响应式改动了页面不会刷新。这个坑我至少见过三次。Pinia 还支持 Composition API 风格的 setup 写法和 Vue3 组合式 API 更融合状态定义可以像写一个 setup 函数一样自由。4.2 computed 与响应式思维Vue3 的响应式底层换成了 Proxy不再像 Vue2 那样只能劫持对象已有的属性。新对象、数组索引变更、嵌套对象读取在 Proxy 下都会被拦截依赖追踪更完整也省去了 Vue2 里各种$set的写法。computed 是响应式系统里最常用的工具。它接收一个 getter返回一个会被缓存的计算值只有依赖的数据真正变化时才重新计算。购物车价格就是一个经典场景const cartList ref([ { name: 商品A, price: 100, count: 2 }, { name: 商品B, price: 50, count: 1 } ]) const totalPrice computed(() cartList.value.reduce((total, item) total item.price * item.count, 0) )只要 cartList 不变反复读取 totalPrice 不会重复求值。这种缓存特性对性能有实际价值千万别在模板里写一个函数来承担计算职责函数每次渲染都会执一次。还有一点容易忽略computed 应该是纯函数不要在 getter 里发请求、改别的状态、操作 DOM。需要监听变化并做副作用时用 watch 或 watchEffect 更合适。4.3 Vue Router 4 与 keep-alive 的实际配合Vue Router 4 配合 Vue3路由表写法相比 Vue2 变化不大import { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /, name: home, component: () import(/views/Home.vue) } ] })component 用() import(/views/xxx.vue)就是页面级懒加载只有访问该路由时才会加载对应 JS chunk。在后台管理系统里keep-alive 是使用频率很高的能力。表格页跳详情页再返回不缓存的话之前填写的搜索条件和当前页码全丢了router-view v-slot{ Component } keep-alive :includecachePages component :isComponent / /keep-alive /router-viewcachePages 是组件 name 组成的数组注意这里匹配的是组件的 name不是路由表的 name。如果你只写了路由 name却发现 keep-alive 不生效多半是组件里没有定义 name 属性。在对应页面组件中可以这样显式声明defineOptions({ name: UserList })defineOptions是 Vue3.3 以后支持的宏可以安全地给 SFC 组件设置 name。5. 组件开发与场景扩展5.1 用 defineComponent 和 JSX 打破 SFC 的边界模板方式SFC在绝大多数场景下已经够用可一旦遇到高度动态化的组件比如自定义表格列渲染、动态表单字段、树形控件节点模板里写起来会比较别扭。这时候可以考虑用 defineComponent 配合 JSX。Vue3 里使用 JSX 需要安装官方插件npm install vitejs/plugin-vue-jsx -D然后在 vite.config.ts 的 plugins 里加上。一个简单示例比如根据后端下发的动态配置渲染表单字段import { defineComponent, ref } from vue export default defineComponent({ setup() { const count ref(0) return () ( button onClick{() count.value} {count.value} /button ) } })JSX 的好处是可以用完整的 JavaScript 表达式做条件判断、循环、函数回调非常适合渲染函数和高度动态化的组件。代价是模板的可读性会比 SFC 弱一些TypeScript 中 JSX 的类型报错也不如模板直观。我的建议是普通页面和业务组件继续用 SFC只在通用组件内部对外暴露灵活的 render 能力时才使用 JSX 或渲染函数。5.2 列表懒加载与大数据量渲染列表懒加载解决的是“什么时候加载更多”的问题。通常在滚动容器底部放一个哨兵元素当它进入视口时触发加载。原生 IntersectionObserver 就够用了const loadingRef refHTMLElement | null(null) const page ref(1) const list refItem[]([]) onMounted(() { const observer new IntersectionObserver((entries) { if (entries[0].isIntersecting !loading.value) { page.value 1 loadList() } }) observer.observe(loadingRef.value) })但懒加载解决不了“渲染数量过多”的问题。如果一次性要展示几千上万条数据即使接口只请求一次DOM 数量太大一样会卡。这时候需要虚拟滚动只渲染可视区域内的行vue-virtual-scroller 在 Vue3 里有对应的适配版本也可以自己实现固定高度行的虚拟列表。一个很容易被忽视的点接口返回的数据未必直接能用需要前端做清洗、排序、去重。不要把接口原始响应体直接塞进响应式 state最好在进入 state 前先映射成页面真正需要的结构后面排查问题会轻松很多。5.3 iframe、打印模板和可视化大屏的特殊处理中后台项目里经常要嵌套第三方系统或老页面iframe 是最常见的方案。但嵌入式场景有几个大坑其中一个热搜词提到的“iframe 外层 div 点击事件无法触发”我印象很深。原因是 iframe 是一个独立的文档区域鼠标一旦进入 iframe事件就被 iframe 内部文档消费了外层父级文档根本感知不到。外层 div 的 click 监听自然也不会触发。常用处理思路有三种如果只是展示页面不需要用户和 iframe 交互可以给 iframe 加pointer-events: none让鼠标事件穿透外层就能正常接收点击如果需要用户操作 iframe可以通过监听 window 的 blur 事件感知用户从 iframe 切回外部页面更复杂的场景可以通过 postMessage 在 iframe 内外通信让内部页面主动通知外部当前状态。打印模板是另一个高频需求。写打印功能时思路不要停留在“整页截图”而是用 CSSmedia print控制打印样式隐藏侧边栏和菜单只保留内容区再用page设置纸张大小和页边距。如果打印内容比较复杂可以考虑成熟的 Vue3 打印库或通过 iframe 加载一个独立模板再调用打印。关键点是要在生产环境先测一遍Chrome 的打印预览和实际打印结果经常有细微差别。可视化大屏方面Vue3 Vite 非常合适ECharts 按需引入即可。大屏项目要注意 resize 处理窗口变化时调用图表实例的 resize 方法组件卸载时记得 dispose防止内存泄漏。如果大屏要对接实时数据MQTT 或 WebSocket 是常用来做即时推送的方案具体要看数据源类型。6. 构建、部署与前端代码的可逆性6.1 构建产物与资源路径检查npm run build执行完成后dist 目录就是线上要发布的内容。上线前先检查几个东西能避免大部分低级故障。第一打开 dist/index.html确认 script 和 link 标签里的资源路径是否符合部署路径。如果部署在域名根路径默认base: /没问题如果部署在https://example.com/admin/这种子目录必须把 base 设置为/admin/。第二检查打包后的 JS、CSS 文件名。Vite 默认会给产物加 hash文件名变化说明缓存更新没问题。如果发现文件名没带 hash很可能是配置中build.assetsDir或文件名生成函数被修改过可能导致浏览器缓存旧文件。第三建议在部署前用rollup-plugin-visualizer看一下产物体积。如果某个 chunk 超过 1MB就要考虑是否把大依赖比如 echarts、富文本编辑器单独拆分或改成按需引入。6.2 Nginx 部署与 history 路由回退Vue Router 使用 createWebHistory 时前端路由是 history 模式直接访问https://example.com/user/list时服务器找不到这个路径会返回 404。所以 Nginx 必须配置 try_files 回退到 index.htmlserver { listen 80; server_name your-domain.com; root /usr/share/nginx/html/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; } gzip on; gzip_types text/plain text/css application/javascript application/json; }try_files 的作用是请求一个路径时先看有没有对应的文件没有就找目录再找不到就回退到 index.html。因为前端代码在 index.html 挂载后会自动解析 URL所以刷新页面时路由能正确恢复。如果部署在 Windows 服务器上Nginx 配置原理一样但要注意路径分隔符、配置文件编码不要带 BOM否则启动时可能报错。修改完配置用nginx -t先检查语法再 reload。生产环境的接口转发一定是在 Nginx 层做不要在浏览器里直接请求后端地址。除了避免跨域还能隐藏后端真实端口顺便可以在 Nginx 层做一层简单缓存和限流。6.3 前端代码保护source map 与压缩混淆网上经常有人聊“vue项目反编译探索前端代码的可逆性”这个话题对前端开发者来说其实很值得正视。前端代码上线后就以静态文件形式公开任何人都可以在浏览器开发者工具的 Sources 面板里看到加载的 JS 文件。压缩后的代码虽然变量名变短但业务逻辑基本可读。如果生产环境还开放了 source map那更是等于把源码目录结构直接送给别人。所以最佳实践是生产环境构建时把build.sourcemap设为 false不是开发环境调试就不需要生成 source map密钥、接口调用凭据、加密私钥等绝不写进前端代码敏感的业务逻辑尽量放在后端前端只保留展示和交互权限控制必须依赖后端接口校验不能靠隐藏按钮来保证安全如果公司对代码保护有要求可以引入混淆插件提高阅读门槛但混淆只是增加逆向成本不是真正的加密。我自己调试线上问题时会区分场景测试环境开 source map 方便排查生产环境靠日志和监控定位问题线上开放的源码越少越好。前端代码的可逆性提醒我们一个事实任何放在浏览器的代码都不算秘密关键数据的安全边界永远在后端。7. 常见问题与排查技巧实录7.1 一个让我印象深刻的 Edge 浏览器问题有个用户反馈说系统在 Edge 浏览器里打开右上角的最小化按钮有时候无法点击。一开始我也以为是浏览器问题但用户说别的网站都正常只有我们这个项目复现。后来打开开发者工具检查页面覆盖层发现是某个全局组件使用 fixed 定位层级很高而且宽度设置成 100vw在特定窗口尺寸下遮住了浏览器原生按钮的区域。这种问题很隐蔽因为开发时很少会刻意去点浏览器右上角而且它的出现跟窗口大小、缩放比例强相关。排查思路大概是先复现确认是页面元素遮挡还是浏览器扩展影响打开开发者工具检查 titlebar 附近是否有宽高极大的遮罩层逐个关闭全局样式里 top: 0、z-index 超过 100 的定位元素看哪个是元凶让用户禁用浏览器扩展再试排除第三方插件干扰。现代前端项目要处理的不只是 Vue 组件本身全局样式、弹层组件、浏览器默认 UI 之间都可能互相干扰。遇到这种问题重要的是先缩小范围而不是一上来就怀疑浏览器坏了。7.2 跨平台与兼容性旧系统、旧浏览器的现实问题Vite 的新版本对 Node 版本要求比较高而 Windows 7 这类老系统能支持的 Node 版本上限又很低。如果团队里有人还在 Win7 上办公直接用最新 Vite 大概率会启动失败。这种情况一般三条路升级操作系统或者换一台开发机使用兼容旧 Node 的较低版本 Vite但会牺牲一部分新能力再或者通过远程开发机统一环境。浏览器兼容性上也一样。Vite 默认构建目标是最新的现代浏览器如果你需要支持老版 Edge、旧 WebView必须引入vitejs/plugin-legacy做语法降级。但目标越旧产物越臃肿建议先拉一下真实用户数据再决定兼容到哪个版本不要为了极少数用户牺牲所有用户的首屏体验。7.3 接手开源后台系统时的 TS 报错现在很多开发团队会直接拿若依、JeecgBoot 这类开源后台管理系统做二次开发。它们的 Vue3 TS 版本已经很成熟但依赖升级后经常会出现一批类型报错。常见的有几种情况第三方库缺少类型声明需要安装对应types/xxx组件库版本升级后属性改名比如 Element Plus 或 antd 版本跨度大时类型定义会跟着变化tsconfig 里没配置对应的 types导致全局类型无法识别开源模板里写了一大堆 any团队接手后想严格类型检查必然先把显式 any 清理掉。处理方式也不是看到报错就改成 any那样等于把所有类型检查全部绕开。正确做法是锁住项目依赖版本特别是不随意升级核心组件库再针对报错逐个修正类型定义。开箱即用的系统往往代码量大拿到手先不要急着改业务先把登录流程、权限路由、菜单注册这些骨架逻辑看明白再动手做二次开发否则后期越改越乱。8. 面试与成长从会用到理解原理8.1 高频面试题Vue2 vs Vue3 到底差在哪Vue3 面试题里出现频率最高的就是 Vue2 和 Vue3 的区别。最核心的差异如果用表格总结大概是这样的对比项Vue2Vue3响应式原理Object.defineProperty 劫持已有属性Proxy 代理整个对象支持新增属性和数组索引API 风格Options APIComposition API逻辑复用更灵活多根节点模板需要单一根节点支持 Fragment 多根节点异步组件需要显式 defineAsyncComponent同样支持配合 Suspense 更好用生命周期beforeDestroy / destroyedbeforeUnmount / unmounted全局 APIVue.prototype.xxxapp.config.globalProperties.xxx状态管理Vuex 为标准方案Pinia 更轻量官方推荐面试官一般会追问为什么 Proxy 比 Object.defineProperty 好。答案是defineProperty 只能劫持对象已有属性新增或删除属性需要额外调用$set/$delete而 Proxy 代理的是整个对象任何属性读取、设置、枚举都逃不出拦截范围。对象嵌套虽然 Vue2 初始化时要递归遍历Vue3 则是在读取时才做进一步代理性能更优。composition API 的变化也不仅是写法改变。以前 Vue2 里同一类逻辑可能散落在 data、methods、watch、computed 里现在可以全部塞进一个 composable 函数跨组件复用本质上是函数复用不再依赖 mixin 的命名空间冲突。8.2 从使用到原理自己写一个 mini-vue如果你真想深入理解 Vue3我非常推荐自己动手做一个 mini-vue不用功能齐全把响应式和渲染核心跑通就够了。至少要有这几个模块reactive、effect、computed、mount 和 patch。响应式部分的核心其实可以用几十行代码描述const targetMap new WeakMap() function track(target, key) { // 收集依赖把当前 effect 存储到 key 对应的 Set 里 } function trigger(target, key) { // 触发依赖执行该 key 对应的所有 effect } function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { track(target, key) return Reflect.get(target, key, receiver) }, set(target, key, value, receiver) { const result Reflect.set(target, key, value, receiver) trigger(target, key) return result } }) }代码虽然简陋但依赖收集和触发更新这两个核心机制已经体现出来了。再往下写就可以把 effect 和 computed 串起来然后让组件重新渲染。真正做过一遍 mini-vue 的人再看 Vue3 源码会轻松很多面试时聊原理也更自信不会停留在背概念阶段。8.3 前端工程师如何和 Java 后端协作最后聊一个非常实际的协作问题前端拿到一个 Java Spring Boot 后端项目能直接上手改代码吗这个问题的答案是前端不需要会写完整 Java 业务但要能看懂结构、定位接口、判断哪一端的责任。拿到 Spring Boot 项目先看这些地方就够了Controller 类定位每个接口的 URL、请求方法、参数注解Service 层理解业务大概怎么流转不用逐行读application.yml确认服务端口、上下文路径、数据库连接信息Swagger 或在线接口文档查看请求和响应示例跨域配置看看后端是否允许前端跨域调用。如果联调时发现接口返回 401、500、数据格式不一致前端可以很快判断是网关问题、后端逻辑问题还是前端请求参数拼接问题。这种协作能力比单纯会写页面更重要。我在项目里见过不少前端遇到接口问题就截图甩给后端后端回复“我这边调通了啊”就僵住了。其实只要前端能看懂 Controller 里的路径映射和参数定义很多问题自己就能定位。另外如果你的 Vue3 项目使用 TypeScript可以和后端同事约定一份接口类型定义后端把返回结构给到前端前端立即把它转成 interface这样联调阶段的低级字段拼写错误也能大幅减少。说白了现代前端工程从来不是只写页面就能交付的工程化配置和协作意识才是真正决定项目质量的部分。
返回列表