news 2026/9/9 20:10:53

Vue 3 实战笔记:组合式API、响应式原理与工程化部署全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue 3 实战笔记:组合式API、响应式原理与工程化部署全解

Vue 3 正式版发布已经有一阵子了,但直到今天,还是有很多人在问“Vue 3 到底比 Vue 2 强在哪”“项目里要不要上组合式 API”。打开招聘网站搜前端岗,十个里面至少七个写着“熟悉 Vue 3 / 组合式 API”;打开同事的 git log,新模块也基本都是<script setup>的天下。这篇文章就是围绕 Vue 3 写的一份实战向笔记:从环境搭建、核心语法切换,到路由、状态、调试、部署,再到我实际踩过的坑和面试里高频出现的问题,一次给你捋清楚。不管你是刚准备入门前端的新手,还是维护 Vue 2 老项目多年的开发者,又或者是正在准备 Vue 3 面试的候选人,这期内容都值得花点时间看一下。

1. 为什么大家都在迁 Vue 3:选型理由和框架定位

1.1 Vue 3 到底带来了什么

Vue 3 最核心的变化不是“更快了”这么一句话能概括的。它把整个响应式系统重写成了基于Proxy的实现,解决了 Vue 2 里对象新增属性、数组下标修改无法触发更新的历史问题;模板编译器也做了重构,编译出来的渲染函数体积更小、执行效率更高。再加上Composition API的引入,逻辑复用从“mixins 混入”变成了“函数组合”,代码的可维护性明显上了一个台阶。

对普通业务开发来说,最直观的感受是:同一个功能涉及的变量、计算属性和方法可以写在一起,不再像选项式那样被datacomputedmethods劈成好几块。20 行代码看不出差别,当一个组件到 300 行以上时,组合式 API 对阅读和重构的帮助是实打实的。与此同时,Vue 3 对 TypeScript 的支持也是从底层重新设计的,defineComponentrefreactivecomputed这些 API 的类型推导非常丝滑。

如果你是从 Vue 2 迁移过来的,不用太慌。Vue 3 保留了绝大多数模板语法,v-ifv-for组件通信路由这些概念依然成立。真正需要重新适应的核心是“怎么写 setup”,以及“响应式数据在解构之后会丢失响应性”这类细节。后面我会单独讲。

1.2 什么时候你仍然可以留在 Vue 2

我不太喜欢“所有人立刻全量迁移到 Vue 3”这种思路。老项目如果稳定运行、依赖树里有一堆只兼容 Vue 2 的第三方库,那强行升级的成本远大于收益。比如以前项目里用到的某个表格组件只维护 Vue 2 版本,你迁到 Vue 3 可能就得换表、换 API、换交互逻辑,表面上是框架升级,实际上是重写业务。

我的判断标准很简单:新项目无脑选 Vue 3;老项目看两个条件——是否需要长期继续迭代、是否对性能和 TS 有要求。如果老项目只是维护状态,继续待在 Vue 2 完全没问题,但要记住 Vue 2 在 2023 年底已停止维护,安全漏洞不会再有官方修复。所以哪怕不迁,也要尽早规划。长期看,Vue 3 就是接下来的默认选择。

2. 从零搭一个能跑的 Vue 3 项目:环境搭建与工程化

2.1 开发环境:Node、编辑器、Volar

先说你电脑上要准备什么。Vue 3 官方脚手架create-vue要求 Node.js 版本在 18 以上,我用的是 20 LTS,推荐你也用 LTS 版本,别追最新大版本,有些原生模块还没跟上。装完 Node 后 npm 会自带,但更推荐装一个 pnpm,它在依赖安装速度和磁盘占用上比 npm 好不少。安装命令一行搞定:

npm install -g pnpm

编辑器方面,VS Code 依然是主流选择。要注意的是,Vue 3 项目必须安装官方推荐的Vue - Official插件(也就是以往常说的 Volar),同时把旧的Vetur禁用掉,否则模板提示和类型检查会互相打架。Volar 最实用的功能是提供.vue文件内部的模板表达式类型检查和自动导入提示,配合 TypeScript,写起来比纯文本舒服太多。

