资讯动态

Vue前端小例子实战:Vite脚手架、组件通信与组合式函数

发布时间:2026/10/14 17:53:34 来源:尧图企业网站定制
简介这是一份面向 Vue 初学者的可运行小项目以资产管理应用为具体场景覆盖 Vue 实例创建、双向数据绑定、计算属性与侦听器、组件化开发、模板语法、Vue Router 路由管理以及工程化配置等 Vue 核心知识点适合希望借助源码快速理解 Vue 运作机制并尝试动手修改扩展的开发者。压缩包共包含 39 个文件整体仅 45KB体积十分精简其中 15 个 js 文件涉及入口、构建脚本和工具函数9 个 vue 文件直观展示组件拆分与页面模板7 个 png 可对照界面效果另有 json、html、md 等用于依赖、入口与说明目录结构接近标准 Vue 脚手架便于按图索骥。目前已有 4211 人学习浏览是较受欢迎的入门级示例。下载后可以拿到完整项目骨架从入口启动、根组件挂载到路由配置、组件之间 props 与事件交互再到构建目录下的 webpack 配置均能一一对照学习同时还能借鉴静态资源组织、代码规范与 Babel 编译等工程化细节为后续独立开发 Vue 项目打下扎实基础。1. VUE 前端小例子练的是这三件事搜“VUE前端小例子”的人多半是刚把视频课追完、书翻了一半手痒想自己写点东西。与其去抄几十个不痛不痒的片段不如把这几个字当成一个明确目标做出三五个能跑、能改、能拿出去给别人看的最小页面组件。这类小例子真正练的不是记 API而是三件事——数据怎么绑定到视图、请求怎么进来、组件之间怎么说话。本文不假装有现成源码包只把一线开发里最常用的一套 VUE 前端小例子方案完整拆开从脚手架到踩坑给你一份能照着敲的作业。2. 从零搭一个 VUE 前端小例子Vite 脚手架与第一个组件2.1 为什么现在起手都用 Vite而不是老的 Vue CLI两三年前建项目教程里还清一色是vue create。现在再从头搭 VUE 前端小例子我一般直接上 Vite。原因很实在Vite 开发服务器启动是秒级改一行代码浏览器几乎无感刷新而且它的依赖预构建机制让安装完依赖后的首次启动体验也快很多。Vue CLI 走的是 webpack 全量打包项目稍大一点保存一次等三五秒热更新是常事对小例子这种频繁改、频繁看效果的需求来说太磨人。两者的差别说白了是构建引擎不同。Vite 在开发环境用原生 ES Module 按需加载浏览器直接跑源码生产构建才走 Rollup 打生产包。Vue CLI 则是 webpack 从头到尾负责打包。对新手来说Vite 还能顺带把“模块是什么、依赖是什么”这件事在报错里展示得更直白。对比项ViteVue CLI开发启动秒级按需编译全量编译项目越大越慢热更新模块级 HMR改动哪块更新哪块页面级或全量刷新偏多生产构建Rollup产物更精简webpack配置生态老但重TypeScript开箱即用需要额外配置新手友好度配置少报错直观配置多适合老项目维护结论很简单新开的 VUE 前端小例子直接用 Vite。只有一种情况我会考虑 CLI就是你在维护一个老项目或者团队里全是 webpack 时代的配置习惯。2.2 创建项目与目录结构两条命令跑起来打开终端找一个干净的目录执行下面的命令。用npm create vue创建的是官方推荐的 Vite Vue 模板它会交互式问你几个问题新手建议先全部选 No等跑通后再逐步加东西。npm create vuelatest vue-demo执行后终端会依次询问项目名上面已经给了 vue-demo直接回车、是否安装 TypeScript、是否引入 Vue Router、是否引入 Pinia、是否安装 ESLint 等。小例子阶段我建议 TypeScript 先不选把精力放在 Vue 本身Router 和 Pinia 只有在你要写多页或者复杂共享状态的小例子时才需要后面可以随时补装现在选 No 最省心。然后进入项目目录安装依赖并启动cd vue-demo npm install npm run dev跑起来后浏览器访问终端里打印的那个本地地址能看到默认欢迎页就算成功。这里重点看目录里这几个文件因为大多数 VUE 前端小例子的改造都发生在这里文件/目录作用src/main.js应用入口负责创建应用实例并挂载根组件src/App.vue根组件所有小例子页面最终都挂到这里src/components/放自定义组件的目录小例子的主要战场src/assets/静态资源index.html页面壳Vite 的入口 HTMLvite.config.jsVite 配置后续配代理、配路径别名都在这里2.3 改造默认组件把第一段代码换成自己的计数按钮VUE 前端小例子的第一步通常是删掉默认欢迎模板写一个最小组件。我把 App.vue 的内容整个替换成下面的代码这就是一个完整的、可交互的第一件“小例子”script setup import { ref } from vue const count ref(0) function add() { count.value } /script template div p当前计数{{ count }}/p button clickadd加一/button /div /template style scoped button { padding: 8px 16px; font-size: 16px; cursor: pointer; } /style这段代码里ref(0)声明了一个响应式变量count.value修改它模板里的{{ count }}会自动刷新。这里有一个新手最容易忽略的点ref包裹的变量在script setup里读取和修改必须带.value但在模板里 Vue 会自动解包所以模板里直接写count不要多写.value。click是事件绑定的简写等价于v-on:click。第一次把这套跑通后你对 VUE 前端小例子的整体印象就有了一个.vue文件 模板 脚本 样式三块各管各的。接下来所有的例子都是在这个文件结构上扩展。3. 三个能直接抄进 VUE 前端小例子的组件数据绑定、请求与组件通信3.1 待办清单v-model、v-for 与计算属性三件套待办清单Todo几乎是所有 VUE 前端小例子里的常青树。它的价值在于横跨了三个最核心的知识点v-model双向绑定、v-for列表渲染、computed派生数据。我在实际带人写的时候总是推荐第一个完整例子写它而不是写一些花哨的动画组件。script setup import { ref, computed } from vue const todos ref([ { id: 1, title: 写一个 Todo 小例子, done: false }, { id: 2, title: 整理笔记, done: true } ]) const inputTitle ref() const addTodo () { const title inputTitle.value.trim() if (!title) return todos.value.push({ id: Date.now(), title, done: false }) inputTitle.value } const removeTodo (id) { todos.value todos.value.filter((t) t.id ! id) } const toggleTodo (id) { const todo todos.value.find((t) t.id id) if (todo) todo.done !todo.done } const remainingCount computed(() { return todos.value.filter((t) !t.done).length }) /script template div h3待办清单/h3 input v-modelinputTitle placeholder输入新事项 keyup.enteraddTodo / button clickaddTodo添加/button p还剩 {{ remainingCount }} 项未完成/p ul li v-fortodo in todos :keytodo.id span :style{ textDecoration: todo.done ? line-through : none } clicktoggleTodo(todo.id){{ todo.title }}/span button clickremoveTodo(todo.id)删除/button /li /ul /div /template关键点在于v-model把输入框的值和响应式变量inputTitle绑起来用户输入时变量自动更新清空输入框时把变量置空字符串即可。v-for循环必须配:key这里用todo.id而不是数组下标后面第 4 章会重点说为什么。computed的好处是它只依赖todos只要todos里任何一项变化这个结果会自动重新计算模板里永远拿到的是最新值。这段代码里keyup.enter是键盘事件修饰符按下回车等于点添加按钮这是个很实用的用户体验细节。id用Date.now()生成在小例子里完全够用但如果你做更复杂的数据管理可以考虑crypto.randomUUID()或者引入一个简单的自增计数器。3.2 天气卡片生命周期里发起请求处理加载与异常VUE 前端小例子里第二类必练的是“请求数据”。很多教程教fetch就只教你发一个请求但真实场景里你要管三件事加载中的等待态、接口报错的提示态、组件销毁后还在回调里操作页面的隐患。下面这个天气卡片例子把这三件事一起覆盖了script setup import { ref, onMounted, onUnmounted } from vue const city ref(杭州) const weather ref(null) const loading ref(false) const error ref() const fetchWeather async () { loading.value true error.value try { const res await fetch(https://api.example.com/weather?city${city.value}) if (!res.ok) throw new Error(请求失败) weather.value await res.json() } catch (e) { error.value e.message || 加载失败 } finally { loading.value false } } let timer null onMounted(() { fetchWeather() timer setInterval(fetchWeather, 60 * 1000) }) onUnmounted(() { clearInterval(timer) }) /script template div h3{{ city }} 天气/h3 button clickfetchWeather :disabledloading刷新/button p v-ifloading加载中.../p p v-else-iferror{{ error }}/p div v-else-ifweather p温度{{ weather.temperature }}°C/p p天气{{ weather.description }}/p /div /div /template这里的onMounted是组件挂载完成后执行的生命周期钩子适合放初始化请求。我额外写了一个setInterval定时刷新并在onUnmounted组件销毁前清除定时器否则组件被切走之后定时器还在跑会一直发请求甚至报内存泄漏的警告。很多新手翻车就翻在这里只写了onMounted没写onUnmounted。另外注意v-if / v-else-if / v-else的结构把加载、错误、正常三种状态拆开渲染。这种写法比一股脑渲染数据要稳得多。fetch的地址我用的是api.example.com占位符实际开发中替换成你自己的后端接口就好如果你只是练手不想依赖后端可以用本地 mock 数据或者找那些公开的、不需要复杂鉴权的接口。3.3 父传子与子传父购物车数量联动总价第三个必练例子是组件通信。VUE 前端小例子里最常见的父子结构就是购物车父组件管列表和总价子组件管单个商品的数量加减。这里的关键概念是单向数据流——父组件把数据通过 props 传给子组件子组件不能直接改 props只能通过事件把“我要改”告诉父组件由父组件真正修改数据。先看父组件 Cart.vuescript setup import { ref, computed } from vue import CartItem from ./CartItem.vue const cart ref([ { id: 1, name: 无线键盘, price: 199, count: 1 }, { id: 2, name: 机械鼠标, price: 89, count: 2 } ]) const total computed(() cart.value.reduce((sum, item) sum item.price * item.count, 0) ) const changeCount (id, delta) { const item cart.value.find((i) i.id id) if (item) item.count Math.max(1, item.count delta) } /script template div CartItem v-foritem in cart :keyitem.id :itemitem changechangeCount / p总计¥{{ total }}/p /div /template再看子组件 CartItem.vuescript setup const props defineProps({ item: { type: Object, required: true } }) const emit defineEmits([change]) /script template div classcart-item span{{ item.name }}/span button clickemit(change, item.id, -1)-/button span{{ item.count }}/span button clickemit(change, item.id, 1)/button span¥{{ item.price * item.count }}/span /div /template父组件用:itemitem把商品对象传给子组件子组件用defineProps声明接收子组件内部的按钮通过emit(change, item.id, -1)把意图告诉父组件父组件用changechangeCount监听并真正修改数据。defineProps和defineEmits都是script setup里的编译器宏不需要额外 import直接调用就行。为什么要绕这么一圈因为如果子组件直接改props.item.count数据源就变得不可追踪数据流混乱项目稍微一复杂就全是玄学 bug。坚持单向数据流数据永远有一个明确的修改入口出问题的时候在父组件里打断点就能排查。这个购物车例子做完父子通信这个坎基本就过了。4. VUE 前端小例子最常踩的 5 个坑现象、原因、解决4.1 样式写了没生效子组件里的 class 被无视现象在父组件里给子组件标签加了一个 class或者在父组件的style scoped里想改子组件内部的样式刷新页面后发现样式完全没有命中。原因scoped样式会为当前组件的元素加上一个 data 属性选择器子组件内部元素的属性由子组件自己生成父组件的 scoped 样式选不到子组件内部节点。这是 Vue 作用域样式机制的正常表现不是 bug。解决想改子组件内部样式用:deep()穿透选择器。写法是.parent :deep(.child-inner) { color: red; }。当初我刚接触这个特性时也被“玄学”了一把其实它设计的初衷就是让样式作用域清晰不至于全局污染。如果非要在子组件根元素上加 class子组件根节点会自动继承父组件传下来的 class但内部元素只能靠:deep()。4.2 v-for 用 index 做 key删除列表项后内容错乱现象列表渲染正常删除中间某一项后剩下的项内容对不上勾选状态串位甚至输入框里的内容跑到了别的行。原因key是 Vue 做节点复用的依据。用index做 key 时删除第一项后原来第二项的 index 变成了 0Vue 认为还是同一项于是复用现有节点但节点内绑定的数据还是老的。解决每条数据给一个稳定且唯一的 id。上面的待办清单里:keytodo.id就是正解。增删操作频繁的场景永远不要用 index 当 key。数据生成时可以用Date.now()加自增后缀或者crypto.randomUUID()保证每条数据的 id 不重复。4.3 修改数组某项的值视图纹丝不动现象list[0].title 新标题执行后页面没有刷新列表里还是旧文字。原因Vue 3 的响应式基于 Proxy理论上直接改数组下标也能被拦截但如果你用的是ref且初始数组是普通数组在嵌套层级较深或在某些边界写法下直接赋值确实会触发不了视图更新更常见的是整体重新赋值时引用没换导致视图不更新。解决统一用“整体替换”或者“新数组赋值”的做法。例如要更新下标 0list.value list.value.map((item, index) index 0 ? { ...item, title: 新标题 } : item)。删除用filter生成新数组添加用push或展开运算。养成“不直接改原数组返回新数组”的习惯既稳又方便调试。4.4 接口返回正常页面数据迟迟不来现象浏览器网络面板里请求 200响应 JSON 也完整但页面上一直是空白或加载中。原因最常见两种情况一是请求在setup顶层就发了但组件还没挂载完或者接口返回后组件已经卸载二是接口返回的字段名和模板里写的不一致比如蹩脚后端给你返回temp你模板里写temperature。解决先在then回调里console.log(数据)确认字段名和结构再检查请求是否在onMounted里发起。网络请求做好 loading 和 error 状态错误提示直接渲染出来页面空白时至少有个方向。这也是黑匣子最少见的场景——数据其实到了只是你还没去看而已。4.5 本地跑得好好的部署到静态服务器刷新就 404现象npm run build之后把 dist 目录扔到 Nginx 或静态托管首页能打开但一刷新某个子页面就是 404。原因如果小例子用了 Vue Router默认是 history 模式路由路径是真实路径静态服务器没有对应的物理文件刷新时服务器找不到文件就返回 404。解决开发阶段用 history 没问题部署时让服务器把所有未知路径回退到 index.html。Nginx 加一行try_files $uri $uri/ /index.html;即可如果是托管平台找一下 SPA fallback 或 rewrite 配置。不想折腾的话直接用 hash 模式路由也能绕开。这个坑几乎每个把例子部署上线的人都会碰到一次早点知道能省不少时间。5. 把 VUE 前端小例子再往前走一步用 useTodo 把逻辑抽出来小例子写多了你会发现组件里的script setup越来越长模板反而成了配角。这时候就该考虑把逻辑抽取成组合式函数composable。写个文件src/composables/useTodo.js把第 3 章待办清单里的响应式数据和操作全部搬进去import { ref, computed } from vue export function useTodo() { const todos ref([]) const inputTitle ref() const remainingCount computed(() { return todos.value.filter((t) !t.done).length }) const addTodo () { const title inputTitle.value.trim() if (!title) return todos.value.push({ id: Date.now(), title, done: false }) inputTitle.value } const removeTodo (id) { todos.value todos.value.filter((t) t.id ! id) } const toggleTodo (id) { const todo todos.value.find((t) t.id id) if (todo) todo.done !todo.done } return { todos, inputTitle, remainingCount, addTodo, removeTodo, toggleTodo } }组件里原来的逻辑部分就可以压缩成三行script setup import { useTodo } from ../composables/useTodo const { todos, inputTitle, remainingCount, addTodo, removeTodo, toggleTodo } useTodo() /script模板部分保持不变。这样拆的好处是同一个逻辑可以在多个小例子组件里复用比如后面做一个“计划表”页面、做一个“习惯打卡”组件直接 import 这个useTodo再扩展它。这也是我自己的习惯——每个小例子跑通后把里面可复用的逻辑丢进composables/目录下次写新例子时先翻一遍自己的旧目录能抄的绝不重写。这比收藏任何“项目源码包”都可靠因为代码是你一行行敲过、踩过坑的。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