尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Vue3入门指南:从响应式原理到组合式API实战

Vue3入门指南:从响应式原理到组合式API实战 很多初学者学 Vue3 时第一反应是打开官方文档从安装开始一步步看。看着看着就发现不对劲文档里全是script setup、ref、reactive、defineEmits这些新名词和网上那些 Vue2 教程完全不在一个频道。然后就开始怀疑是不是自己太笨我见过不少从 Vue2 转 Vue3 的开发者也带过完全零基础的小白。一个比较扎心的判断是Vue3 真正的学习难点不在语法而在思维方式的转换。你只要理解了它“把所有逻辑按功能组织在一起”这一条核心思想后面的语法、组件通讯、路由配置都只是水到渠成的事。这篇文章不打算带你把官方文档全部抄一遍而是按照一个零基础小白真正会遇到的学习顺序来安排先搞懂 Vue3 和 Vue2 的区别再动手把项目跑起来然后掌握最常用的组合式 API最后解决实际开发中经常出现的几个坑。读完你会有一份可以直接照做的知识框架而不是一堆散乱的代码片段。1. 学 Vue3 前必须想清楚的一件事很多新手问的第一个问题是“Vue2 我还没学好可以直接学 Vue3 吗”我的建议很直接如果完全没接触过 Vue直接学 Vue3如果已经会 Vue2更要学 Vue3。为什么因为 Vue3 不是 Vue2 上面增加了一点新功能而是把组件代码的组织方式重新设计了一遍。这件事带来的影响比大多数人以为的要大得多。Vue2 的写法叫“选项式 API”Options API一个组件被拆成data、computed、methods、watch这几个固定格子。这种写法对新手友好但你写一个稍微复杂一点的组件就会发现同一个功能的代码被拆散到不同位置。比如一个“搜索商品”的功能搜索关键词放在data里搜索逻辑放在methods里搜索防抖放在watch里。一旦要修改这个功能你得在组件里上下翻找。Vue3 的组合式 APIComposition API解决的就是这个问题。它允许你按“功能”而不是按“选项类型”来组织代码。搜索关键词、搜索逻辑、防抖逻辑可以写在一起甚至抽到一个独立的函数文件里。这个变化看起来只是代码组织方式变了但对项目的可维护性影响是质变。所以学 Vue3 的第一步不是背语法而是接受一个观念转变从“按数据分类”变成“按功能聚合”。想通了这一点后面学ref、computed、watch的时候你自然知道它们应该放在哪里。2. Vue3 和 Vue2 的核心差异一张表看清变化如果你准备面试或者准备从 Vue2 项目迁移到 Vue3这一节内容可以当成速查笔记。Vue3 和 Vue2 的核心差异集中在四个层面响应式原理、API 组织方式、组件写法、工程生态。对比维度Vue2Vue3响应式原理Object.defineProperty只能监听已有属性新增属性需要$setProxy可以监听整个对象新增/删除属性都能自动触发更新API 组织方式选项式 APIdata、methods、computed分开写组合式 API按功能聚合也可以保留选项式写法组件根节点必须有单个根节点支持 Fragment多个根节点不会报错生命周期beforeDestroy、destroyedbeforeUnmount、unmounted状态管理Vuex 3Vuex 4 或官方推荐 Pinia构建工具主要用 Vue CLI / Webpack官方推荐 Vite冷启动明显更快TypeScript支持较弱底层用 TypeScript 编写类型推断更友好常用语法v-model默认绑定value和input默认绑定modelValue和update:modelValue支持多v-model这里面最值得展开的是响应式原理的变化。Vue2 用的是Object.defineProperty它只能拦截对象上已存在的属性的读写操作。所以你在 Vue2 中给data里的对象直接添加一个新属性页面上不会更新必须调用this.$set。这是一个经典面试题也是 Vue2 用户经常踩的坑。Vue3 换成了Proxy可以直接拦截整个对象的读取、写入、删除、遍历等操作。所以给对象新增属性、删除属性响应式系统都能自动感知到。$set这个方法也就退出了历史舞台。这一点对新手是好事——少记一个 API少踩一个坑。另一个对开发体验影响很大的变化是构建工具。Vue3 官方推荐 Vite而不是 Webpack。Vite 用浏览器原生 ES Module 来做开发服务器冷启动和热更新速度都比 Webpack 时代快很多。这也是为什么现在新建 Vue3 项目基本都是 Vite 起步。小结论Vue2 和 Vue3 的差异不是补丁式升级而是底层架构和编程思想的整体换代。你不需要焦虑但需要重新建立一套心智模型。3. 环境准备与项目搭建用 Vite 跑通第一个 Vue3 项目进入实操环节。先把环境准备好然后把项目跑起来。3.1 安装 Node.jsVue3 项目依赖 Node.js 环境。建议安装 LTS长期支持版本例如 Node 18 或 Node 20。如果已经装了其他版本也可以使用版本请以实际情况为准不建议使用过旧的版本。安装完成后在终端里验证node -v npm -v能看到版本号输出就说明 Node 和 npm 都正常。3.2 使用 Vite 创建 Vue3 项目打开终端执行下面的命令npm create vitelatest vuedemo -- --template vuevuedemo是项目名称可以换成你自己想要的目录名。--template vue表示创建一个 Vue3 的基础模板项目。命令执行后进入项目目录并安装依赖cd vuedemo npm install依赖安装完成后启动开发服务器npm run dev终端会输出一个本地访问地址默认是http://localhost:5173。在浏览器里打开这个地址看到 Vite Vue 的欢迎页面就说明你的第一个 Vue3 项目已经成功跑起来了。3.3 项目目录结构说明用 Vite 创建的项目目录结构比 Vue CLI 简洁得多vuedemo ├── index.html ├── package.json ├── vite.config.js └── src ├── App.vue ├── main.js ├── assets/ └── components/初学者最容易忽略的是index.html。在 Vite 项目中index.html在项目根目录而不是public目录。它里面有一个div idapp/divVue 应用最终会挂载到这个节点上。打开src/main.js你会看到// src/main.js import { createApp } from vue import App from ./App.vue createApp(App).mount(#app)这就是 Vue3 创建应用的入口。createApp是 Vue3 的新 API和 Vue2 的new Vue()相比它更强调“应用实例”的概念。多个 Vue 应用可以在同页面共存互不干扰这在微前端场景下很有价值。运行结果判断标准浏览器能打开默认欢迎页并且修改App.vue的文本内容后页面自动刷新说明开发环境完全正常。4. script setup 语法糖入门第一个门槛打开一个 Vue3 单文件组件你会发现第一行往往是script setup。这个写法是 Vue 3.2 开始提供的语法糖现在已经是 Vue3 组件开发的事实标准。4.1script setup解决了什么问题在script setup出现之前组合式 API 的代码要写在setup()函数里并且必须手动返回变量和函数模板才能使用。写法比较繁琐import { ref } from vue export default { setup() { const count ref(0) const add () { count.value } return { count, add } } }有了script setup上面的代码可以简化为script setup import { ref } from vue const count ref(0) const add () { count.value } /script template button clickadd点击了 {{ count }} 次/button /template注意区别顶层声明的变量和函数模板里可以直接用不需要return。这就是“语法糖”的含义——背后仍然是setup()函数但开发者少写了很多样板代码。4.2 使用script setup的注意事项第一个注意点script setup里不能使用export default它不是普通的组件配置对象而是一个被编译处理的特殊块。第二个注意点顶层await可以直接用组件会自动变成异步组件这在需要顶层请求数据时比较好用。第三个注意点引入组件后不需要注册直接使用即可。比如你在script setup里import Child from ./Child.vue模板中直接用Child /不需要在components选项里再注册一遍。小结论script setup不是可学可不学的进阶技巧而是 Vue3 组件开发的基础。你以后看到的 Vue3 代码绝大多数都是这种写法。建议从一开始就按这个方式练习不要绕道走setup()函数的老写法。5. 响应式核心ref、reactive、computedVue3 的响应式系统围绕两个 API 展开ref和reactive。它们一个管基本类型一个管对象类型再加上computed用来处理派生数据这套组合覆盖了绝大多数业务场景。5.1 ref基本类型响应式的首选ref的作用是把一个值变成响应式引用。注意访问方式在 JavaScript 代码中必须用.value读取和修改在模板中会自动解包直接写变量名即可。script setup import { ref } from vue const count ref(0) const inc () { count.value } /script template p当前计数{{ count }}/p button clickinc加 1/button /template这里真正容易踩坑的地方是很多新手在 JavaScript 逻辑中写count或count 1结果发现页面不更新。原因就是忘记.value。模板里可以直接写count是因为编译器帮你做了解包处理但script里的代码不会有这种待遇。5.2 reactive对象响应式的选择如果你要响应的数据是一个对象可以选reactivescript setup import { reactive } from vue const user reactive({ name: 张三, age: 18 }) const updateName () { user.name 李四 } /script template p{{ user.name }}{{ user.age }} 岁/p button clickupdateName修改姓名/button /template使用reactive时读写属性不需要.value写法更接近普通对象。但有一个重要的限制reactive只能处理对象类型数组、对象、Map、Set 等不能处理基本类型。而且如果你解构reactive对象比如const { name, age } user解构出来的name和age会丢失响应式。这是因为响应式代理建立在对象本身解构后拿到的只是那一刻的原始值。要解决这个问题要么用toRefs保持结构后的响应式要么直接用ref来定义。5.3 新手到底用 ref 还是 reactive我的建议很明确在script setup里优先使用ref。原因有三个ref可以为基本类型创建响应式reactive不行。ref在模板中自动解包写起来和普通变量没区别。ref可以很方便地整体赋值或重置而reactive整体替换对象时需要小心因为直接user xxx会丢失响应式。reactive并不是不能用而是更适合“整个对象就是要被深层读写、不会被整体替换”的场景。如果你分不清就统一用ref这个选择在 95% 的场景下都不会错。5.4 computed派生数据自动更新computed用于创建“依赖其他响应式数据、自动计算更新”的值。看一个常见搜索场景import { ref, computed } from vue const keyword ref() const list ref([Vue, React, Angular]) const filteredList computed(() { return list.value.filter(item item.includes(keyword.value)) })当keyword变化时filteredList会自动重新计算模板里绑定filteredList会得到最新的过滤结果。这个计算过程是有缓存的只要依赖没有变化多次访问filteredList不会重复执行计算函数。这是computed和watch加手动变量之间的核心区别。小结论ref computed是 Vue3 日常开发最高频的组合。ref管数据computed管派生数据watch仅在需要处理副作用比如异步请求时使用。理解了这个分层你就不会被大量 API 淹没。6. 组件通讯props、defineEmits、provide/inject一个项目是由多个组件组合而成的组件之间必然需要传数据。Vue3 中三种通讯方式覆盖了绝大多数场景props父传子defineEmits子传父provide/inject跨层级共享。6.1 父组件通过 props 传数据给子组件先看父组件App.vue!-- 父组件src/App.vue -- script setup import { ref } from vue import Child from ./components/Child.vue const message ref(这是来自父组件的消息) /script template Child :msgmessage / /template再看子组件Child.vue!-- 子组件src/components/Child.vue -- script setup defineProps({ msg: { type: String, default: } }) /script template p{{ msg }}/p /template注意点在script setup中props通过defineProps声明不需要导入直接调用。这里要特别提醒模板里可以直接使用msg但script中获取 props 需要用变量接收比如const props defineProps(...)然后通过props.msg访问。6.2 子组件通过 defineEmits 通知父组件defineEmits用来声明子组件可以触发的事件通过这种方式把消息发给父组件。子组件!-- 子组件src/components/Child.vue -- script setup const emit defineEmits([updateText]) const sendMessage () { emit(updateText, 这是子组件发出的消息) } /script template button clicksendMessage发送消息给父组件/button /template父组件!-- 父组件src/App.vue -- script setup import { ref } from vue import Child from ./components/Child.vue const text ref(等待子组件消息) const handleUpdateText (value) { text.value value } /script template p{{ text }}/p Child update-texthandleUpdateText / /template注意事件名的大小写规则在模板中推荐使用 kebab-caseupdate-text在defineEmits中声明时使用 camelCaseupdateText。Vue 会帮你做匹配但初学者经常在这里搞混。事件名不遵循v-model的命名约定时写法要保持一致。6.3 跨层级直接共享provide / inject如果组件层级很深一层层传 props 会非常痛苦。比如页面最外层传一个用户信息给第七层嵌套的按钮组件中间所有层都要写一遍:useruser。provide/inject就是为了解决这类问题上层提供数据下层直接注入中间组件无需感知。// 顶层组件 import { provide, ref } from vue const userInfo ref({ name: 张三, role: admin }) provide(userInfo, userInfo)// 底层组件 import { inject } from vue const userInfo inject(userInfo) // 模板中直接使用 userInfo.name这里有一个热词引发的常见问题provide的数据如果是ref注入方拿到后修改它顶层组件会同步更新这是响应式特性。如果你只想共享静态数据也可以直接传普通字符串或对象但跨组件共享的可变数据建议用ref包装。小结论组件通讯的选择顺序是——兄弟/父子用 props emits跨层级用 provide/inject全局数据用 Pinia。不要一上来就上状态管理库很多场景根本用不到。7. 路由、Axios 与日常开发高频操作项目不可能只有一个页面。这一节解决三个高频操作路由配置、在setup中用 Axios 发请求、以及两个容易卡住新手的小问题。7.1 Vue Router 4 的基本配置如果项目需要多个页面先安装 Vue Router 4npm install vue-router4然后在src目录下新建router/index.js// src/router/index.js import { createRouter, createWebHistory } from vue-router import Home from ../pages/Home.vue import About from ../pages/About.vue const router createRouter({ history: createWebHistory(), routes: [ { path: /, name: home, component: Home }, { path: /about, name: about, component: About } ] }) export default router在入口文件main.js中注册路由// src/main.js import { createApp } from vue import App from ./App.vue import router from ./router createApp(App).use(router).mount(#app)模板中通过router-view /渲染当前路由对应的组件用router-link to/about代替普通的a标签做页面跳转。7.2 路由跳转不刷新页面useRouter 与 useRoute热搜词中有一个很有代表性的问题“vue3 路由跳转不刷新页面”。这通常不是 bug而是没有理解路由单页应用的特性。Vue 路由切换时默认只会切换router-view /部分的组件页面不会整页刷新。如果你在跳转到同一个组件比如/user/1跳到/user/2时希望组件重新执行初始化逻辑需要在跳转后监听路由变化。import { useRoute } from vue-router import { watch } from vue const route useRoute() watch( () route.params.id, (newId) { console.log(路由参数变化重新加载数据, newId) } )另一个常见操作是编程式跳转import { useRouter } from vue-router const router useRouter() const goHome () { router.push(/) }7.3 在 setup 中使用 Axios 请求数据在组合式 API 中发请求常见的套路是ref保存数据loading控制加载状态error保存错误信息。示例// src/api/getUser.js import axios from axios export function getUser(id) { return axios.get(/api/user/${id}) }script setup import { ref, onMounted } from vue import { getUser } from ../api/getUser const userInfo ref(null) const loading ref(true) const errorMsg ref() onMounted(async () { try { const res await getUser(1) userInfo.value res.data } catch (e) { errorMsg.value e.message || 请求失败 } finally { loading.value false } }) /script这个模式值得多说一句把请求逻辑放在onMounted里而不是直接写在script setup顶层是为了保证组件挂载后再发请求。如果你在顶层直接await虽然语法上允许但会增加组件渲染的复杂度和时序不确定性。新手先按“初始化数据放onMounted”的规则来最稳。实际项目中建议再用loading做状态判断template div v-ifloading加载中.../div div v-else-iferrorMsg{{ errorMsg }}/div div v-else{{ userInfo?.name }}/div /template7.4 自定义指令Vue3 的指令生命周期变化Vue3 的自定义指令也发生了 API 变化。Vue2 中你常用bind、inserted、updateVue3 统一改成了beforeMount、mounted、updated、unmounted。看一个自动聚焦示例// src/directives/focus.js export default { mounted(el) { el.focus() } }在组件中使用script setup import focus from ./directives/focus /script template input v-focus / /template在script setup中以v开头的变量名类似vFocus或导入的focus会直接被识别为指令模板里用v-focus即可。小结论路由、请求、指令都是“遇到时再查”的知识不必提前背完。你只需要知道它们的存在并且知道在什么场景下应该去查哪一类文档学习效率反而更高。8. 常见问题与排查思路以下是 Vue3 新手在入门阶段最常遇到的几类问题按“现象—原因—排查—解决”的格式整理。建议收藏遇到问题对照排查。问题现象可能原因排查方式解决方案修改数据后页面不更新忘记ref的.value或直接替换了reactive对象在事件回调里打印数据确认是否修改的是响应式引用访问和修改用.value替换对象时给整体赋新值重新赋值给ref解构reactive后数据不响应解构出的变量是原始值丢失了代理检查是否对reactive对象做了解构赋值用toRefs解构或改用ref使用事件名update-text不生效事件声明和模板中的大小写不一致检查defineEmits里的命名和模板绑定声明用 camelCase模板用 kebab-case或全部保持一致路由切换后页面内容不变同一个组件实例被复用没有重新加载数据确认是否在监听路由参数变化用watch监听路由参数并重新请求数据组件在script setup中无法使用插件忘记在main.js中app.use(...)注册插件检查入口文件是否注册了对应插件在createApp(App).use(router).use(pinia).mount(#app)链式注册自定义指令不生效指令生命周期名用 Vue2 的bind/inserted检查指令文件和方法名改用beforeMount、mounted、updatednpm run dev后端口冲突5173 端口被占用看终端是否提示端口占用修改vite.config.js的server.port或换用自动递增端口排查时遵循一个原则先看控制台报错再看网络请求最后看数据状态。新手最常见的错误是只盯着模板看不看报错信息。浏览器 DevTools 里的 Console 面板会告诉你大部分问题出在哪里。9. 最佳实践与后续学习建议学完前面的内容你已经能跑通一个完整的 Vue3 小项目。最后补充一些工程实践层面的建议这些内容来自真实项目和团队协作的经验。9.1 代码组织按功能拆文件在script setup中如果组件的逻辑超过两三块建议把相关逻辑抽到独立的composable文件里。比如一个搜索功能可以抽到src/composables/useSearch.jsimport { ref, computed } from vue export function useSearch(list) { const keyword ref() const results computed(() { return list.value.filter(item item.includes(keyword.value)) }) return { keyword, results } }组件里只需要import { useSearch } from ../composables/useSearch const { keyword, results } useSearch(list)这个模式的好处是功能逻辑可以跨组件复用也可以单独测试。这是组合式 API 相比选项式 API 最大的工程价值。9.2 关于响应式的一个稳定建议如果你和团队成员合作建议在项目规范中明确默认所有响应式数据都用ref只有明确需要深层响应式的对象才用reactive。这种约定能避免很多“为什么数据不更新”的问题。9.3 组件通讯的限制与边界不是所有数据都适合用 props 传。如果多个页面、多个组件需要共享一份数据且数据会频繁变化比如登录用户信息、购物车列表直接引入 Pinia 做全局状态管理会比用 provide/inject 堆一坨更清晰。9.4 学习路线建议按这个顺序继续深入效率和效果更高组合式 API 熟练ref、reactive、computed、watch、生命周期钩子。组件通讯熟练props、emits、插槽、provide/inject。路由与工程化Vue Router 4、Vite 配置、环境变量。状态管理Pinia理解 store 的写法。进阶特性自定义指令、Suspense、动态组件、插件开发。实际项目练手做一个待办事项、后台管理系统、或小型商城页面。9.5 遇到“百度不到”的问题时学 Vue3 最可能遇到的困难是网上教程新旧混在一起不知道哪个是对的。遇到这种情况建议直接查两个可靠来源Vue3 官方文档和搜索报错信息时带上版本号比如“Vue3 script setup defineEmits”。把 Vue2 的解决方案强行套到 Vue3 上是很多项目出现诡异 bug 的根源。到这里Vue3 的主干知识已经全部过了一遍。这套知识框架覆盖了 Vue2 区别、环境搭建、script setup、响应式、组件通讯、路由请求和常见排错。下一步找一个真实场景练手比如把你的个人介绍页面用 Vue3 重写一遍在项目里用一次computed用一次组件通讯用一次路由跳转。代码写够量这些概念才能真正长在你手上。
返回列表