如果你在内网环境开发,不上外网,也不要慌。思路很简单:在一台能联网的机器上准备好 node_modules 压缩包,或者配置公司内网 npm 镜像源,然后把.npmrc文件里的 registry 指到内网镜像地址,再执行pnpm install即可。内网机器上如果连 Node 都没有,那就直接拷贝一份绿色版 Node 的安装目录,配好 PATH 环境变量,基本就能把环境跑起来。

2.2 create-vue 还是手动配置

大部分人不需要从零写 Webpack/Vite 配置,直接用官方脚手架是最稳的路子。执行:

pnpm create vue@latest

命令行会让你选择是否启用 TypeScript、JSX、Vue Router、Pinia、ESLint、Prettier 等功能,按需勾选就行。我个人的习惯是:新项目必开TypeScriptVue RouterPiniaESLintPrettier。现在脚手架默认用的是 Vite,冷启动速度基本在几百毫秒,开发体验比 Vue CLI 时代好了不止一点。

如果你非要手动搭,流程也不复杂:pnpm create vite,选择vue-ts模板,再把vue-routerpinia装进来。但说实话没必要,create-vue生成了更完整的工程化结构:src/main.tssrc/App.vuevite.config.ts都是标配,还带了.vscode推荐配置,省去很多调编辑器的时间。

2.3 依赖安装与 Element Plus 自动导入

Vue 3 项目里用 Element Plus 做中后台 UI 很常见。很多人装完组件库后直接按全局引入方式写:

import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' app.use(ElementPlus)

这种方式简单,但会把全部组件都打进包里,首屏体积会很大。更推荐按需自动导入。需要装三个包:

pnpm install element-plus pnpm install -D unplugin-vue-components unplugin-auto-import

然后在vite.config.ts里配置:

import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import AutoImport from 'unplugin-auto-import/vite' export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] })

这样你在模板里写<el-button>,它会自动按需引入对应组件和样式。但有个非常常见的坑:在组合式 API 里直接调用ElMessageElMessageBox时,组件没有在模板里被使用,所以Components插件不会自动导入它,运行时会报ElMessage is not defined。解决办法有两个,要么在用到的地方手动引入:

import { ElMessage } from 'element-plus' ElMessage.success('保存成功')

要么在AutoImport里再加一个解析器,让它帮我们自动 import ElMessage 这一类命令式 API。个人建议是手动引入,因为更直观,也避免自动注入的隐藏依赖让团队成员看得一头雾水。

3. 组合式 API 与生命周期:Vue 3 的语法重心

3.1 选项式和组合式,到底怎么选

这是面试里必问的问题,也是很多新人真正上手 Vue 3 时最容易迷茫的地方。简单说,选项式就是 Vue 2 的写法:data里定义数据,methods里定义方法,computed里定义计算属性。组合式则是把同一逻辑相关的代码放到一起,在setup函数或<script setup>中完成。

那是不是组合式一定更好?不是。二三十行的小组件,选项式写起来更紧凑,别人接手看起来也熟悉。但组件一大,选项式的问题就暴露了:你想改某个模块的功能,需要在 data、methods、computed 之间来回跳。组合式可以按“功能块”组织代码,比如用户列表这个功能,你就把userListloadUserListfilteredUserList写在一起。代码量大的时候,组合式的维护优势非常明显。

所以我的建议是:组件复杂度低时,怎么顺手怎么来;一旦组件开始承担多个业务职责,就优先用组合式 API。你完全可以在一个.vue文件里混用,Vue 3 并没有强制要求二选一。但新项目还是建议统一用组合式,团队协作时代码风格一致比个人偏好重要得多。

3.2 setup、ref、reactive 到底怎么用

<script setup>是目前主流写法,它把setup()函数简化成了编译语法糖,顶层变量可以直接在模板里用。定义响应式数据有两个 API:refreactive

