资讯动态

Vue入门实战:从数据驱动到组件化,搭建第一个待办清单

发布时间:2026/10/15 19:53:28 来源:尧图企业网站定制
第一次正儿八经想学一套前端框架是我被一个后台管理页面的需求折磨到不行的时候。左边一个任务列表右边一个详情表单选了左边右边要跟着刷新改了右边某个字段列表统计数字要立刻变化。用原生写法把这一切串起来逻辑倒是不难但改需求的时候是真崩溃——产品说“把状态字段从A改成B”我得同时改数据渲染、事件绑定、列表刷新三处每一处还都是隐式关联。那会儿我才意识到自己缺的不是写代码的能力而是一套能把“数据和界面”自动同步的工具。这个工具我后来选的是Vue框架。这篇是“vue学习”系列的第一篇目标很纯粹让一个没接触过前端框架的人用一下午时间搞懂Vue的核心逻辑并能亲手写出一个能跑的小项目。你不需要有很深的前端底子只要写过原生JavaScript、见过HTML和CSS剩下的路我带你走。会涉及为什么选它、环境怎么装、核心语法怎么记、组件怎么拆、常见坑有哪些全部按我实际学习过程中的顺序来不绕弯子。1. 为什么一上来就选Vue数据驱动到底省了多少事1.1 原生写法让人崩溃的三个场景先回忆一下原生JavaScript开发中常见的麻烦这样你才知道Vue到底在解决什么。第一是“改一处要牵三处”数据变了你得手动找到页面里对应的DOM节点更新它的内容用户操作了你又要反过来更新数据。数据在内存里是一份在页面上是另一份两边的同步全凭你自觉。第二是“DOM操作代码混在业务逻辑里”一个简单的列表渲染你既要想数据怎么处理又要考虑怎么拼HTML字符串、怎么绑定事件代码写着写着就变成意大利面。第三是“状态一多就失控”弹窗开没开、选中了哪一项、表单校验过没过、接口加载中……这些状态散落在各个函数里互相影响排查问题只能靠console.log。这三个场景本质上是同一个问题视图和状态没有建立自动联系。手动同步一次两次还行项目一复杂成本是几何级上涨的。这也是当时让我下定决心换技术栈的直接原因——我不想再做那个“人肉数据同步器”了。1.2 Vue给出的解法是“数据和视图分离”Vue的核心思路一句话就能说清你只管维护数据DOM怎么更新是框架的事。你在JavaScript里定义一个变量模板里写上它之后你只改这个变量页面会自动跟着变。反过来用户在页面上输入内容数据对象也会自动更新。这就是所谓的“数据驱动视图”。我一开始觉得这像个魔术后来才知道底层是依赖收集加变更通知Vue会代理你定义的数据对象读取属性时记录“谁在用这个值”修改属性时通知“所有用这个值的地方去更新”。这块调用的是浏览器原生的能力日常使用你不需要理解全部细节只要记住大方向数据变了视图变视图操作了数据变。为什么在众多方案里我选了Vue而不是另一套界面框架理由其实很个人化。第一它的上手曲线确实平缓模板语法跟HTML很像能无缝从原生开发切过来第二它是渐进式的一个老项目可以只在一个页面里引入不用推倒重来第三中文资料和社区讨论质量高遇到问题搜一下基本都有答案。对刚入门的人来说这三点比“技术更酷”重要得多。1.3 快速入门的心理预期学Vue最忌讳的就是上来啃源码、啃原理然后两周之后放弃了。我的建议是先让它跑起来再问为什么。你在接触框架的前期完全可以先接受“写了这个API就会这样”的设定等手上有了两三个小项目再去深入看响应式原理、虚拟DOM这些东西那时候看起来全是“原来如此”的顿悟。所以接下来我不堆原理按实际开发顺序走装环境、建项目、写页面、拆组件、跑一个小项目。每一步怎么做、为什么这么做、可能出什么问题我都会讲清楚。先把这五个环节打通你就已经入门了。2. 装环境这一步版本、脚手架、目录结构全捋顺2.1 先把Node和包管理环境搞好Vue应用的开发环境依赖Node.js生态所以第一步是安装它。打开终端输入node -v看看有没有装过。如果输出版本号说明已经存在如果没有去Node官网下载LTS版本装好。这里要提醒一句版本别追太新LTS长期维护版本最稳妥因为很多工具链对新版本Node的兼容性是滞后的。有了Nodenpm包管理器会跟着一起装好。这一步经常有人卡住因为默认的包下载源在国内访问很慢动不动就卡在进度条。解决办法是切换到可用的镜像源在终端执行一次配置命令之后下载速度就正常了。这是我带过无数新人后总结出的第一道坎不是技术问题是环境问题先解决掉才不会影响后续心情。你现在搜Vue相关教程会发现两种不同的安装方式一种是基于老构建工具的Vue CLI另一种是基于新构建工具的create-vue。这两者对应的是Vue 2和Vue 3两个时代。我的建议是如果是新学直接学Vue 3 官方最新脚手架别花时间在旧流程上。不仅因为Vue 3是现在的默认主流更因为新脚手架的启动速度和开发体验明显更好而且官方文档全面转向了Vue 3学习材料不会过时。2.2 用官方脚手架五分钟建一个项目在准备好的目录下执行下面这行命令npm create vuelatest回车之后终端会问你几个问题项目名叫什么、要不要加TypeScript、要不要路由、要不要状态管理库等。第一次学习时除了项目名其余都可以选No保持最小化配置。这样生成出来的项目结构最干净不会出现一堆暂时看不懂的文件干扰你。命令执行完后按照提示继续cd 你的项目名 npm install npm run devnpm install会安装项目依赖执行完会出现一个node_modules文件夹这个文件夹体积很大但不用担心它就是当前项目的依赖库已经被忽略上传的配置处理好了。npm run dev是启动开发服务器终端会输出一个本地地址一般是localhost:5173。浏览器打开它页面上会出现一个默认的示例页面这就说明你的Vue项目正式跑起来了。整个过程五分钟以内搞定。区别于以前动不动上百个配置文件的方式新脚手架把大量配置都藏在了底层你只管写业务代码。这对新手特别友好——你不需要在入门第一天就去理解模块打包是怎么回事。2.3 新建项目的目录结构逐行看项目生成后你会看到一堆文件。先别慌真正的开发只关心这几个index.html整个应用的HTML宿主文件Vue挂载的入口就在这里。src/main.js应用的启动文件负责创建应用实例并挂载到页面上。它是Vue应用的生命起点。src/App.vue根组件比喻成整个页面的“外壳”你写的内容最终都从这里分发下去。src/components/存放你自己拆出的组件像抽屉一样往里加文件即可。vite.config.js构建工具的配置文件新手先不用动等你需要配置代理、路径别名时再回头研究。第一次看会有“这到底是怎么把页面显示出来的”的困惑。简单说main.js加载了App.vueApp.vue里写了模板和逻辑最终渲染到index.html里一个特定标签上。这个链路是固定的照着这个思路去读代码比死记硬背强。有两个文件你不用急着研究public目录下的静态资源和src/assets里的公共样式。前者是直接原样发布的文件后者是经过构建处理的资源。入门阶段知道它们各自存在就行用到了再深入了解。3. 三块硬骨头模板、响应式数据、指令怎么记3.1 模板语法和指令写页面就像填空Vue的单文件组件长这样一个文件里同时写模板、脚本和样式三者各占一块区域。模板部分几乎就是HTML但多了几个特殊写法。最基础的是插值。你在script里定义一个变量模板里用双大括号把它显示出来script setup const message 你好Vue /script template p{{ message }}/p /template这里{{ message }}会在页面直接显示“你好Vue”。注意如果脚本里用的是响应式变量这里会自动保持同步更新如果是普通常量就只是一次性渲染。指令则是模板里以v-开头的特殊属性常用的就几个。v-bind用来动态绑定属性简写是一个冒号比如:class、:hrefv-on用来监听事件简写是比如click、inputv-model专门处理表单输入实现数据的双向绑定v-if控制元素是否存在v-for遍历列表渲染。我当年学指令时最晕的是v-if和v-for能不能一起用。官方不推荐在同一个元素上同时使用因为v-for优先级更高某些场景下判断不够直观。遇到需要判断又需要循环的情况建议在循环外面包一层判断或者用计算属性先过滤再循环。这个习惯能帮你少踩很多莫名其妙的渲染问题。3.2 ref和reactive响应式数据怎么选现在Vue 3里声明响应式数据主要用两个APIref和reactive。先看对比对比项refreactive适用数据类型基本类型为主也支持对象只能传对象/数组脚本中访问需要.value直接访问属性模板中访问自动解包不用写.value直接访问属性解构赋值后用toRefs恢复响应直接解构会丢失响应推荐程度日常优先推荐适合局部复杂对象ref的作用是把一个值包装成响应式对象。它接收一个初始值返回一个带有.value属性的对象。在脚本逻辑里修改需要用count.value这样的写法但在模板里系统会自动帮你解包直接写{{ count }}就行不用加.value。这个设计有时候会让新手困惑但记住一句话就够模板里不用写脚本里要写。reactive则直接接收一个对象内部所有属性都会被代理脚本里可以直接state.name访问。它看起来比ref简洁但有一个大坑如果对reactive返回的对象进行结构赋值比如const { name } state拿到的属性就不再是响应式的了。修复方案是借助toRefs转换后再解构。所以我个人的建议是默认用ref遇到需要把一组相关数据放在一起时用reactive并且不要对它做解构。虽然ref的.value写起来略显繁琐但它让数据灵不灵、什么时候需要解包变得非常明确维护成本反而更低。3.3 computed和watch别把两者搞混很多新人会在computed和watch之间纠结数据变化后要做事情究竟用哪个我的判断标准很简单——你是为了“算出新值”还是为了“执行副作用”。computed计算属性它根据一个或多个响应式数据派生出一个新值并且带缓存。比如购物车里的总价它依赖于商品列表和数量不需要你手动维护只要声明了依赖关系数据一变总价自动变。还有列表的过滤结果也是典型的计算属性场景。它必须像函数一样返回一个值模板里可以像普通变量一样直接使用。watch监听器它观察一个响应式数据的变化然后执行一段带有副作用的代码。典型场景是搜索框内容变化后去请求接口、或者每次登录状态变化后把用户信息写入本地存储。监听的目标可以是ref、reactive对象的某个属性也可以是getter函数。监听对象属性时最好写成函数形式watch(() state.userName, (newName) { console.log(用户名变了, newName) })经验法则就是能用computed解决的值派生问题别用watch手动改别的变量。我见过不少代码用watch监听A然后去改B实际上完全可以用computed一步生成B写起来更简洁也不容易造成数据流混乱。4. 组件化把页面拆成互不打架的积木4.1 组件到底拆到什么颗粒度组件化是Vue最有价值的思维它解决的不只是复用问题更多是隔离问题。一个页面里如果同时有搜索框、列表、弹窗、分页全部耦合在一起任何一处改动都可能牵动全局。拆成组件后每个组件只负责自己那一块的渲染和交互数据和样式都用scoped隔离开思路清楚很多。颗粒度怎么把握我的习惯是一段区域可以被独立描述就值得拆成组件。比如“任务输入框”是一个组件“任务列表”是一个组件列表里的“单条任务”又是一个组件。但这种拆分也不是越细越好如果一个组件模板只有一行HTML却要层层传递数据那反而过度设计了。你拆完之后会觉得每个文件都能单独读得懂那就对了。另外一个实战细节组件文件命名。我用的是多单词加首字母大写的风格比如TodoItem.vue而不是Item.vue。这在大型项目里能显著减少命名冲突也让组件含义更清晰。开发工具和规范都能自动处理这种命名所以一开始就养成习惯不吃亏。4.2 父子组件通信props向下、emit向上组件之间最基本的通信方式就两种父组件通过props把数据传给子组件子组件通过emit触发事件告诉父组件。这个方向永远是单向的不允许子组件直接修改props传来的值。为什么要这么设计因为单向数据流能让数据变化可追踪——你知道数据从哪里来也知道它会被谁改排查问题不会陷入互相修改的循环。先说props。父组件在模板里给子组件标签传值子组件用defineProps声明接收!-- 父组件 -- TodoItem :textitem.text :doneitem.done / !-- 子组件 -- script setup const props defineProps({ text: String, done: Boolean }) /script子组件拿到props后只能读取如果想让父组件知道“用户把这一项标记为完成了”就要用emit向上发消息。子组件里先声明defineEmits然后在事件回调里触发script setup const emit defineEmits([toggle]) const handleClick () { emit(toggle) } /script父组件则在子组件标签上监听这个自定义事件TodoItem toggletoggleItem(index) /这个模式刚接触会有点绕但它其实是浏览器事件机制的翻版点击事件是浏览器通知你自定义事件是子组件通知父组件。理解了这个类比组件通信就算是入门了。4.3 插槽让组件外壳变成开放接口还有一种通信机制不容易绕明白就是slot插槽。它解决的场景是组件的外壳和内部布局是固定的但某个区域的内容需要由父组件来决定。最典型的例子是弹窗组件——弹窗的关闭按钮、标题位置都是写死的但弹窗里放什么内容每个页面都不一样。父组件在使用弹窗时往标签内部写入内容BaseDialog p这里是自定义内容/p /BaseDialog子组件里用slot /标记内容渲染的位置当父组件传了内容时就会显示没传时显示默认内容。更进一步还有具名插槽一个组件里可以开多个口比如头部插槽、底部插槽。这个能力让组件的复用度上了一整个台阶。插槽不需要第一天就全都掌握但你需要知道它存在。等你在项目里遇到“明明只是外层样式不一样内容却要全部复制一遍”的情况时你就明白该用插槽了。5. 练手项目一个待办清单从拆需求到跑通5.1 先拆需求和文件结构别急着写代码光看语法容易飘动手做一个完整例子才有真正的体感。我选的是待办清单Todo因为它的复杂度刚好能覆盖入门所有核心概念表单输入、列表渲染、事件通信、计算属性。先拆需求。一个最小可用的待办清单需要具备这么几个能力在输入框里写任务按回车添加列表展示所有任务点击任务可以切换完成状态可以删除任务显示未完成数量按“全部/未完成/已完成”三种方式筛选文件结构规划如下App.vue管整体数据和筛选状态TodoInput.vue管输入框TodoList.vue管列表整体TodoItem.vue管单条任务的显示和交互。四个组件互相配合数据集中在App层自顶向下单向传递。5.2 逐个写出组件输入、列表、单选逻辑数据的核心结构是数组每个任务有三个字段唯一标识id、文本内容text、完成状态done。在App.vue里先用ref定义这个数组script setup import { ref, computed } from vue const todos ref([]) const filter ref(all) const addTodo (text) { todos.value.push({ id: Date.now(), text, done: false }) } const toggleTodo (id) { const item todos.value.find(t t.id id) if (item) item.done !item.done } const removeTodo (id) { todos.value todos.value.filter(t t.id ! id) } const filteredTodos computed(() { if (filter.value active) return todos.value.filter(t !t.done) if (filter.value done) return todos.value.filter(t t.done) return todos.value }) /script注意几个设计细节任务的id用时间戳生成足够这个场景使用切换完成状态和删除时都通过id定位任务而不是通过数组下标筛选逻辑用computed派生出来不修改原始数组。这些都是实战中养成的习惯核心原则是让数据流清晰、可预测。输入框组件负责接收内容并触发添加事件。它内部有一个ref保存输入值回车时把内容通过emit发给父组件然后清空输入框script setup import { ref } from vue const inputValue ref() const emit defineEmits([add]) const handleSubmit () { if (!inputValue.value.trim()) return emit(add, inputValue.value.trim()) inputValue.value } /script template input v-modelinputValue placeholder输入任务后按回车 keyup.enterhandleSubmit / /template这里v-model实现了输入框内容和inputValue的双向绑定省去了手动监听输入事件再赋值的两行代码。加上keyup.enter修饰符只响应回车键不用在函数里手动判断按键码这也是Vue语法糖减少样板代码的典型例子。单条任务组件接收text和done两个props再向上抛出toggle和remove事件。样式上完成任务加删除线按钮删除当前项script setup defineProps({ text: String, done: Boolean }) const emit defineEmits([toggle, remove]) /script template li :class{ done } span clickemit(toggle){{ text }}/span button clickemit(remove)删除/button /li /template style scoped .done span { text-decoration: line-through; color: gray; } /stylescoped样式只对当前组件生效不会污染外部。给li动态绑定一个done类名配合样式规则实现完成态的划线效果这就是:class指令结合对象语法的实战用法。最后在App.vue的模板里把三块串起来顶部输入框、中部筛选按钮、底部列表。筛选按钮通过点击修改filter变量列表组件接收filteredTodos并循环渲染TodoItemtemplate section classtodo-app h2待办清单/h2 TodoInput addaddTodo / div classfilters button clickfilter all全部/button button clickfilter active未完成/button button clickfilter done已完成/button /div p剩余未完成{{ todos.filter(t !t.done).length }}/p TodoList :todosfilteredTodos toggletoggleTodo removeremoveTodo / /section /template剩余未完成数量直接在模板里用表达式计算虽然略微重复但胜在直观。等你再熟练一点可以把这部分也提成computed但入门阶段保持代码可读性比追求完美更重要。5.3 验证交互然后想三条扩展方向运行起来之后你按下面的路径把功能点全部过一遍输入内容按回车确认新增剩余数量随之变化点击任务文字确认划线效果出现点击删除按钮确认任务被移出列表切换筛选按钮确认列表内容按照“全部/未完成/已完成”正确显示。这个过程不只是在测试代码更是在感受Vue的响应式更新——所有UI变化都是数据驱动的你没有手动操作任何DOM。跑通以后我建议你立刻做三个扩展练习每个练习都不需要看教程试着自己实现给任务加一个双击编辑的能力把文本变成输入框回车后保存把任务列表持久化到本地存储刷新页面数据不丢按创建时间倒序排列任务让新添加的在最上面这三个练习分别涉及事件和条件渲染、生命周期钩子、computed排序都是常用技能。做完这三个你对Vue的掌握深度会明显不一样。6. 新手的坑我踩过的和见过别人踩的6.1 响应式失效.value、解构、深层监听最经典的一个坑是ref的.value问题。模板里能直接用count脚本里忘了写就成了count结果页面不动代码也不报错排查半天发现是操作了一个不存在响应式关系的普通属性。新手阶段只要发现“数据变了页面没反应”第一反应先检查脚本里ref变量有没有加.value。第二个坑是解构丢失响应。你有一个reactive对象为了图方便写了const { name, age } form之后直接改name页面毫无反应。这是因为解构出来的新变量已经脱离了响应式代理。应对措施有二能用ref就多用ref不要想着把一堆数据塞进一个对象再解构非要用reactive解构时借助toRefs。第三个坑和watch有关监听一个reactive对象时如果你直接监听对象本身而不指定属性任何属性变化都会触发回调回调内部又去读其他属性容易出现意料之外的连锁反应。需要监听某一项时记得把监听源写成返回该属性的函数。深层监听设为deep: true也要谨慎对象很大时会产生性能开销。6.2 v-for为什么要key以及为什么不用index渲染列表时官方强制要求绑定key属性这是为了方便框架在数据变化时做精确对比复用已有的DOM节点。如果漏写key或随便绑一个固定值Vue只能靠元素顺序去匹配一旦列表顺序变了状态和DOM可能对不上。更实际的问题是为什么不让用index当key。用一个例子说明你有三行数据删除中间一行如果key是index那么原来第三行的元素会被当成“原第二行的元素”去复用它的本地状态不会正确销毁。这在只有纯文本的列表里看不出问题一旦列表项里有输入框、选中态、动画就会出现状态错位的诡异现象。所以key必须要用一个稳定且唯一的标识。最理想的是数据本身的id比如接口返回的编号在没有的情况下可以自己生成唯一值就像待办清单里用Date.now()。这是我在实际项目里吃过亏之后才真正理解的希望你能一开始就避开。6.3 scoped样式和组件通信的常见误会scoped样式是我强烈建议你每次写组件都加的属性。它会给当前组件的元素加上一个随机的数据属性标识样式规则只匹配带这个标识的元素实现真正的隔离。不加scoped时你写.title { color: red }项目里所有叫title的元素都可能变红这种干扰在多人协作时极其恼人。但scoped也有一个副作用它会影响子组件根元素的样式。父组件想微调子组件的根元素样式时直接写类名是生效不了的因为那个元素属于子组件的作用域。解决办法是用:deep()选择器穿透作用域。这个用法不必现在就深究但遇到“样式怎么都改不动”的时候可以先想想是不是scoped阻断了穿透。组件通信方面的误会也不少。最常见的是所有数据都想通过全局状态管理一把梭结果把简单页面搞得异常复杂。我个人建议遵循“能props就不emit能父子联动就不全局确实需要跨层传递时再考虑状态管理库”的原则。还有一个容易踩的坑是emit事件名的大小写和连字符问题模板里监听事件时对大小写不敏感建议全用kebab-case命名避免奇怪的不生效问题。6.4 三个值得早点养成的小习惯最后一个部分分享三个习惯不算什么高深技术但能让你后续的学习顺畅很多。一是组件逻辑尽量往脚本setup里放模板里只做展示。我见过很多初学者喜欢在模板里写复杂的表达式比如{{ list.filter(...).map(...) }}一长串根本没法读。正确的做法是在脚本里定义一个computed模板里只写一个友好的变量名。模板负责“长什么样”脚本负责“怎么算”这个边界越清晰代码越好维护。二是利用开发工具看响应式状态。Vue提供了一个浏览器扩展装上之后页面上的组件树、每个组件的props和响应式数据都一目了然。排查问题的时候先看数据的状态对不对再判断是不是模板用法出了问题比盲改代码高效得多。三是动手之前先想一下数据结构。很多人写项目容易越写越乱根源不在代码技巧而在开始时没有把数据规划好。数组还是对象、嵌套还是平铺、唯一标识是什么想清楚了直接决定后续代码的复杂度。动手写界面很容易规划数据才是需要多花时间的地方。学Vue框架这件事最大的障碍往往不是语法而是“不知道学了之后到底怎么用”。所以这个系列的第一篇我刻意选择用一个真实的小项目收尾——当你能独立把待办清单跑通并且理解每一行代码为什么这么写的时候你已经有能力继续向路由、状态管理、工程化这些方向深入了。如果你也是刚开始学建议你现在就去把项目建起来亲手敲一遍这份代码那会比看十遍文档更有效果。

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

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

免费获取报价 →
↑