ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue 3实战:从搭建到部署,用m3u8播放器串起完整开发流程

Vue 3实战:从搭建到部署,用m3u8播放器串起完整开发流程 我一直觉得Vue 是一门“看起来三天就能上手但真正进项目之后才发现满地都是坑”的技术。你去看各大社区的热搜词常年挂着 vue 安装、vue 路由、vue 播放 m3u8、vue 打包后布局异常、vue 和 react 的区别、手写 reactive……这些问题单看都不算难但串起来恰好就是从一个 Vue 新手到一个能独立扛起前后端分离项目的人必须走过的完整链路。这篇教程我不打算复读官方文档而是用我自己的方式带你完整走一遍从 Node.js 环境怎么装、项目怎么建到路由、播放器、状态管理再到手写响应式原理和打包部署全程用一个“在 Vue 项目里播放 m3u8 视频流”的小实战串起来。你照着敲一遍基本就能把 Vue 3 从开发到上线的每个环节都摸到。1. 为什么我建议用“边学边做”的方式进入 Vue1.1 先搞清楚 Vue 到底是帮你解决什么问题的很多人一上来就纠结“Vue 和 React 到底选哪个”其实这个问题的前提就错了。两个框架解决的是同一个问题把 UI 和状态之间的关系变得可维护、可预测。Vue 的答案是用响应式数据驱动视图更新你改了一个变量页面自动跟着变React 的答案是单向数据流加显式渲染你调用 setState 之后由框架去协调更新。Vue 3 的响应式体系是细粒度的依赖收集改了一个 ref只有用到了这个 ref 的组件会重新渲染React 则需要靠 Fiber 调度来决定哪些组件需要更新。所以 Vue 的上手曲线确实更平缓这也是很多中小团队选 Vue 的核心原因。但低门槛不等于没深度。等你真正去读 Vue 源码就会发现它的响应式、调度器、编译器三块组合得非常精巧。所以这篇教程我会一直保持“先用起来再拆原理”的节奏让你先把项目跑起来再在第六章带你去手写一个 mini 版响应式。1.2 用“播放一个 m3u8 视频”当第一个实战目标我见过太多人学 Vue 的方式是跟着视频把 todo-list 敲了三遍结果一进公司接手真实项目连一个视频直播流都调不通。所以我建议你选一个足够真实、又不会复杂到劝退的题目。m3u8 播放就是一个特别合适的例子它涉及环境搭建、组件封装、第三方库集成、样式调整、兼容性处理又不涉及复杂的后端逻辑。而且“vue 播放 m3u8”在搜索榜上常年靠前说明这是真实开发里绕不开的需求。就以它为主线我们把一整个项目从零到上线走一遍。1.3 环境准备Node.js 版本选不对后面全是坑第一步永远是装 Node.js。这里有个我踩过无数次的坑不要图省事去装最新版也不要装太老的版本。Vue 3 Vite 5 要求 Node 18 以上但 Node 最新的奇数版本比如 23.x有些原生模块兼容性还不稳。我的建议是直接用 20 LTS。Node.js 版本适合场景风险点16.x老项目维护跑不动 Vite 5装依赖一堆 warning18.x能跑 Vite 5但部分工具链开始放弃勉强可用20.xLTS新项目首选生态兼容最好目前没发现大坑22.x / 最新想尝鲜可以有些 CI/CD 镜像还没跟上装完之后在终端验证一下node -v # 确认 v20.x npm -v # 确认 9.x 以上这里多说一句包管理器的事。新手阶段建议直接用 npm因为脚手架默认就是它等你对依赖版本冲突有概念了可以再换 pnpm它的磁盘占用和安装速度确实香但一开始叠太多工具反而容易乱。2. 从零搭建 Vue 项目并启动开发服务器2.1 创建项目的命令到底该怎么选Vue 官方现在的脚手架是create-vue它底层帮你接好了 Vite。创建命令是npm create vuelatest执行之后它会问你一堆交互式问题。我这边的推荐选择是问题推荐选项原因TypeScript?看情况想进阶就选 Yes现在新项目 TS 基本是标配JSX?NoVue 的 SFC 写法比 JSX 顺手Vue Router?Yes路由基本是刚需后面也要用Pinia?Yes状态管理先备着用不用另说Vitest?暂时不选单元测试是加分项别拖慢起步节奏ESLint / Prettier?Yes尽早养成格式化习惯还有一个小建议把vue-tsc相关的类型检查选项打开虽然开发时会偶尔报红但比上线之后才发现类型错误要舒服得多。2.2 安装依赖、启动 dev server以及装好调试利器项目创建完成后按它给你的提示执行cd your-project-name npm install npm run dev正常的话终端会打印出一个本地地址比如http://localhost:5173浏览器打开就是 Vue 的欢迎页。这一步想提醒你的是只要看到npm install卡住不动先检查是不是网络问题再检查是不是 Node 版本问题。不要一上来就重装依赖先看报错信息。多数情况下把 Node 切换到 20 LTS删掉 node_modules 和 package-lock.json重新 install 就能解决。浏览器里强烈建议装一个 Vue Devtools 插件它能看到组件树、路由、Pinia 状态调试效率直接翻倍。2.3 项目目录拆解哪些是你该关心的创建完的项目目录里真正的核心就三个地方src/ main.js # 入口文件负责创建应用实例并挂载 App.vue # 根组件所有页面都在它里面渲染 views/ # 页面级组件如果脚手架生成了的话main.js里那几行代码值得逐句看懂import { createApp } from vue import App from ./App.vue // 这里就是“vue mount”搜出来的 mount应用挂载到 #app 这个 DOM 节点上 createApp(App).mount(#app)顺带一提index.html里的div idapp/div就是整个 Vue 应用的容器。SPA单页应用的骨架就是这么个空壳所有页面内容都是 Vue 运行时动态塞进去的。3. 路由与页面跳转vue-router 4 的核心玩法3.1 路由表配置把 URL 映射到组件Vue Router 4 的用法和 Vue 2 时代差别很大最重要的变化是 API 全部变成了函数式调用。在src/router/index.js里你有这么一段代码import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: HomeView }, { path: /player, name: player, component: () import(../views/PlayerView.vue) } ] }) export default router这里有两个细节值得注意。第一createWebHistory用的是 HTML5 History 模式URL 里没有#好看也好用但后面部署到 nginx 时必须配try_files回退否则刷新页面就 404。第二component: () import(...)是路由懒加载每个页面的 JS 会被单独打包首屏加载更快这是 vue-router 官方推荐写法。3.2 路由传参query、params、props 各自的使用边界“vue 路由参数”这个热搜词下面我猜八成是传了参数取不到值的初学者。我把三种传参方式整理成了表格方式写法读法刷新后参数还在吗适用场景queryrouter.push({ path: /player, query: { id: 1 } })route.query.id在URL 上可见搜索条件、列表页跳到详情页paramsrouter.push({ name: player, params: { id: 1 } })route.params.id页面刷新后丢失临时传参不推荐用于重要信息props路由表里配置props: true组件直接用defineProps接收在但前提是配合 params/query 使用解耦组件和路由推荐推荐的做法是能用 props 就用 props能让路由参数出现在 URL 里就让它在 URL 里。刷新页面丢参数这种问题我陪别人排查过太多次多半就是用了 params 又没接好兜底逻辑。3.3 路由拦截器登录鉴权写在哪儿最合适很多项目都有“未登录用户不能进入某些页面”的需求这就是“vue 路由拦截器”要解决的问题。vue-router 4 提供了全局前置守卫beforeEachrouter.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ name: login }) } else { next() } })实际项目中我会在路由表的路由对象上加一个meta字段比如meta: { requiresAuth: true }然后守卫里统一判断。这里有个容易踩的坑不要把 token 放在内存变量里刷新就清掉了要放 localStorage 或者 cookie。另外拦截器只控制前端页面跳转真正的权限校验必须由后端接口完成前端拦截只是体验优化不是安全手段。4. 播放 m3u8 视频流组件封装、扩展与样式细节4.1 m3u8 是什么为什么浏览器不能直接播放一个 m3u8 文件本质上是一个索引清单里面按顺序列出了一个个视频分片的地址播放器拿到这个清单后会逐个下载并连续播放。这种“切片索引”的设计好处很明显可以做多码率切换、可以做直播低延迟、可以边下边播。但它不是标准 HTML5 视频格式所以 Chrome、Firefox 这些浏览器默认播放不了。Safari 因为背靠 Apple 的 HLS 生态天然支持 m3u8。其他浏览器就得靠 JavaScript 拉流然后把分片喂给浏览器的 Media Source Extensions 去播放。社区里做这件事最成熟的库就是hls.js。4.2 用 hls.js 封装一个可复用的播放组件在项目里执行一下npm install hls.js然后建一个src/components/VideoPlayer.vue这是核心组件包含了“vue 播放 m3u8”这个场景下你会遇到的几乎所有代码模式template div classvideo-player video refvideoEl classvideo-player__video controls :mutedmuted clicktogglePlay /video /div /template script setup import { ref, onMounted, onBeforeUnmount, watch } from vue import Hls from hls.js const props defineProps({ src: { type: String, required: true }, muted: { type: Boolean, default: false } }) // 动态 ref在 v-for 循环或动态组件里给 DOM 元素绑定引用时用函数方式拿实例 const videoEl ref(null) let hls null // 允许多余的属性透传到根元素比如 style、class 这种原生标签支持的属性 defineOptions({ inheritAttrs: false }) function initPlayer() { if (!videoEl.value || !props.src) return // 如果浏览器原生支持 HLS比如 Safari直接用 video 标签播 if (videoEl.value.canPlayType(application/vnd.apple.mpegurl)) { videoEl.value.src props.src return } // 否则用 hls.js 处理前提是浏览器支持 Media Source Extensions if (Hls.isSupported()) { hls new Hls({ maxBufferLength: 30, enableWorker: true }) hls.loadSource(props.src) hls.attachMedia(videoEl.value) hls.on(Hls.Events.ERROR, (event, data) { if (data.fatal) { // 自动恢复网络错误就尝试换源媒体错误就尝试恢复最坏情况调起销毁 if (data.type Hls.ErrorTypes.NETWORK_ERROR) { hls.startLoad() } else if (data.type Hls.ErrorTypes.MEDIA_ERROR) { hls.recoverMediaError() } else { hls.destroy() } } }) } } function togglePlay() { const video videoEl.value if (!video) return if (video.paused) { video.play() } else { video.pause() } } onMounted(initPlayer) watch(() props.src, () { if (hls) hls.destroy() initPlayer() }) onBeforeUnmount(() { if (hls) hls.destroy() }) /script这个组件里有两个非常实用的点。第一是Hls.Events.ERROR的错误处理直播流断流是常态没有这个自动恢复逻辑页面播到一半就白屏了。第二是watch(() props.src, ...)也就是说你在父组件里切换视频地址播放器会自动重建不用刷新页面。这是真实业务中一定会遇见的细节。4.3 播放按钮居中、样式细节和 WebRTC 场景的取舍热搜里那条“vue video 播放按钮移到正中间”其实是 video 标签的 controls 样式问题。浏览器的原生控件样式没法精细控制最简单的办法是不要依赖原生 controls 的播放按钮而是自己盖一个按钮在视频上。template div classvideo-player clicktogglePlay video refvideoEl classvideo-player__video :controls!showCustomBtn/video button v-ifshowCustomBtn classvideo-player__btn/button /div /template样式上注意三点按钮用绝对定位居中pointer-events别乱设Z-index 保证按钮在视频上方。另外给根容器设一个aspect-ratio: 16 / 9视频就不会因为源分辨率不同而撑破布局。如果你想做的是实时音视频通话而不是播放直播流那 m3u8 的思路就不适用了你需要的是 WebRTC。Vue 里没有官方 WebRTC 库但我建议直接封装浏览器原生 API数据通道用 PeerConnection媒体流用 getUserMediaVue 只负责把 stream 绑定到video的srcObject上。这类库封装层很薄不推荐套很重的前端 SDK。5. 状态管理和 UI 框架选型Pinia、Vuex 和组件库怎么选5.1 Pinia 还是 Vuex这不只是一次版本升级“vue pinia vs vuex”本质上是“是不是该彻底抛弃 Vuex”的问题。我先说结论新项目直接用 Pinia老项目只要维护成本可控也可以趁迭代顺手迁过去。Pinia 和 Vuex 的核心差异可以用一张表说清楚对比项Vuex 4Pinia变更状态必须走 mutations直接改 store 里的 state或调 actionTypeScript 支持需要自己写一堆类型体操原生友好推断顺畅模块结构modules 嵌套容易绕晕每个 store 独立定义类似组合式 APIDevtools 支持好更好时间线调试非常清晰学习成本mutations / actions / getters 四个概念只剩 state / getters / actionsPinia 把 mutations 砍掉是我认为最舒服的改动少一层概念少一半的心智负担。它的写法也非常直观import { defineStore } from pinia export const useVideoStore defineStore(video, { state: () ({ currentSrc: , isPlaying: false }), actions: { setSrc(src) { this.currentSrc src } } })5.2 Element Plus、Ant Design Vue、Naive UI 怎么选“vue ui 框架对比 element plus ui”这类问题我在团队里被问过很多次。市面上主流的 Vue 3 组件库主要是这三家我按实战体验讲下区别框架风格适合场景我的使用体感Element Plus稳重、后台味浓管理系统、后台、中后台项目组件全坑少团队招人容易上手Ant Design Vue细腻、设计规范强对设计一致性要求高的项目跟着 Ant Design 规范走但版本迭代历史包袱重Naive UI现代、轻量、TS 强新项目、追求体验和可维护性类型体验最好主题定制灵活我的观点是按团队经验存量来选不要按个人喜好来选。如果团队成员都熟 Element Plus那就继续用它效率比“更酷”重要。如果你从零组团队我会推荐 Naive UI它的 TypeScript 体验和模块化做得最舒服。顺带提一句如果你要做可视化组件库不覆盖这类需求直接集成 ECharts 或 AntV 对应 Vue 封装就行vue-echarts的维护状态不错。5.3 一个“够用但又不臃肿”的技术栈组合对我个人来说现在开一个小型 Vue 项目默认组合是Vue 3.4 的script setup语法别再写 Options APIVite 作为构建工具Pinia 做状态管理Vue Router 4 做路由UnoCSS 或 Tailwind 处理样式Element Plus 只负责复杂交互组件ESLint Prettier 从一开始就配好这套组合的好处是每个位置都有明确职责不会出现“样式用组件库硬刮、状态全靠 props 传递”这种捉襟见肘的局面。6. 深入响应式原理用原生 Proxy 手写一个 mini 版 Vue 36.1 为什么“手写响应式”成了面试和进阶必考题“脱离 vue 源码使用原生 proxy 手写 reactive、ref、effect、computed”这条热搜背后反映的是一个真实需求只会用 Vue不会解释 Vue。Vue 3 能成为今天的样子核心就是一套基于 Proxy 的响应式系统。你只要搞懂它就掌握了 Vue 的半壁江山面试官问“vue 为什么能自动更新”时你也能给出有深度的回答。下面我带你从零手写一版代码量不大但足以回答绝大多数响应式原理问题。6.2 从零实现 reactive、effect、ref、computed 的完整过程先设计两个核心容器targetMap用来存“对象 → 它每个属性依赖了哪些函数”activeEffect用来标记“当前正在执行的函数”。const targetMap new WeakMap() let activeEffect null function track(target, key) { if (!activeEffect) return let depsMap targetMap.get(target) if (!depsMap) { depsMap new Map() targetMap.set(target, depsMap) } let dep depsMap.get(key) if (!dep) { dep new Set() depsMap.set(key, dep) } dep.add(activeEffect) } function trigger(target, key) { const depsMap targetMap.get(target) if (!depsMap) return const dep depsMap.get(key) if (dep) { dep.forEach(effect effect()) } }然后是reactive它用 Proxy 拦截get和set在 get 时收集依赖在 set 时触发依赖。这就是 Vue 3 响应式的骨架function reactive(target) { return new Proxy(target, { get(target, key, receiver) { const res Reflect.get(target, key, receiver) track(target, key) return res }, set(target, key, value, receiver) { const result Reflect.set(target, key, value, receiver) trigger(target, key) return result } }) }effect用来注册副作用函数执行时会把自己暂存到activeEffect上这样函数内部读取响应式属性时track 就能把这个函数收集进去function effect(fn) { const wrapped () { activeEffect wrapped fn() activeEffect null } wrapped() return wrapped }ref其实就是对普通值的包装用一个对象包一层value 存在_value里利用对象引用传入 reactive 变成响应式。这个设计的精妙之处在于它解决了 Proxy 只能代理对象、不能代理原始值的问题function ref(initialValue) { return reactive({ get value() { return this._value }, set value(newVal) { this._value newVal }, _value: initialValue }) }computed则是基于 effect 的“惰性求值”第一次访问才计算依赖变化时标记为脏下次访问重新计算function computed(getter) { let value let dirty true const runner effect(() { value getter() dirty false }) // 这里需要一点小技巧依赖变化时把 dirty 重新置为 true return { get value() { if (dirty) { getter() dirty false } return value } } }一个完整的 computed 实现还应该在 trigger 时把dirty置回 true。这里省略了部分调度细节但核心思路就是这个effect 负责记依赖Proxy 负责拦截computed 负责懒执行。6.3 这版迷你实现和 Vue 源码的真实差距写到这里必须泼一盆冷水这个 mini 版能跑通 demo但离生产级还差得远。真实 Vue 3 的响应式至少还有这几层调度器schedulereffect 不是同步立刻执行而是放进队列合并批量更新否则一次改十个属性就触发十次重渲染性能直接崩。reactive 和 readonly 的区别前者可写后者只读底层是嵌套 Proxy 的 handler 属性标注。ref 的深层响应式Vue 3 的 ref 内部会调用reactive处理对象类型的值让深层属性也是响应式的。这些细节在面试里都是加分点但我不建议你一下子全啃。先把这条主线跑通至少你能回答“Proxy 的 get 和 set 分别干了什么”这个核心问题了。7. 打包部署布局异常排查和前后端分离上线7.1 打包后布局异常大概率是这三个原因搜索榜上那条“vue 打包后布局异常”基本是新手上线第一关的集体呐喊。我在帮别人排查过几十次之后总结出最常见的原因只有一个判断思路先在本地npm run build然后npm run preview一把看看异常是否复现。如果复现那问题就不在服务器而在项目配置。最常见的三个元凶第一静态资源路径不对。默认 Vite 构建出的资源路径是根路径/assets/...如果你部署在服务器子目录下比如https://example.com/admin/那自然 404。解决办法是在vite.config.js里配置base: /admin/。第二路由模式导致刷新 404。用createWebHistory之后直接访问/player时 nginx 不知道这个路径属于前端会按文件查找找不到就 404。必须在 nginx 加try_fileslocation / { try_files $uri $uri/ /index.html; }第三CSS 顺序或缓存问题。打包后多个页面的 CSS 被合成成一个 chunk如果某个页面的样式覆盖了另一个页面会出现“打包前正常打包后样式变乱”的情况。这个往往是组件库里全局样式和业务样式互相污染建议约定业务组件不要写无 scoped 的全局样式。7.2 Vite 环境变量正确使用 .env.production 和 import.meta.env“vite vue env.production”搜出来其实就是环境变量的玩法。Vite 会自动加载项目根目录下的.env文件以.env.production为例只有VITE_前缀的变量会被注入到前端代码中VITE_API_BASE_URLhttps://api.example.com VITE_APP_TITLEMy Vue App在代码里通过import.meta.env.VITE_API_BASE_URL读取。我建议你在项目里至少维护三个文件.env.development存本地联调地址.env.production存线上接口域名.env.local这个文件加入.gitignore避免把本地密钥提交上去。这是 Vite 项目最容易忽略但价值极高的规范。7.3 Spring Boot Vue 前后端分离的部署要点最后说部署。前后端分离项目的经典组合是前端 Vue 打包出的静态文件交给 nginx后端 Spring Boot 单独跑在8080端口通过 nginx 反向代理把/api/开头请求转发给后端。宝塔面板里实操时我会按下面这套来配置server { listen 80; server_name your-domain.com; root /www/wwwroot/vue-project/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有个细节容易搞混proxy_pass后面的路径带不带/api/效果完全不同。带上了nginx 会把整个/api/xxx原样转发给后端不带则会把匹配到的/api/前缀去掉再转发。多数项目里后端接口就是带/api路径的所以原样转发最省心。在本地开发时Vite 也提供了一个 proxy 配置让你不用开 nginx 也能前端调后端接口// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这套配置搞定之后本地开发、线上部署的行为就是一致的前后端联调时的“跨域报错”“接口 404”基本不会再出现。写在最后一条我验证过多次的学习路线我个人带过的所有人里难得有不卡壳的而那些能独立做项目的走的路大体都一致先照着教程把项目跑起来遇到问题不要绕开而是用搜索词把问题的上下文补全然后把官方文档当作字典来查而不是当小说来读等把第一章到第七章的链路走完再回头去啃 Vue 源码。你会发现很多以前觉得神秘的概念——虚拟 DOM 为什么快、diff 算法到底在比什么——都是在一遍遍实际摩擦中慢慢变清晰的。学到这个阶段我建议你去看 GitHub 上几个 star 高的开源 Vue 项目别只看源码要跑到本地改几处让它出点“问题”再自己把它修好。这种主动制造 bug、再亲手修掉的过程是任何教程都替代不了的。真把这条“跑项目 → 看报错 → 查机制 → 改源码”的路走通Vue 对你来说就不只是“一门框架”而是你工具箱里真正用得顺手的那把工具。
返回列表