<script setup lang="ts"> import { ref, reactive } from 'vue' const count = ref(0) const user = reactive({ name: '张三', age: 20 }) function add() { count.value++ user.age++ } </script> <template> <button @click="add">{{ count }} - {{ user.name }}</button> </template>

记住一个口诀:ref适合“单个值”,reactive适合“对象”。在模板里ref会自动解包,所以你写{{ count }}而不是{{ count.value }};但在 JS 逻辑里一定要写成count.valuereactive只能传对象,它返回的代理对象可以直接访问属性,不过在解构时会丢失响应性。如果你需要在逻辑里解构对象,优先使用toRefs或直接换成ref包对象。

还有个容易踩的坑:ref接收一个对象时,内部其实会调用reactive来包装,所以你用ref({ name: '李四' })也可以,改的时候要obj.value.name = '李四'。两种写法在业务上都能用,团队统一就好。

3.3 生命周期钩子:请求到底放哪里发

Vue 2 里有beforeCreatecreatedmounted等钩子。Vue 3 的组合式 API 里,这些钩子的“调用时机”和“写法”都变了。在<script setup>中,你直接引入onMountedonUnmounted等函数,然后传入回调:

<script setup lang="ts"> import { onMounted, onUnmounted } from 'vue' onMounted(() => { fetchData() }) onUnmounted(() => { clearTimer() }) </script>

注意,组合式 API 里的生命周期函数必须在setup执行同步阶段调用,不能在setTimeout或异步函数里写onMounted,否则 Vue 没法把回调绑定到当前组件实例。至于请求放哪里,我的习惯是:如果数据跟 DOM 渲染无直接关系,可以放在onMounted;如果需要尽快请求,甚至可以在模块加载时就发起请求(配合<Suspense>或外层状态管理),但别写在setup顶层之后就立刻发,因为那时代理对象已经可以用了,但组件还没挂载,依赖 DOM 的逻辑会出问题。

beforeUnmountunmounted用来清理定时器、取消事件监听、关闭 WebSocket。Vue 还会自动清理组件自身的渲染副作用,不需要像 React 那样手动处理所有 effect。真正需要主动清理的,多半是你自己加给窗口或全局事件总线的事件,别漏。

4. 路由、传参和页面交互:高频场景拆解

4.1 路由基础:子路由、动态路由和参数传递

Vue 3 搭配 Vue Router 4,核心概念没变,但 API 变成了createRoutercreateWebHistory。路由文件里常见写法:

const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/user', component: UserLayout, children: [ { path: 'list', component: UserList }, { path: 'detail/:id', component: UserDetail } ] } ] })

子路由的路径不要写开头的/,比如list而不是/list,否则会被当成根路由。动态路由用detail/:id声明,组件里用useRoute()取参:

const route = useRoute() console.log(route.params.id)

如果希望组件通过 props 接收参数,更规范的做法是配置props: true

{ path: 'detail/:id', component: UserDetail, props: true }

对应组件定义const props = defineProps<{ id: string }>(),这样路由参数和普通 props 一样,便于组件复用。query参数(?name=xx)则用route.query获取。注意,当你在同一组件内切换路由,比如从/user/1跳到/user/2,组件实例默认会被复用,onMounted不会再次触发。正确的做法是监听route.params的变化,或者在路由配置上给组件加key。用<router-view :key="route.fullPath" />是最暴力的解法,但会重建整个页面,性能差一些,建议只在确实需要重置状态的场景用。

4.2 对象赋值页面不刷新:响应式丢失的几种解法

“明明改了数据,页面却不更新”是 Vue 3 里非常高频的问题。尤其从 Vue 2 迁移过来的同学,习惯写this.user = res.data,在 Vue 3 里如果user是用reactive定义的整对象,这种赋值方式在新版 Vue 3 中是支持整体替换的,并不会像 Vue 2 那样丢失新增属性。但真正的问题出在“解构”和“直接替换某个深层字段”。

