Vue 3高频实战小例子:初始化、路由传参与部署打包全解析

发布时间:2026/9/7 6:21:43
Vue 3高频实战小例子:初始化、路由传参与部署打包全解析 简介一套面向Vue初学者的前端小例子工程以资产管理应用为场景集中演示Vue实例、双向数据绑定、计算属性与侦听器、组件化拆分、模板指令及Vue Router路由配置等核心知识适合希望快速上手Vue基础并理解组件化开发流程的读者。压缩包共39个文件含15个js脚本、9个vue组件、7个png图片、2个json配置以及html、md等辅助文件整体仅45KBjs覆盖构建与路由逻辑vue为页面与子组件json保存依赖及环境配置便于对照学习工程化结构。目前已有4209人学习下载。通过这个小例子可梳理从main.js入口、App.vue根组件到components、router目录的典型Vue项目布局还能了解webpack构建配置、静态资源管理、布局实现与响应式设计思路是初学Vue时轻量而完整的参考素材。 在面试题和八股文里翻一圈你会发现Vue前端小例子永远是最高频的搜索关键词之一。我自己的收藏夹里也攒了不少早期是为了应付面试手撕代码后来工作了反而成了验证新功能最快的工具——产品说要拉一路m3u8直播流我先开个小demo拉通联调时大文件上传卡主线程也是先写一个Worker小例子验证再搬过去。这些小例子单个看都很普通但凑在一起基本就是我日常前端开发最真实的工具箱。这篇文章把几个被问得最多、踩坑最深的Vue小例子整理出来工程初始化、组合式和选项式API、路由传参、视频流播放、大文件上传、PDF下载最后说一下构建部署相关的问题。整体以Vue 3为基础所有代码我都按可复现的标准写你复制下来就能跑。适合刚入门想跑通第一个Vue小项目的新人也适合准备面试想快速过一遍重点的开发者。1. 从零搭工程先让这个Vue小例子跑起来1.1 Node和npm版本容易被忽略的隐形门槛不少小例子跑不起来的第一个原因不是代码是环境。Vue 3配的是Vite而Vite对Node版本要求很直接Vite 5至少要Node 18Vite 7则要Node 20.19或更高。你用node -v查一下发现是14运行npm create vuelatest大概率直接报错。解决办法是用nvm切版本nvm是node版本管理工具装上之后一条命令就能在不同node版本间切换。我习惯在16和20之间反复横跳因为老项目要求低版本新demo又必须用高版本没有nvm会非常痛苦。node -v npm -v # 用 nvm 切到高版本 node nvm install 20 nvm use 201.2 create-vue初始化模板删掉默认演示代码环境没问题后创建项目npm create vuelatest vue-demo cd vue-demo npm install npm run devcreate-vue是Vue官方脚手架命令行会问你要不要Router、Pinia、ESLint等。小例子建议Router勾上后面路由要玩一下Pinia可以先不加免得模板太臃肿。创建完后src/App.vue里默认带了一堆示例样式和HTMLHelloWorld.vue这种演示组件也没必要留着全删掉。很多人做小例子喜欢删代码但要注意别把main.js里的createApp和router挂载逻辑删了否则页面会完全空白。// main.js import { createApp } from vue import App from ./App.vue import router from ./router createApp(App).use(router).mount(#app)接着把App.vue清干净script setup /script template RouterView / /template这样就是一个空壳工程后续的小例子往里填。另外说一个热词里出现过的细节vue项目启动后network不可用。这个通常有两种情况一是Vite默认只监听本地localhost需要在vite.config.js里加host: true才会暴露到局域网二是Windows防火墙拦了端口。小例子里如果需要手机联调就加上export default defineConfig({ server: { host: true, port: 5173 } })2. 组合式还是选项式两种写法在小例子里的实际差异2.1 同一份逻辑两种写法的代码对比vue选项式和组合式区别是搜索热度很高的词也是面试常规题。要理解它最好的方式是看同一个功能的两种写法。选项式写法script export default { data() { return { count: 0 } }, methods: { addCount() { this.count } }, computed: { doubleCount() { return this.count * 2 } } } /script组合式写法script setup import { ref, computed } from vue const count ref(0) const addCount () count.value const doubleCount computed(() count.value * 2) /script表面看只是写法差异本质是代码组织方式不同选项式按数据的类型组织代码data写一块、methods写一块、computed写一块组合式按业务逻辑组织代码和应用场景相关的变量、函数写在一起这就是Composition API名字的由来。对比维度选项式组合式代码组织方式按类型data/methods/computed按业务逻辑script setup不支持支持逻辑复用mixin坑比较多自定义hook适合场景简单组件、老项目维护新项目、复杂场景2.2 我为什么建议新例子直接用组合式对小例子而言两种写法都能跑甚至选项式看起来更工整。但我还是建议新的demo统一用script setup组合式有两个实际原因。第一script setup语法更简洁省掉了export default、methods、computed这些壳子第二组合式可以很方便地把一段相关逻辑抽成函数比如把用户列表的逻辑抽出useUserList()不同页面复用。这种组织方式在项目变复杂的时候收益极其明显。我看过很多Vue 2转Vue 3的同事习惯性用选项式写新代码。做小例子问题不大但项目里只要涉及跨组件复用逻辑选项式就会绕很多额外工作。面试如果被问组合式和选项式区别把按类型组织 vs 按逻辑组织这个核心差异讲清楚基本就过关了。调试时记得装Vue DevTools浏览器扩展里搜一下就有。打开后Components和Router两个Tab能直接看到当前组件的props、路由参数和分析记录排查问题比console.log快很多。3. 路由传参三种方式小例子里的高频考点与刷新丢参教训3.1 query、params、props三种传参实例路由传参是vue路由里最常见的需求。假设有列表页跳详情页的场景目标路由是/user/:id。先定义路由import { createRouter, createWebHistory } from vue-router const routes [ { path: /, component: () import(/views/Home.vue) }, { path: /user/:id, name: UserDetail, component: () import(/views/UserDetail.vue), props: true } ] const router createRouter({ history: createWebHistory(), routes })三种传参方式方式一query参数// 跳转 router.push({ path: /user, query: { id: 123 } }) // 接收 const route useRoute() console.log(route.query.id) // 123方式二params参数// 跳转注意必须用 name不能只用 path router.push({ name: UserDetail, params: { id: 123 } }) // 接收 const route useRoute() console.log(route.params.id) // 123方式三props映射路由配置里加了props: true之后参数会自动映射到组件propsconst props defineProps([id]) console.log(props.id) // 123props方式的好处是组件不依赖具体在哪一层路由耦合更低写单元测试也舒服。传参方式代码写法刷新是否保留使用场景queryrouter.push({ query: {...} })保留搜索筛选、分页params非路径参数router.push({ name, params })丢失只传一次、不复用params props路径参数路由定义 /user/:id props: true保留详情页ID3.2 页面刷新后参数丢失的真正原因params方式有一个非常经典的坑刷新后参数丢了。原因在vue-router本身params参数如果没有体现在URL路径上即不是动态路由的:id而只是一个对象传递刷新后它依赖的是内存里的对象一刷新自然没了。所以凡是需要刷新后依然存在的参数业务上必须放进query或者路径参数里不要放纯对象形式的params。这个坑在企业项目里很常见面试也爱问。可以记一句判断标准看一眼URL地址栏。参数出现在URL地址栏里的刷新基本不会丢没出现的刷新就依赖浏览器内存刷新等于重新走一遍生命周期。4. 三个实用型小例子视频流播放、大文件上传、PDF下载4.1 m3u8直播流播放vue播放m3u8搜索量很高。m3u8本质是一个播放列表索引文件浏览器原生不支持所以需要hls.js之类的库来解析。npm install hls.jstemplate video refvideoRef controls muted autoplay/video /template script setup import Hls from hls.js import { onMounted, ref } from vue const videoRef ref(null) const streamUrl https://example.com/live/test.m3u8 onMounted(() { const video videoRef.value if (Hls.isSupported()) { const hls new Hls() hls.loadSource(streamUrl) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () { video.play() }) } else if (video.canPlayType(application/vnd.apple.mpegurl)) { // Safari 原生支持 HLS video.src streamUrl video.addEventListener(loadedmetadata, () video.play()) } }) /script两个细节video标签上加muted否则浏览器自动播放策略会把play()拦下来Safari对HLS有原生支持不需要hls.js所以要做兼容判断。如果播放请求跨域后端还得配CORS否则hls.js拿不到分片。4.2 用Worker做切片上传大文件上传时如果在主线程里读文件、发请求页面会卡死。最直接的小例子是把切片逻辑放进Web Worker。主线程代码const worker new Worker(new URL(./uploadWorker.js, import.meta.url), { type: module }) worker.postMessage(file) worker.onmessage (e) { console.log(当前上传进度, e.data.progress) }Worker内部uploadWorker.jsself.onmessage async (e) { const file e.data const CHUNK_SIZE 2 * 1024 * 1024 // 每片2MB let start 0 while (start file.size) { const chunk file.slice(start, start CHUNK_SIZE) const formData new FormData() formData.append(chunk, chunk) formData.append(name, file.name) formData.append(index, start / CHUNK_SIZE) await fetch(/api/upload, { method: POST, body: formData }) start CHUNK_SIZE self.postMessage({ progress: Math.round((start / file.size) * 100) }) } }这里的关键是用new URL(./uploadWorker.js, import.meta.url)这种写法Vite能正确打包Worker文件直接写字符串路径会踩构建报错。还有Worker里不能用DOM API只能postMessage和主线程通信所以进度更新要靠postMessage传回去。实际的断点续传还需要把已上传的分片索引记录下来比如localStorage刷新后跳过已完成的部分。这个例子虽然小但已经是企业级上传方案的雏形。调试的时候打开DevTools的Network面板能看到每个分片请求的耗时确认是否真的没阻塞主线程。4.3 iOS上PDF下载变预览的解决思路热搜词里有一条vue a标签下载pdf在ios上会变成预览。这个问题很真实PC端的a标签download属性在iOS Safari里基本不生效点击之后会直接打开新的预览页。最简单的方案是用fetch把文件转成blob再通过URL.createObjectURL生成临时下载链接触发下载async function downloadPdf(url, fileName) { const res await fetch(url) const blob await res.blob() const objectUrl URL.createObjectURL(blob) const a document.createElement(a) a.href objectUrl a.download fileName document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(objectUrl) }这个方案在PC上基本通用但iOS上依然有兼容历史问题。如果还不行备选方案是后端把响应头设置成Content-Disposition: attachment; filenamexxx.pdf让系统层面把它当附件下载而不是内联预览。所以最优解往往是前后端一起改只改前端一段代码很难彻底解决iOS的下载行为。5. 构建部署的最后一公里本地打开、Docker和Electron5.1 打包后本地打开全是空白根因在base路径npm run build之后直接双击dist/index.html浏览器一片空白控制台报一堆资源404。根因是Vite默认base设置为/打包后的资源路径是绝对的根路径本地双击时其实是从file://协议访问绝对路径自然找不到。解决办法export default defineConfig({ base: ./, plugins: [vue()] })把base改成./之后资源路径变成相对路径本地直接打开index.html就能跑。小例子不用上服务器验证的时候这个配置非常实用。关于iOS能不能直接加载本地vue打包文件答案是有限制的。iOS上的WKWebView对file://协议加载本地html有安全限制通常需要把文件放到App的沙盒目录通过类似WKWebView加载本地目录的形式打开路径不能由用户随便指定。纯浏览器输入file://是走不通的企业里一般用Cordova或内置WebView容器承载。5.2 Docker部署最省心的上线方式前端Vue项目Docker部署的思路只有两步用Node镜像构建出dist产物再用nginx镜像跑静态文件。FROM node:20-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM nginx:alpine COPY --frombuild /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]nginx.conf里通常要配一下SPA路由的history模式回退server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } }这步不配Vue Router用history模式时刷新二级页面会404。很多人Docker部署后遇到页面刷新404就是这个原因。5.3 把Vue小例子打包成exevue项目怎么打包成exe也是一条高频词。思路是用Electron套壳先按前面配置build出dist然后Electron主进程加载dist/index.html。// electron/main.js const { app, BrowserWindow } require(electron) const path require(path) app.whenReady().then(() { const win new BrowserWindow({ width: 1200, height: 800 }) win.loadFile(path.join(__dirname, ../dist/index.html)) })再配合electron-builder打包就能产出Windows下的exe。要注意的是如果Vue项目里有接口请求本地exe环境下的host地址不能是localhost要指向真实服务器地址而且接口服务器需要允许跨域否则请求会被同源策略拦截。springboot vue前后端分离正好对应这个场景开发环境用Vite代理生产拆开部署后跨域问题要靠网关或后端配合解决。说实话整理完这些例子我自己也挺感慨的。Vue小例子之所以值得反复写、反复搜集原因不是它们简单恰恰是它们把一个一个的真问题掰开了路由传参背后是浏览器的URL机制m3u8背后是视频编解码生态大文件上传背后是线程模型PDF下载背后是Safari的兼容策略。每解一个就等于往前端这条路上多铺了一块砖。我给新人的建议也很朴素看到不懂的功能不要急着往业务里加先开一个vue小例子试。跑通了你再往里搬搬的过程中遇到问题也不会影响线上。我现在很多生产代码最初都是从这种不起眼的小例子长出来的。本文还有配套的精品资源点击获取