最常见的错误是:

<script setup> const user = reactive({ info: { name: 'Tom' } }) const { info } = user info.name = 'Jack' // 响应式仍会更新,因为 info 本身是响应式对象 const { name } = user.info name = 'Jack' // 这是基本类型,解构后就是普通变量,改它不触发更新 </script>

所以,在组合式 API 里,尽量少对reactive对象做深层解构。如果你确实需要解构,使用toRefs

const { info } = toRefs(user) info.value.name = 'Jack'

还有一种场景是从接口返回的数据直接塞给一个已定义的对象,比如:

const list = ref([]) list.value = await getList()

这里ref是没问题的,因为list是 ref 对象,替换.value会触发更新。真正会出问题的是用ref包了一个数组,然后直接list.value.push(...items),这其实也能触发更新,因为 ref 内部是 reactive。比较隐蔽的坑是:在v-for里面直接修改数组子项的属性,如果子项是普通对象包在ref数组内,嵌套仍是响应式的,但如果用Object.freeze冻结过或有大量非响应式数据,就不会更新。遇到这种问题,别急着怀疑框架,先在onMounted里打印数据,看看改动前后响应式代理是否还是同一个。

4.3 click 截流与按钮防重复提交

业务里最常见的场景是用户双击提交按钮,导致接口发了两次。直接写一个通用的防抖/节流并绑定到 click 上,是最快的解决方式。简单封装一个指令:

import { Directive, DirectiveBinding } from 'vue' const throttle = { mounted(el: HTMLElement, binding: DirectiveBinding) { const delay = binding.value || 1000 let lastTime = 0 el.addEventListener('click', (e: Event) => { const now = Date.now() if (now - lastTime < delay) { e.preventDefault() e.stopPropagation() return } lastTime = now }) } }

然后在组件里注册v-throttle

<template> <button v-throttle="2000" @click="submit">提交</button> </template>

这个指令只负责拦截高频触发,具体的submit逻辑还是走普通@click。注意,这个实现有点粗糙,因为指令在阻止第二次点击时会阻止事件冒泡,可能影响其他 UI 行为。更精细的做法是在业务层加一个loading状态,按钮在请求期间设置disabled,这是最推荐的方式:

<template> <el-button :loading="submitting" @click="submit">提交</el-button> </template> <script setup lang="ts"> const submitting = ref(false) const submit = async () => { if (submitting.value) return submitting.value = true try { await api.save() } finally { submitting.value = false } } </script>

按钮级防重比通用指令更可靠,因为它能跟接口真实状态联动。指令适合用来做全局的快速点击拦截,但不能替代业务层的并发控制。

4.4 标签页切换丝滑动画与圆环进度条

标签页或 Tab 切换动画,通常是给router-view或组件切换包一层<Transition>加 CSS 动画。Vue 3 中推荐用<Transition name="fade" mode="out-in">,配合少量 CSS:

.fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease, transform 0.2s ease; } .fade-enter-from { opacity: 0; transform: translateY(8px); } .fade-leave-to { opacity: 0; transform: translateY(-8px); }

mode="out-in"可以避免新老组件同时渲染导致的布局跳动。如果切换的是同一组件但路由参数不同,需要加key才能触发重渲染和动画。想要更丝滑的“滑动切换”,可以直接用第三方库如@vueuse/motionGSAP,但小项目没必要上重库。

圆环进度条这类视觉组件,用 SVG 实现是最顺手的。一个圆弧的stroke-dasharraystroke-dashoffset就能控制进度:

<template> <svg viewBox="0 0 120 120" width="120" height="120"> <circle cx="60" cy="60" r="50" stroke="#eee" stroke-width="10" fill="none" /> <circle cx="60" cy="60" r="50" stroke="#409EFF" stroke-width="10" fill="none" stroke-linecap="round" :stroke-dasharray="circumference" :stroke-dashoffset="circumference * (1 - percent / 100)" transform="rotate(-90 60 60)" /> </svg> </template>

circumference等于2 * Math.PI * 50。计算出周长后,用dashoffset控制露出来的弧长,加上transform: rotate(-90)把起点转到顶部,一个圆环就成了。之后再用transitionstroke-dashoffset做动画,就能看到平滑增长效果。

5. 调试提效与多端场景:把 Vue 3 用得更顺手

5.1 快速定位代码:从浏览器元素到源文件

拿到一个不熟悉的项目,老板说“首页这个按钮样式改一下”,你第一反应是不是全局搜文案?效率太低。正确的方式是:在浏览器里右键那个按钮,选择“检查”,打开 DevTools 的 Elements 面板,此时按钮元素高亮,在 Network 或底层会显示它对应的源文件和编译后的源码位置。但.vue文件编译后是一个个模块,直接点没意义。

Vue 官方 DevTools 插件带了一个功能:在 Elements 面板中选中一个组件对应的 DOM 元素,切换到 Vue 面板时,会高亮对应的组件,并在组件详情里显示它的文件名。点开文件名,配合 Vite 的 sourcemap,可以直接跳转到源文件。更快的办法是给组件根节点加>const response = await fetch('/api/chat', { method: 'POST', body: JSON.stringify(data) }) const reader = response.body?.getReader() const decoder = new TextDecoder() let content = '' while (true) { const { value, done } = await reader!.read() if (done) break content += decoder.decode(value, { stream: true }) text.value = content }

中间的text如果是ref(''),反复赋值会触发视图更新,本质上是一个高频渲染过程,注意别在这种场景里做多余的计算属性处理。如果直接用EventSource或 WebSocket 收消息,逻辑也类似。

另一些同学问“能不能直接把 Vue 打包后的 index.html 通过本地文件加载,在 iOS WebView 里打开”,这个要看你的路线。Vue 打包产物默认使用绝对路径/assets/xxx.js,直接双击打开或丢进 iOS WebView 的loadFileURL时,资源路径会找不到。所以你得在vite.config.ts里把base设为'./',这样打包后的资源引用变成相对路径。还要注意 Vue Router 的 history 模式在 file 协议下也会出问题,因为刷新路由时找不到对应的静态文件,所以在 WebView 环境下建议启用hash模式,或者自己拦截 scheme 请求。其实更稳的方案是在本地起一个轻量静态服务器,比如 Python 的http.server,再让 WebView 指向http://127.0.0.1:port,这种方案在PyWebview集成 Vue 时非常常见。

python -m http.server 8080 -d dist

然后在 PyWebview 里加载http://127.0.0.1:8080,接口走本地或远程服务,本质就是一个“包了壳的 Web 应用”。这种方式比纯 file 协议省心很多,在 Android/iOS 的 WebView 里也适用。

6. 前后端分离与部署:Vue 3 项目上线全流程

6.1 Spring Boot 前后端联调

现在的 Java 后端项目用 Spring Boot 很常见,Vue 前端要联调时,最省事的方式是配置 Vite 代理,把/api请求转发到本地的后端端口。在vite.config.ts里:

server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

这样前端在开发环境直接请求/api/login,Vite 会帮你转发到http://localhost:8080/api/login,绕开了浏览器的跨域限制。到生产环境,把后端接口地址写进环境变量(.env.production),然后通过 Nginx 的反向代理将/api转发到后端服务,这样前端打包产物和接口处于同域,不会再有跨域问题。

联调时最容易遇到的不是跨域,而是接口字段命名不一致、时间格式不统一、分页参数风格不同。建议前后端先约定一个 API 文档工具,或用 TypeScript 自动生成接口类型,能省掉大量“你是不是传错了参数”的无效沟通。

6.2 用 Xshell 部署到服务器

服务器部署 Vue 3 项目其实不复杂。先在本地执行:

pnpm build

执行后会生成dist文件夹。然后通过 Xshell 连接服务器,用rzscpdist上传到服务器某个目录,比如/opt/web/your-project。Nginx 配置核心就两块:一个root指向这个目录,一个try_files处理前端路由。

server { listen 80; server_name your-domain.com; root /opt/web/your-project; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; } }

try_files$uri先找真实文件,找不到就回退到index.html,这样 Vue Router 的 history 模式在深链接刷新时才不会 404。这里有一个非常常见的坑:proxy_pass http://127.0.0.1:8080/;末尾的斜杠会把/api前缀去掉,如果后端接口不带/api路径,这个写法就刚好;如果后端也带/api,就要写http://127.0.0.1:8080;不带斜杠。别问我是怎么知道的。

6.3 打包成 exe 和移动端兼容的现实解法

“Vue 项目怎么打包成 exe”这个问题在社区被问了很多次。真正可行的是用 Electron 或 Tauri 套一层壳。Electron 生态成熟,直接把dist作为静态资源加载进主窗口即可,适合团队熟悉 JS 的场景。Tauri 体积更小、性能更好,但需要一点点 Rust 环境。二者都能调系统 API,也能用本地服务做文件读写。

如果你只是想给内部工具一个“桌面应用”的样子,还可以用前面提到的 PyWebview,把 Vue 打包产物放进一个 Python 小项目里,用webview.create_window加载本地index.html,再用pyinstaller打包成 exe。这种方式很轻量,但不能频繁操作原生文件系统,适合简单业务。

移动端兼容方面,最典型的坑是“a 标签下载 PDF 在 iOS 上会变成预览”。iOS 对本地 PDF 的默认行为是在内置预览中打开,而不是下载。要改变这个行为,可以使用Content-Disposition: attachment的响应头,或者在业务层生成 PDF 后先下载到本地,再用分享面板让用户选择保存位置。前端能做的并不多,本质上是 iOS WebView 对 MIME 类型的默认处理逻辑。

7. Vue 3 面试高频点:用面试驱动知识梳理

7.1 响应式原理与 Diff、生命周期

面试官问 Vue 3,十有八九绕不开响应式原理。你要能说出几个点:Vue 2 的响应式基于Object.defineProperty,只能监听已有属性,数组通过改写 7 个方法来支持;Vue 3 改为Proxy,可以拦截整个对象的所有属性操作,包括新增、删除、下标赋值。同时Proxy的拦截能力让 Vue 3 能更精确地追踪依赖,默认惰性监听,性能更好。

Diff 算法方面,Vue 3 在虚拟 DOM 中引入了静态标记(PatchFlag)和动态属性缓存。模板编译器会对节点做静态提升,只对动态变化的类型做 diff,这也是为什么同样规模列表,Vue 3 因为手写v-for优化,更新性能比 Vue 2 好很多。你可以这样回答:Vue 3 不是“更笨地全量 diff”,而是“尽量只 diff 可能变化的部分”。

生命周期要能说出组合式 API 下的钩子映射关系:beforeCreatecreated在 Vue 3 中有新的对应,简单说被setup替代;beforeMount/mounted对应onBeforeMount/onMountedbeforeDestroy/destroyed改名为onBeforeUnmount/onUnmounted。再补充一句:setup本身发生在“创建组件实例”和“初始化 props”之后,它可以访问 props 但访问不了 this。

7.2 组件通信、路由和性能优化

组件通信的考点分几类:父子之间用propsemit;跨层级用provide/inject;复杂状态共享用 Pinia;临时跨组件通知可以用事件总线(Vue 3 不再提供实例上的$on/$off,需要引入mitt之类的外部库)。组合式 API 里还鼓励复用逻辑自定义 hooks,也就是把一段响应式逻辑封装成一个普通函数。

Vue Router 4 的考点集中在 history 模式和 hash 模式的差异、路由守卫的使用、动态路由什么时候注册。性能优化则常问:组件异步引入(defineAsyncComponent)、路由懒加载(动态import)、v-forkey、长列表虚拟滚动、keep-alive缓存组件状态、避免不必要的响应式深度等。能说出两三条并配合实际项目经验,基本就能过。

8. 常见问题速查表:踩坑实录

问题原因解决办法
ElMessage is not defined自动导入组件时没覆盖命令式 API在使用处手动import { ElMessage } from 'element-plus'
对象赋值后页面不更新解构响应式对象导致响应式丢失使用toRefs或直接保持ref写入
项目启动后 Network 不可用Vite 开发服务器没有监听局域网地址配置server.host: '0.0.0.0'
iOS 中 a 标签下载 PDF 变成预览iOS WebView 默认处理 PDF MIME使用Content-Disposition: attachment或引导用户用分享面板
打包后的文件通过 file 协议打不开资源路径使用了绝对路径设置base: './',路由改用 hash 模式
路由切换组件不刷新同一路由组件实例被复用监听route.params变化或给router-view加 key
内网环境安装依赖超时默认 npm 源无法访问配置公司内网 registry,或离线解压 node_modules
Vue 3.3 以上版本 ESLint 报错模板新特性需要 ESLint 插件升级升级eslint-plugin-vue到最新版

这张表其实就是我这些年做 Vue 3 项目时积攒下来的几块大坑。很多问题在静态代码层面看不出来,只有真正跑起来、不同环境下一试才会暴露。建议你把这条速查表保存下来,遇到类似报错时先对号入座,能省不少排查时间。

最后分享一个我自己在项目里的习惯:不管团队文档怎么吹,我始终把 Vue 3 + TypeScript + Pinia 当成默认组合。这套组合初期上手确实比 Vue 2 慢一点,但三个月后的维护成本真的会明显下降。如果你正在从 Vue 2 迁移,不要想着一次性重写整个系统,挑一个最核心的业务模块试水,跑顺了再逐步铺开。我经历过一次 20 个模块的整体迁移,过程非常痛苦,后来改成按页面渐进替换,配合组合式 API 封装复用逻辑,效率反而高得多。Vue 3 的技术栈还在不断迭代,但只要把响应式、组件通信、路由、工程化这几块地基打扎实,后面遇到任何新生态都不慌。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 20:10:42

Java基本类型与包装类型:从自动装箱到NPE实战全解析

Java面试里有一道题&#xff0c;明明背得滚瓜烂熟&#xff0c;但每次被问都能感觉到面试官在等你说出某个隐藏的坑。这道题就是&#xff1a;包装类型和基本类型的区别是什么&#xff1f;包装类型与基本类型&#xff0c;一个是对象&#xff0c;一个是普通值&#xff0c;这两个概…

作者头像 李华
网站建设 2026/9/9 20:09:24

Function Calling 本质:LLM 工具调用的运行时契约解析

Function Calling 这个词&#xff0c;最近半年在大模型应用开发圈里几乎天天刷屏——不是在调试 tool call&#xff0c;就是在重试 codex runtime 报错的路上。我从去年底开始做 Agent 类项目&#xff0c;从最原始的手写 JSON Schema 工具描述&#xff0c;到接入 LangChain 的 …

作者头像 李华
网站建设 2026/9/9 20:07:06

TikTok商城卖家必看:跌落测试实战指南

1. 跌落测试&#xff1a;TikTok商城卖家必须补上的第一课 做TikTok商城&#xff0c;很多卖家把精力都花在了选品、拍摄和投流上&#xff0c;觉得只要产品好、视频爆&#xff0c;就能出单。但有一个环节&#xff0c;往往被忽视&#xff0c;却直接决定了你能否留住客户、赚到利润…

作者头像 李华
网站建设 2026/9/9 20:05:40

动态规划入门:从斐波那契到背包问题的状态转移全解析

动态规划这四个字&#xff0c;大概是算法学习路上劝退率最高的名词之一。我一开始接触动态规划时&#xff0c;完全被"状态""转移方程""最优子结构"这些术语砸晕&#xff0c;一度怀疑是自己数学基础太差。后来刷了足够多题目、反复推演过几个经典…

作者头像 李华