云计算百科
云计算领域专业知识百科平台

Vue3完全指南从原理到实战

Vue3 完全指南:从原理到实战

文档版本:v1.0 更新时间:2026-09-21 适用范围:前端工程师、全栈工程师、技术负责人,以及所有需要从 Vue2 迁移或从零学习 Vue3 的人 说明:版本与特性基于 2026 年 9 月 Vue 官方资料;Vue 3.5 “Tengen Toppa Gurren Lagann” 为当前最新小版本


目录

  • 概述:Vue3 是什么、为什么选它
  • Vue2 vs Vue3 核心区别总览
  • 响应式原理:Proxy vs Object.defineProperty
  • Composition API 详解
  • <script setup> 语法糖
  • 组件通信:Props/Emits/Slots
  • Pinia 状态管理
  • Vite 构建工具
  • Vue Router 4
  • Nuxt 3 SSR/元框架
  • 新特性:Teleport/Suspense/Fragment
  • Vue3 性能优化
  • 迁移:从 Vue2 到 Vue3
  • 总结与未来方向

  • 1. 概述:Vue3 是什么、为什么选它

    1.1 Vue3 的定位

    Vue 3 是 Vue.js 的第三个大版本,于 2020 年 9 月正式发布——它不是"Vue2 的升级版",而是从头重写的新一代前端框架。2026 年的今天,Vue 3.5 “Tengen Toppa Gurren Lagann” 已是当前最新小版本,Vue2 已 EOL(2023 年 12 月停止维护)。

    Vue3 的核心定位是:

    • 渐进式框架:从小项目到大型应用都能覆盖;
    • 性能优先:从头重写的响应式系统与虚拟 DOM;
    • Composition API:解决大型应用的代码组织问题;
    • TypeScript 原生支持:从设计之初就拥抱类型系统。

    1.2 为什么选 Vue3

    维度Vue3 的优势
    性能 Proxy 响应式、编译时优化、更小的运行时
    代码组织 Composition API 解决大型应用的逻辑复用
    TypeScript 原生 TS 支持,类型推导更自然
    生态 Vite、Pinia、Nuxt3 组成新一代生态
    维护 Vue2 已 EOL,Vue3 是唯一现役版本

    2026 年的现状:Vue2 已停止维护两年多,Vue3 是事实上的标准——新项目直接选 Vue3,老项目按节奏迁移。

    1.3 Vue3 的设计目标

    Vue3 的三个核心设计目标:

  • 更好的性能:Proxy 替代 Object.defineProperty,编译时优化;
  • 更好的代码组织:Composition API 替代 Options API;
  • 更好的 TypeScript 支持:从设计之初就考虑类型推导。
  • 1.4 2026 年 Vue3 生态现状

    2026 年 9 月的今天,Vue3 生态已经完全成熟:

    生态组件当前版本状态
    Vue 核心 3.5 “Tengen Toppa Gurren Lagann” 当前最新小版本
    Vite 6.x 构建工具标准
    Pinia 2.x 官方推荐状态管理
    Vue Router 4.x 官方路由
    Nuxt 3.x SSR/SSG 元框架
    TypeScript 5.x 原生支持

    关键事实:Vue2 已于 2023 年 12 月 31 日正式 EOL——不再有安全补丁、不再有新功能、社区不再维护。2026 年的今天,Vue2 是"已淘汰"的技术,Vue3 是唯一现役选择。

    1.5 Vue3 的适用场景

    场景适合度说明
    中大型单页应用(SPA) ⭐⭐⭐⭐⭐ Composition API + Pinia 完美适配
    内容型网站(博客/文档/营销页) ⭐⭐⭐⭐⭐ Nuxt3 SSG 静态生成
    电商/门户 ⭐⭐⭐⭐⭐ Nuxt3 SSR 首屏快
    组件库开发 ⭐⭐⭐⭐⭐ TypeScript 原生支持
    移动端 H5 ⭐⭐⭐⭐ Vite 构建快,体验好
    小程序 ⭐⭐⭐ uni-app/Taro 支持 Vue3

    不适合的场景:

    • 极端高性能游戏/实时可视化:选 Canvas/WebGL 原生方案;
    • 需要与 React 生态深度集成:如果团队主力是 React,不要硬切 Vue。

    1.6 Vue3 的设计哲学

    Vue3 的设计哲学可以概括为三句话:

  • 渐进式增强:你可以只用 Vue3 的核心,也可以用上完整生态;
  • 编译时优先:尽可能在编译时做优化,减少运行时开销;
  • 开发者体验优先:减少样板代码,提升开发效率。
  • 与 React 的对比:

    维度Vue3React
    学习曲线 平缓(模板语法友好) 陡峭(JSX + Hooks)
    状态管理 Pinia(官方推荐) Redux/Zustand/Jotai(多种选择)
    构建工具 Vite(官方推荐) Vite/Webpack/Next.js
    SSR 元框架 Nuxt 3 Next.js
    类型支持 原生 TS 支持 TSX 原生

    Vue3 的核心优势:学习曲线平缓、官方生态统一、开发效率高——这是 Vue3 在国内市场占有率持续领先的根本原因。

    1.7 Vue3 与其他框架对比

    维度Vue3ReactSvelte
    学习曲线 平缓 陡峭 平缓
    包体积 ~30KB ~42KB 更小(编译时)
    性能 更好(编译时优化)
    状态管理 Pinia Redux/Zustand 内置 store
    SSR Nuxt 3 Next.js SvelteKit
    TypeScript 原生 原生 原生
    国内生态 最成熟 成熟 较新
    就业市场

    选型建议:

    • 国内企业:Vue3 生态最成熟,就业市场最大;
    • 国际化团队:React 生态更成熟,国际社区更大;
    • 小团队/快速开发:Svelte 更轻量,但生态较小;
    • 长期学习:Vue3 和 React 都值得学,思路相通。

    1.8 Vue3 的学习资源

    资源类型说明
    Vue 官方文档 文档 https://cn.vuejs.org — 最权威的文档
    Vue 官方博客 博客 https://blog.vuejs.org — 最新版本动态
    Vue Mastery 视频课程 付费,质量高,实战导向
    Vue School 视频课程 付费,实战项目多
    VueUse 工具库 https://vueuse.org — 实用 composable 集合
    awesome-vue 资源大全 GitHub 上的 Vue 生态资源列表

    学习建议:

    • 入门:先看官方文档,掌握基础语法;
    • 进阶:做实战项目,深入 Composition API;
    • 深入:读源码,理解响应式原理;
    • 生态:学习 Vite、Pinia、Nuxt 3 等生态工具。

    Vue3 适合谁学:

    人群适合度理由
    前端新手 ⭐⭐⭐⭐⭐ 模板语法友好,学习曲线平缓
    Vue2 老司机 ⭐⭐⭐⭐⭐ 升级路径清晰,生态统一
    React 开发者 ⭐⭐⭐⭐ 思路相通,迁移成本低
    后端转前端 ⭐⭐⭐⭐⭐ 模板语法更接近后端思维
    全栈开发者 ⭐⭐⭐⭐⭐ 配合 Nuxt 3 做全栈开发

    为什么 2026 年还要学 Vue3:

  • 国内市场占有率第一:Vue 在国内前端市场占有率持续领先;
  • 生态最成熟:Vite、Pinia、Nuxt 3 组成完整生态;
  • 就业市场大:国内企业大量使用 Vue;
  • 学习曲线平缓:比 React 更容易上手;
  • 长期维护:Vue3 持续迭代,Vue2 已 EOL。

  • 2. Vue2 vs Vue3 核心区别总览

    2.1 一张表看懂核心差异

    维度Vue2Vue3
    响应式原理 Object.defineProperty Proxy + Reflect
    API 风格 Options API(data/methods/computed) Composition API(ref/reactive/computed)
    构建工具 webpack Vite
    状态管理 Vuex Pinia
    路由 Vue Router 3 Vue Router 4
    TypeScript 支持有限 原生支持
    多根节点 不支持(单根) 支持 Fragment(多根)
    Teleport 内置
    Suspense 内置
    生命周期 beforeDestroy/destroyed onBeforeUnmount/onUnmounted

    2.2 迁移的本质

    从 Vue2 到 Vue3 的迁移,本质上是三件事:

  • 响应式系统换代:Proxy 替代 defineProperty;
  • 代码组织换代:Composition API 替代 Options API;
  • 生态换代:Vite + Pinia + Vue Router 4。
  • 这不是"改几个语法",而是"整个开发范式的升级"——但升级方向是对的,且路径清晰。

    2.3 迁移的收益分析

    从 Vue2 迁移到 Vue3,你能得到什么:

    收益说明
    性能提升 Proxy 响应式 + 编译时优化,渲染性能提升 1~2 倍
    代码组织 Composition API 解决大型应用的逻辑复用
    TypeScript 原生 TS 支持,类型推导更自然
    开发体验 Vite 极快启动 + 热更新
    维护性 Vue2 已 EOL,Vue3 持续迭代
    生态 新组件库、新工具都优先支持 Vue3

    迁移成本:

    项目规模主要工作量预估周期
    小型项目(< 10 个页面) 语法迁移 + Pinia 1~3 天
    中型项目(10~50 个页面) Composition API 迁移 + Vuex→Pinia 1~2 周
    大型项目(> 50 个页面) 全量迁移 + 组件库适配 2~4 周

    核心结论:Vue2 已 EOL,迁移不是"要不要",而是"什么时候"——越早迁移,技术债越少。

    2.4 Vue2 到 Vue3 的渐进迁移策略

    渐进迁移三步走:

    #mermaid-svg-ynznx95KtWlpV66U{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ynznx95KtWlpV66U .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ynznx95KtWlpV66U .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ynznx95KtWlpV66U .error-icon{fill:#552222;}#mermaid-svg-ynznx95KtWlpV66U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ynznx95KtWlpV66U .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ynznx95KtWlpV66U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ynznx95KtWlpV66U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ynznx95KtWlpV66U .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ynznx95KtWlpV66U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ynznx95KtWlpV66U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ynznx95KtWlpV66U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ynznx95KtWlpV66U .marker.cross{stroke:#333333;}#mermaid-svg-ynznx95KtWlpV66U svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ynznx95KtWlpV66U p{margin:0;}#mermaid-svg-ynznx95KtWlpV66U .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ynznx95KtWlpV66U .cluster-label text{fill:#333;}#mermaid-svg-ynznx95KtWlpV66U .cluster-label span{color:#333;}#mermaid-svg-ynznx95KtWlpV66U .cluster-label span p{background-color:transparent;}#mermaid-svg-ynznx95KtWlpV66U .label text,#mermaid-svg-ynznx95KtWlpV66U span{fill:#333;color:#333;}#mermaid-svg-ynznx95KtWlpV66U .node rect,#mermaid-svg-ynznx95KtWlpV66U .node circle,#mermaid-svg-ynznx95KtWlpV66U .node ellipse,#mermaid-svg-ynznx95KtWlpV66U .node polygon,#mermaid-svg-ynznx95KtWlpV66U .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ynznx95KtWlpV66U .rough-node .label text,#mermaid-svg-ynznx95KtWlpV66U .node .label text,#mermaid-svg-ynznx95KtWlpV66U .image-shape .label,#mermaid-svg-ynznx95KtWlpV66U .icon-shape .label{text-anchor:middle;}#mermaid-svg-ynznx95KtWlpV66U .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ynznx95KtWlpV66U .rough-node .label,#mermaid-svg-ynznx95KtWlpV66U .node .label,#mermaid-svg-ynznx95KtWlpV66U .image-shape .label,#mermaid-svg-ynznx95KtWlpV66U .icon-shape .label{text-align:center;}#mermaid-svg-ynznx95KtWlpV66U .node.clickable{cursor:pointer;}#mermaid-svg-ynznx95KtWlpV66U .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ynznx95KtWlpV66U .arrowheadPath{fill:#333333;}#mermaid-svg-ynznx95KtWlpV66U .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ynznx95KtWlpV66U .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ynznx95KtWlpV66U .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ynznx95KtWlpV66U .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ynznx95KtWlpV66U .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ynznx95KtWlpV66U .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ynznx95KtWlpV66U .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ynznx95KtWlpV66U .cluster text{fill:#333;}#mermaid-svg-ynznx95KtWlpV66U .cluster span{color:#333;}#mermaid-svg-ynznx95KtWlpV66U div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ynznx95KtWlpV66U .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ynznx95KtWlpV66U rect.text{fill:none;stroke-width:0;}#mermaid-svg-ynznx95KtWlpV66U .icon-shape,#mermaid-svg-ynznx95KtWlpV66U .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ynznx95KtWlpV66U .icon-shape p,#mermaid-svg-ynznx95KtWlpV66U .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ynznx95KtWlpV66U .icon-shape .label rect,#mermaid-svg-ynznx95KtWlpV66U .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ynznx95KtWlpV66U .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ynznx95KtWlpV66U .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ynznx95KtWlpV66U :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    第一步:Vue2.7兼容 Vue3 语法

    第二步:Vite + Vue3并行运行

    第三步:全量迁移移除 Vue2

    第一步:Vue2.7 兼容 Vue3 语法

    Vue 2.7 是 Vue2 的最后一个版本,它 backport 了 Vue3 的 Composition API:

    // Vue 2.7 中也可以用 Composition API
    import { ref, computed } from 'vue';

    export default {
    setup() {
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    return { count, doubleCount };
    }
    };

    第二步:Vite + Vue3 并行运行

    用 Vite 搭建 Vue3 项目,和 Vue2 项目并行运行,逐步把页面从 Vue2 迁移到 Vue3。

    第三步:全量迁移

    所有页面都迁移到 Vue3 后,移除 Vue2 项目,完成迁移。

    2.5 Vue3 的生态对比

    生态Vue2 时代Vue3 时代
    构建工具 webpack Vite
    状态管理 Vuex Pinia
    路由 Vue Router 3 Vue Router 4
    SSR Nuxt 2 Nuxt 3
    UI 组件库 Element UI Element Plus
    工具库 零散 VueUse

    生态升级的本质:从"零散生态"到"统一生态"——Vue3 的生态更统一、更现代、更高效。

    2.6 Vue3 的版本历史

    版本发布时间关键特性
    Vue 3.0 2020.09 首个正式版本,Composition API
    Vue 3.1 2021.06 兼容性改进
    Vue 3.2 2021.08 性能优化,<script setup> 稳定
    Vue 3.3 2023.05 TS 改进,泛型组件
    Vue 3.4 2023.12 defineModel,SSR 改进
    Vue 3.5 2026.07 Reactive Props Destructure,onEffectCleanup

    版本选择建议:

    • 生产环境:用最新的稳定版本(Vue 3.5+);
    • 新项目:直接用最新版本;
    • 老项目:逐步升级,不要跨大版本跳跃。

    版本升级注意事项:

    版本升级注意
    3.0 → 3.1 兼容性改进,直接升级
    3.1 → 3.2 <script setup> 稳定,推荐升级
    3.2 → 3.3 TS 改进,升级后检查类型
    3.3 → 3.4 defineModel 新特性,可选升级
    3.4 → 3.5 Reactive Props Destructure,推荐升级

    升级建议:小版本升级通常是安全的,升级前务必跑一遍完整测试;大版本升级(如 Vue2→Vue3)需要按迁移路线图执行,不要一次性大爆炸。


    3. 响应式原理:Proxy vs Object.defineProperty

    3.1 Vue2 的痛点

    Vue2 的响应式基于 Object.defineProperty——它有两个无法根治的问题:

    // Vue2 的响应式实现(简化)
    function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
    get() {
    // 依赖收集
    return val;
    },
    set(newVal) {
    // 触发更新
    val = newVal;
    }
    });
    }

    // 痛点 1:无法监听新增属性
    Vue.set(vm.someObject, 'newKey', 'value'); // 必须用 Vue.set

    // 痛点 2:无法监听数组索引赋值
    vm.someArray[0] = 'newValue'; // 不触发更新

    这两个痛点在大型应用中会变成"玄学 bug"——为什么数据变了视图没更新?答案通常是"你新增了属性或改了数组索引"。

    3.2 Vue3 的解决方案:Proxy

    Vue3 用 JavaScript 原生的 Proxy 替代 Object.defineProperty——它从根本上解决了这两个问题:

    #mermaid-svg-NQpEe0z4aTreOGrw{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NQpEe0z4aTreOGrw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NQpEe0z4aTreOGrw .error-icon{fill:#552222;}#mermaid-svg-NQpEe0z4aTreOGrw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NQpEe0z4aTreOGrw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NQpEe0z4aTreOGrw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NQpEe0z4aTreOGrw .marker.cross{stroke:#333333;}#mermaid-svg-NQpEe0z4aTreOGrw svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NQpEe0z4aTreOGrw p{margin:0;}#mermaid-svg-NQpEe0z4aTreOGrw .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-NQpEe0z4aTreOGrw .cluster-label text{fill:#333;}#mermaid-svg-NQpEe0z4aTreOGrw .cluster-label span{color:#333;}#mermaid-svg-NQpEe0z4aTreOGrw .cluster-label span p{background-color:transparent;}#mermaid-svg-NQpEe0z4aTreOGrw .label text,#mermaid-svg-NQpEe0z4aTreOGrw span{fill:#333;color:#333;}#mermaid-svg-NQpEe0z4aTreOGrw .node rect,#mermaid-svg-NQpEe0z4aTreOGrw .node circle,#mermaid-svg-NQpEe0z4aTreOGrw .node ellipse,#mermaid-svg-NQpEe0z4aTreOGrw .node polygon,#mermaid-svg-NQpEe0z4aTreOGrw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NQpEe0z4aTreOGrw .rough-node .label text,#mermaid-svg-NQpEe0z4aTreOGrw .node .label text,#mermaid-svg-NQpEe0z4aTreOGrw .image-shape .label,#mermaid-svg-NQpEe0z4aTreOGrw .icon-shape .label{text-anchor:middle;}#mermaid-svg-NQpEe0z4aTreOGrw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NQpEe0z4aTreOGrw .rough-node .label,#mermaid-svg-NQpEe0z4aTreOGrw .node .label,#mermaid-svg-NQpEe0z4aTreOGrw .image-shape .label,#mermaid-svg-NQpEe0z4aTreOGrw .icon-shape .label{text-align:center;}#mermaid-svg-NQpEe0z4aTreOGrw .node.clickable{cursor:pointer;}#mermaid-svg-NQpEe0z4aTreOGrw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NQpEe0z4aTreOGrw .arrowheadPath{fill:#333333;}#mermaid-svg-NQpEe0z4aTreOGrw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NQpEe0z4aTreOGrw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NQpEe0z4aTreOGrw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NQpEe0z4aTreOGrw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NQpEe0z4aTreOGrw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NQpEe0z4aTreOGrw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NQpEe0z4aTreOGrw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NQpEe0z4aTreOGrw .cluster text{fill:#333;}#mermaid-svg-NQpEe0z4aTreOGrw .cluster span{color:#333;}#mermaid-svg-NQpEe0z4aTreOGrw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NQpEe0z4aTreOGrw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NQpEe0z4aTreOGrw rect.text{fill:none;stroke-width:0;}#mermaid-svg-NQpEe0z4aTreOGrw .icon-shape,#mermaid-svg-NQpEe0z4aTreOGrw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NQpEe0z4aTreOGrw .icon-shape p,#mermaid-svg-NQpEe0z4aTreOGrw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NQpEe0z4aTreOGrw .icon-shape .label rect,#mermaid-svg-NQpEe0z4aTreOGrw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NQpEe0z4aTreOGrw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NQpEe0z4aTreOGrw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NQpEe0z4aTreOGrw :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Vue3: Proxy

    对象

    整个对象 Proxy 代理

    新增属性 ✅ 响应

    数组索引 ✅ 响应

    Vue2: Object.defineProperty

    对象

    逐属性 defineProperty

    新增属性 ❌ 不响应

    数组索引 ❌ 不响应

    Proxy 的核心优势:

    维度Vue2(defineProperty)Vue3(Proxy)
    劫持粒度 单个属性 整个对象
    新增属性 ❌(需 Vue.set) ✅ 原生支持
    删除属性 ❌(需 Vue.delete) ✅ 原生支持
    数组索引 ✅ 原生支持
    Map/Set ✅ 原生支持
    性能 初始化时递归遍历 懒监听,按需代理

    3.3 响应式的两个核心 API

    Vue3 提供了 ref 和 reactive 两个核心 API:

    // ref:基本类型(或需要整体替换的对象)
    import { ref } from 'vue';

    const count = ref(0);
    console.log(count.value); // 0(模板中自动解包,脚本中需 .value)

    // reactive:对象类型
    import { reactive } from 'vue';

    const state = reactive({
    name: 'Vue3',
    version: '3.5'
    });
    console.log(state.name); // 'Vue3'(无需 .value)

    选型建议:

    • 基本类型用 ref(number/string/boolean);
    • 对象用 reactive(object/array);
    • 不确定用 ref——更通用,且模板中自动解包。

    3.4 响应式原理深度解析

    Vue3 的响应式系统基于三个核心概念:Proxy、依赖收集、触发更新。

    // Vue3 响应式原理(简化)
    function reactive(target) {
    return new Proxy(target, {
    get(target, key) {
    // 依赖收集:记录当前 effect 依赖了这个 key
    track(target, key);
    return Reflect.get(target, key);
    },
    set(target, key, value) {
    const result = Reflect.set(target, key, value);
    // 触发更新:执行所有依赖这个 key 的 effect
    trigger(target, key);
    return result;
    }
    });
    }

    关键流程:

  • 组件渲染时:读取响应式数据 → get 被触发 → 依赖收集(记录这个 effect 依赖了哪些 key);
  • 数据变化时:写入响应式数据 → set 被触发 → 触发更新(执行所有依赖这个 key 的 effect);
  • 重新渲染:effect 重新执行,读取新数据 → 视图更新。
  • 与 Vue2 的本质区别:

    • Vue2:初始化时递归遍历所有属性,给每个属性 defineProperty——初始化慢;
    • Vue3:懒代理——只有被访问的属性才会被 Proxy 代理——初始化快。

    3.5 常见响应式陷阱

    陷阱原因解决
    解构 reactive 丢失响应式 reactive 解构后变成普通变量 用 toRefs 保持响应式
    直接替换整个 reactive 对象 替换后原对象不再响应 用 ref 或 Object.assign
    数组索引赋值不更新 Vue2 的问题,Vue3 已解决 Vue3 原生支持
    新增属性不更新 Vue2 的问题,Vue3 已解决 Vue3 原生支持
    深层对象响应式性能差 深层 Proxy 递归代理 用 shallowReactive 浅层响应式

    最佳实践:

    // 正确:用 toRefs 保持解构后的响应式
    import { reactive, toRefs } from 'vue';

    const state = reactive({ count: 0, name: 'Vue3' });
    const { count, name } = toRefs(state);
    // count 和 name 仍是响应式的

    // 正确:不确定用 ref
    const count = ref(0);
    count.value++; // 模板中自动解包

    3.6 响应式系统的性能调优

    Vue3 的响应式系统虽然比 Vue2 好很多,但在极端场景下仍有性能问题:

    场景问题解决方案
    大型对象深层嵌套 深层 Proxy 递归代理,初始化慢 shallowReactive 浅层响应式
    频繁更新的大数组 每次更新都触发依赖收集 shallowRef + 手动触发
    第三方库对象 不需要响应式但被代理了 markRaw 标记为不响应式
    大量计算属性 每个 computed 都有依赖追踪 减少 computed 嵌套,用普通函数

    性能调优最佳实践:

    import { shallowReactive, markRaw } from 'vue';

    // 1. 大型对象用 shallowReactive
    const bigList = shallowReactive({ items: [] });
    // 只有 bigList.items 的引用变化才触发更新,深层属性不追踪

    // 2. 第三方对象用 markRaw
    import Map from 'mapbox-gl';
    const map = markRaw(new Map(container));
    // map 不会被 Proxy 代理,性能更好

    // 3. 频繁更新的数据用 shallowRef
    const data = shallowRef(null);
    // 手动触发更新
    data.value = newData; // 只有引用变化才触发

    什么时候需要调优:

    • 渲染性能 profiling 显示响应式系统是瓶颈;
    • 有大型数据对象(> 1000 个属性);
    • 有频繁更新的动画/实时数据。

    3.7 响应式系统的内部实现

    Vue3 的响应式系统基于三个核心类:

    类说明
    ReactiveEffect 响应式副作用(组件渲染、watch、computed)
    Dep 依赖收集器(记录哪些 effect 依赖了这个 key)
    RefImpl ref 的实现类

    依赖收集流程:

  • 组件渲染时,创建一个 ReactiveEffect;
  • 读取响应式数据时,get 触发 track;
  • track 把当前 effect 记录到 Dep 里;
  • 数据变化时,set 触发 trigger;
  • trigger 执行所有依赖这个 key 的 effect;
  • effect 重新执行,触发组件重新渲染。
  • 与 Vue2 的本质区别:

    • Vue2:每个属性一个 dep.id,初始化时递归遍历所有属性;
    • Vue3:每个对象的每个 key 一个 Dep,懒收集——只有被访问的 key 才会被追踪。

    3.8 响应式系统的常见问题

    #问题原因解决方案
    1 数据变了视图没更新 解构 reactive 丢失响应式 用 toRefs
    2 新增属性不响应 Vue2 的问题,Vue3 已解决 Vue3 原生支持
    3 深层对象性能差 深层 Proxy 递归代理 用 shallowReactive
    4 数组索引不响应 Vue2 的问题,Vue3 已解决 Vue3 原生支持
    5 ref 和 reactive 混用 类型不清晰 统一用 ref 或 reactive
    6 监听 props 不响应 props 是只读的 用 watch(() => props.xxx)

    调试技巧:

    import { isRef, isReactive, toRaw } from 'vue';

    // 检查是不是 ref
    console.log(isRef(count)); // true

    // 检查是不是 reactive
    console.log(isReactive(state)); // true

    // 获取原始对象(跳过 Proxy)
    const rawState = toRaw(state);

    3.9 Vue 3.5 的响应式改进

    Vue 3.5 对响应式系统做了重大改进:

    改进说明
    结构重设计 用版本计数和双向链表重构响应式系统
    数组追踪优化 优化数组的依赖收集和触发
    onEffectCleanup 新 API,用于副作用清理
    watch 暂停/恢复 新功能,可以暂停和恢复 watch
    SSR 改进 服务端渲染的响应式性能优化

    onEffectCleanup 示例:

    import { watchEffect, onEffectCleanup } from 'vue';

    watchEffect((onCleanup) => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
    .then(res => res.json())
    .then(data => { /* … */ });

    // 清理:下次重新执行前调用
    onCleanup(() => {
    controller.abort(); // 取消上一次请求
    });
    });


    4. Composition API 详解

    4.1 什么是 Composition API

    Composition API 是 Vue3 的核心新特性——它解决了 Options API 在大型应用中的代码组织问题:

    // ========== Vue2 的 Options API ==========
    // 同一个功能的代码散落在 data、methods、computed、watch 里
    export default {
    data() {
    return { count: 0, user: null, loading: false };
    },
    methods: {
    increment() { this.count++; },
    async fetchUser() { /* … */ }
    },
    computed: {
    doubleCount() { return this.count * 2; }
    },
    watch: {
    count(newVal) { /* … */ }
    }
    };
    // 问题:同一功能的代码散落在四个地方,大型组件难以维护

    // ========== Vue3 的 Composition API ==========
    // 同一功能的代码集中在一起,且可以提取为独立函数
    import { ref, computed, watch } from 'vue';

    function useCounter() {
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    function increment() { count.value++; }
    watch(count, (newVal) => { /* … */ });
    return { count, doubleCount, increment };
    }

    function useUser() {
    const user = ref(null);
    const loading = ref(false);
    async function fetchUser() { /* … */ }
    return { user, loading, fetchUser };
    }

    export default {
    setup() {
    // 同一功能的代码集中使用
    const { count, doubleCount, increment } = useCounter();
    const { user, loading, fetchUser } = useUser();
    return { count, doubleCount, increment, user, loading, fetchUser };
    }
    };

    4.2 Composition API 的核心优势

    优势说明
    代码组织 同一功能的代码集中在一起,不再散落在四个选项里
    逻辑复用 提取为独立的 composable 函数,跨组件复用
    TypeScript 天然支持类型推导,比 Options API 更好
    Tree-shaking 未使用的 API 可以被打包工具移除

    4.3 常用的 Composition API

    API用途
    ref 基本类型响应式
    reactive 对象类型响应式
    computed 计算属性
    watch 侦听器
    watchEffect 立即执行的侦听器
    onMounted/onUnmounted 生命周期钩子
    provide/inject 依赖注入
    defineProps/defineEmits 组件通信(<script setup> 中)

    4.4 Composition API 实战示例:倒计时

    // composables/useCountdown.js
    import { ref, onMounted, onUnmounted } from 'vue';

    export function useCountdown(initialSeconds = 60) {
    const seconds = ref(initialSeconds);
    const isRunning = ref(false);
    let timer = null;

    function start() {
    isRunning.value = true;
    timer = setInterval(() => {
    if (seconds.value > 0) {
    seconds.value;
    } else {
    stop();
    }
    }, 1000);
    }

    function stop() {
    isRunning.value = false;
    if (timer) {
    clearInterval(timer);
    timer = null;
    }
    }

    function reset(newSeconds = initialSeconds) {
    stop();
    seconds.value = newSeconds;
    }

    onMounted(() => {
    // 组件挂载时自动开始
    start();
    });

    onUnmounted(() => {
    // 组件卸载时清理定时器
    stop();
    });

    return {
    seconds,
    isRunning,
    start,
    stop,
    reset
    };
    }

    在组件中使用:

    <script setup>
    import { useCountdown } from '@/composables/useCountdown';

    const { seconds, isRunning, reset } = useCountdown(30);
    </script>

    <template>
    <div>
    <p>{{ seconds }} 秒</p>
    <button @click="reset(30)" :disabled="isRunning">重置</button>
    </div>
    </template>

    4.5 composable 提取实践

    什么时候提取 composable:

    场景是否提取
    同一组件内复用 不需要,函数即可
    跨组件复用 ✅ 提取为 composable
    有独立状态逻辑 ✅ 提取为 composable
    与 UI 强耦合 不提取,保持组件内

    composable 的命名规范:

    • 以 use 开头(useUser、useCart、useCountdown);
    • 函数式,接收参数,返回状态和方法;
    • 不返回 UI,只返回逻辑。

    4.6 Composition API vs Options API 选型

    场景推荐说明
    新项目 Composition API + <script setup> 官方推荐,长期方向
    小型组件 Options API 也可以 简单组件差异不大
    大型组件 Composition API 代码组织优势明显
    需要跨组件复用 Composition API composable 函数复用
    团队熟悉 Vue2 渐进迁移 先 Options API 跑通,再逐步迁移

    核心结论:Composition API 是 Vue3 的长期方向——新项目直接用,老项目按节奏迁移。

    4.7 Composition API 的性能考虑

    考虑说明
    setup 执行时机 setup 在组件创建时执行一次,不要在里面做重计算
    computed 缓存 computed 有缓存,依赖不变不重新计算
    watch 清理 watch 要在 onUnmounted 里清理,避免内存泄漏
    effect 清理 Vue 3.5 的 onEffectCleanup 用于副作用清理

    性能最佳实践:

    // 1. 重计算用 computed
    const heavyComputed = computed(() => {
    // 重计算只执行一次,缓存结果
    return expensiveCalculation(props.data);
    });

    // 2. watch 要清理
    watch(source, (newVal) => {
    const timer = setInterval(() => {
    // …
    }, 1000);

    onUnmounted(() => {
    clearInterval(timer); // 清理定时器
    });
    });

    // 3. 不要在 setup 里做异步操作
    // 用 onMounted 或顶级 await

    4.8 Composition API 的最佳实践

    #实践说明
    1 按功能组织代码 同一功能的代码集中在一起
    2 提取 composable 跨组件复用的逻辑提取为 composable
    3 使用 TypeScript 类型推导更自然
    4 避免过度抽象 不要为了提取而提取
    5 命名规范 composable 以 use 开头
    6 单一职责 每个 composable 只做一件事

    最佳实践示例:

    // 好的 composable:单一职责
    function useUser() {
    const user = ref(null);
    const loading = ref(false);

    async function fetchUser(id) {
    // …
    }

    return { user, loading, fetchUser };
    }

    // 不好的 composable:做了太多事
    function useEverything() {
    // 用户、购物车、设置、主题… 都放一起
    // 违反单一职责
    }

    4.9 Composition API 与 React Hooks 对比

    维度Vue3 Composition APIReact Hooks
    响应式 自动追踪(Proxy) 手动追踪(useEffect 依赖数组)
    依赖数组 不需要 需要手动写
    闭包问题 没有(Proxy 自动追踪) 有(旧值闭包)
    生命周期 onMounted/onUnmounted useEffect/useLayoutEffect
    代码复用 composable 函数 自定义 Hook
    学习曲线 平缓 陡峭

    核心区别:Vue3 的响应式是自动的,React Hooks 的依赖是手动的——这是两者最大的区别,也是 Vue3 开发体验更好的根本原因。


    5. <script setup> 语法糖

    5.1 什么是 <script setup>

    <script setup> 是 Vue3 的编译时语法糖——它让 Composition API 更简洁:

    <!– 传统的 Composition API –>
    <script>
    import { ref, computed } from 'vue';

    export default {
    setup() {
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    function increment() { count.value++; }
    return { count, doubleCount, increment };
    }
    };
    </script>

    <!– <script setup> 语法糖 –>
    <script setup>
    import { ref, computed } from 'vue';

    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    function increment() { count.value++; }
    // 无需 return,顶层变量自动暴露给模板
    </script>

    5.2 <script setup> 的优势

    优势说明
    更少样板 无需 export default 和 setup()
    自动暴露 顶层变量自动暴露给模板,无需 return
    更好 TS 纯 TypeScript 声明 props 和 emits
    更好 IDE 类型推导更快,更少 LSP 工作

    5.3 Vue 3.5 的新特性:Reactive Props Destructure

    Vue 3.5 新增了响应式 props 解构——解构 props 后仍保持响应式:

    <script setup>
    // Vue 3.5+:解构后仍保持响应式
    const { name, age } = defineProps({
    name: String,
    age: Number
    });

    // 不需要 .value,且 name 和 age 仍是响应式的
    console.log(name);
    </script>

    这是 Vue 3.5 的重要改进——以前解构 props 会丢失响应式,现在编译器自动处理。

    5.4 <script setup> 进阶用法

    1. 顶级 await 支持:

    <script setup>
    // 直接 await,编译器自动处理 Suspense
    const post = await fetch(`/api/post/1`).then(r => r.json());
    </script>

    2. 自定义指令:

    <script setup>
    const vFocus = {
    mounted: (el) => el.focus()
    };
    </script>

    <template>
    <input v-focus />
    </template>

    3. 命名组件:

    <script setup>
    // 不需要额外定义 name,文件名即组件名
    // 如果需要,可以用 <script> 块补充
    </script>

    <script>
    export default {
    name: 'MyComponent'
    };
    </script>

    4. 类型化 props 和 emits:

    <script setup lang="ts">
    // TypeScript 类型声明
    interface Props {
    name: string;
    age?: number;
    }

    const props = withDefaults(defineProps<Props>(), {
    age: 18
    });

    const emit = defineEmits<{
    (e: 'update', value: string): void;
    (e: 'delete', id: number): void;
    }>();
    </script>

    5.5 <script setup> 常见问题

    #问题解决方案
    1 怎么定义组件 name? 文件名即 name,或用 <script> 块补充
    2 怎么获取 DOM 引用? ref="xxx" + const xxx = ref(null)
    3 怎么使用第三方插件? 在 main.js 中 app.use(plugin)
    4 怎么访问全局属性? getCurrentInstance().appContext.config.globalProperties
    5 怎么定义异步组件? defineAsyncComponent
    6 怎么处理 v-model? defineModel()(Vue 3.4+)

    最佳实践:

    <script setup>
    import { ref, onMounted } from 'vue';

    // DOM 引用
    const inputRef = ref(null);

    onMounted(() => {
    inputRef.value.focus(); // 组件挂载后聚焦
    });
    </script>

    <template>
    <input ref="inputRef" />
    </template>

    5.6 <script setup> 与 Options API 混用

    在迁移过程中,可能需要在同一个组件里混用 Options API 和 Composition API:

    <script>
    // 传统 Options API
    export default {
    name: 'MyComponent',
    data() {
    return { oldCount: 0 };
    }
    };
    </script>

    <script setup>
    // Composition API
    import { ref } from 'vue';

    const newCount = ref(0);
    </script>

    混用注意事项:

    • setup() 和 Options API 的 data/methods 是隔离的;
    • 在 Options API 里可以访问 setup() 返回的变量(通过 this);
    • 不推荐长期混用——迁移完成后应该全量 Composition API。

    5.7 <script setup> 的最佳实践

    #实践说明
    1 使用 TypeScript 类型推导更自然
    2 顶层 await 用 Suspense 处理加载状态
    3 不要过度暴露 用 defineExpose 控制暴露的内容
    4 命名组件 文件名即组件名,或用 <script> 块补充
    5 自定义指令 顶层变量自动注册为自定义指令
    6 类型化 props/emits 用纯 TypeScript 声明

    最佳实践示例:

    <script setup lang="ts">
    // 1. TypeScript 类型声明
    interface Props {
    title: string;
    count?: number;
    }

    const props = withDefaults(defineProps<Props>(), {
    count: 0
    });

    // 2. 类型化 emits
    const emit = defineEmits<{
    (e: 'submit', data: FormData): void;
    (e: 'cancel'): void;
    }>();

    // 3. 暴露给父组件的内容
    defineExpose({
    focus: () => {
    // 父组件可以通过 ref 调用
    }
    });
    </script>

    5.8 <script setup> 的常见陷阱

    #陷阱解决方案
    1 顶级 await 导致 hydration 失败 SSR 中避免顶级 await
    2 props 解构丢失响应式 Vue 3.5+ 已修复,用 Reactive Props Destructure
    3 模板引用不工作 确保 ref 变量名和模板里的 ref 属性一致
    4 自定义指令不生效 确保指令名以 v 开头
    5 defineProps 不提示 确保 VS Code 安装了 Volar 插件
    6 类型不推导 确保 <script setup lang="ts">

    最佳实践:

    • 安装 Volar(Vue 官方 VS Code 插件);
    • 使用 TypeScript 获得更好的类型推导;
    • 保持组件简洁,不要在一个组件里写太多逻辑。

    6. 组件通信:Props/Emits/Slots

    6.1 Props:父传子

    <!– 子组件 –>
    <script setup>
    const props = defineProps({
    name: {
    type: String,
    required: true,
    default: 'Vue3'
    },
    count: {
    type: Number,
    default: 0
    }
    });
    </script>

    <!– 父组件 –>
    <ChildComponent name="张三" :count="10" />

    6.2 Emits:子传父

    <!– 子组件 –>
    <script setup>
    const emit = defineEmits(['update', 'delete']);

    function handleUpdate() {
    emit('update', { id: 1, value: 'new' });
    }
    </script>

    <!– 父组件 –>
    <ChildComponent @update="handleUpdate" @delete="handleDelete" />

    6.3 Slots:内容分发

    <!– 子组件 –>
    <template>
    <div>
    <header><slot name="header" /></header>
    <main><slot /></main>
    <footer><slot name="footer" /></footer>
    </div>
    </template>

    <!– 父组件 –>
    <ChildComponent>
    <template #header>标题</template>
    <template #default>内容</template>
    <template #footer>底部</template>
    </ChildComponent>

    6.4 provide/inject:依赖注入

    当组件层级很深时,props 一层层传递很麻烦——provide/inject 让祖先组件直接向后代组件提供数据:

    <!– 祖先组件 –>
    <script setup>
    import { provide, ref } from 'vue';

    const theme = ref('dark');
    const user = ref({ name: '张三', role: 'admin' });

    // 提供给所有后代组件
    provide('theme', theme);
    provide('user', user);
    </script>

    <!– 深层后代组件 –>
    <script setup>
    import { inject, watch } from 'vue';

    // 注入祖先提供的数据
    const theme = inject('theme');
    const user = inject('user');

    console.log(theme.value); // 'dark'
    </script>

    最佳实践:

    • 用 provide/inject 做跨层级共享,不要滥用(破坏组件封装);
    • 用 Symbol 作为 key,避免命名冲突;
    • 响应式数据用 ref/reactive 提供,注入后保持响应式。

    6.5 组件通信选型决策

    #mermaid-svg-8X83K7MuqhNlANqT{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8X83K7MuqhNlANqT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8X83K7MuqhNlANqT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8X83K7MuqhNlANqT .error-icon{fill:#552222;}#mermaid-svg-8X83K7MuqhNlANqT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8X83K7MuqhNlANqT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8X83K7MuqhNlANqT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8X83K7MuqhNlANqT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8X83K7MuqhNlANqT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8X83K7MuqhNlANqT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8X83K7MuqhNlANqT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8X83K7MuqhNlANqT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8X83K7MuqhNlANqT .marker.cross{stroke:#333333;}#mermaid-svg-8X83K7MuqhNlANqT svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8X83K7MuqhNlANqT p{margin:0;}#mermaid-svg-8X83K7MuqhNlANqT .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-8X83K7MuqhNlANqT .cluster-label text{fill:#333;}#mermaid-svg-8X83K7MuqhNlANqT .cluster-label span{color:#333;}#mermaid-svg-8X83K7MuqhNlANqT .cluster-label span p{background-color:transparent;}#mermaid-svg-8X83K7MuqhNlANqT .label text,#mermaid-svg-8X83K7MuqhNlANqT span{fill:#333;color:#333;}#mermaid-svg-8X83K7MuqhNlANqT .node rect,#mermaid-svg-8X83K7MuqhNlANqT .node circle,#mermaid-svg-8X83K7MuqhNlANqT .node ellipse,#mermaid-svg-8X83K7MuqhNlANqT .node polygon,#mermaid-svg-8X83K7MuqhNlANqT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8X83K7MuqhNlANqT .rough-node .label text,#mermaid-svg-8X83K7MuqhNlANqT .node .label text,#mermaid-svg-8X83K7MuqhNlANqT .image-shape .label,#mermaid-svg-8X83K7MuqhNlANqT .icon-shape .label{text-anchor:middle;}#mermaid-svg-8X83K7MuqhNlANqT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8X83K7MuqhNlANqT .rough-node .label,#mermaid-svg-8X83K7MuqhNlANqT .node .label,#mermaid-svg-8X83K7MuqhNlANqT .image-shape .label,#mermaid-svg-8X83K7MuqhNlANqT .icon-shape .label{text-align:center;}#mermaid-svg-8X83K7MuqhNlANqT .node.clickable{cursor:pointer;}#mermaid-svg-8X83K7MuqhNlANqT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8X83K7MuqhNlANqT .arrowheadPath{fill:#333333;}#mermaid-svg-8X83K7MuqhNlANqT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8X83K7MuqhNlANqT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8X83K7MuqhNlANqT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8X83K7MuqhNlANqT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8X83K7MuqhNlANqT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8X83K7MuqhNlANqT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8X83K7MuqhNlANqT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8X83K7MuqhNlANqT .cluster text{fill:#333;}#mermaid-svg-8X83K7MuqhNlANqT .cluster span{color:#333;}#mermaid-svg-8X83K7MuqhNlANqT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8X83K7MuqhNlANqT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8X83K7MuqhNlANqT rect.text{fill:none;stroke-width:0;}#mermaid-svg-8X83K7MuqhNlANqT .icon-shape,#mermaid-svg-8X83K7MuqhNlANqT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8X83K7MuqhNlANqT .icon-shape p,#mermaid-svg-8X83K7MuqhNlANqT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8X83K7MuqhNlANqT .icon-shape .label rect,#mermaid-svg-8X83K7MuqhNlANqT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8X83K7MuqhNlANqT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8X83K7MuqhNlANqT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8X83K7MuqhNlANqT :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    组件通信需求

    父子组件?

    父→子?

    Props

    子→父?

    Emits

    v-model / 双向绑定

    跨层级?

    provide/inject

    全局状态?

    Pinia

    事件总线 / 状态管理

    选型建议:

    通信方式适用场景
    Props 父→子,简单数据流
    Emits 子→父,事件通知
    v-model 父子双向绑定(表单)
    provide/inject 跨层级共享(主题、用户信息)
    Pinia 全局状态(用户、购物车、设置)
    事件总线 跨组件事件(已少用,用 Pinia 替代)

    6.6 组件设计原则

    原则说明
    单一职责 每个组件只做一件事
    Props 下行,Events 上行 数据从父到子,事件从子到父
    组件粒度 不要太大,也不要太小
    可复用性 组件要可复用、可配置
    组合优于继承 用插槽和组合替代继承

    组件拆分示例:

    UserProfile.vue(大组件)
    ├── UserAvatar.vue(头像)
    ├── UserInfo.vue(用户信息)
    ├── UserActions.vue(操作按钮)
    └── UserOrders.vue(订单列表)

    为什么要拆分:

    • 更好的复用性;
    • 更好的可维护性;
    • 更好的测试性;
    • 更好的性能(细粒度更新)。

    6.7 组件通信的最佳实践

    #实践说明
    1 Props 要类型化 用 TypeScript 声明 Props 类型
    2 Emits 要明确 声明所有可能的事件
    3 避免 prop drilling 用 provide/inject 或 Pinia 替代
    4 不要滥用 provide/inject 只用于跨层级共享
    5 全局状态用 Pinia 不要用 props 传全局状态
    6 组件要单一职责 不要做太多事

    最佳实践示例:

    <script setup lang="ts">
    // 好的:类型化 Props 和 Emits
    interface Props {
    title: string;
    visible: boolean;
    }

    const props = withDefaults(defineProps<Props>(), {
    visible: true
    });

    const emit = defineEmits<{
    (e: 'close'): void;
    (e: 'confirm', value: string): void;
    }>();
    </script>


    7. Pinia 状态管理

    7.1 从 Vuex 到 Pinia

    Pinia 是 Vue3 的官方推荐状态管理库——它替代了 Vuex:

    维度VuexPinia
    API 风格 mutations + actions 直接 actions
    TypeScript 支持有限 原生支持
    模块化 modules 嵌套 扁平 store
    DevTools 支持 支持(更好的时间旅行)
    体积 较大 更小

    7.2 Pinia 的核心概念

    // stores/counter.js
    import { defineStore } from 'pinia';
    import { ref, computed } from 'vue';

    export const useCounterStore = defineStore('counter', () => {
    // state
    const count = ref(0);
    // getters
    const doubleCount = computed(() => count.value * 2);
    // actions
    function increment() {
    count.value++;
    }
    function reset() {
    count.value = 0;
    }
    return { count, doubleCount, increment, reset };
    });

    7.3 在组件中使用 Pinia

    <script setup>
    import { useCounterStore } from '@/stores/counter';

    const counter = useCounterStore();

    // 直接使用
    console.log(counter.count); // 0
    console.log(counter.doubleCount); // 0

    // 调用 action
    counter.increment();
    console.log(counter.count); // 1
    </script>

    7.4 Pinia 实战:用户 Store

    // stores/user.js
    import { defineStore } from 'pinia';
    import { ref, computed } from 'vue';
    import { loginApi, logoutApi, getUserInfoApi } from '@/api/user';

    export const useUserStore = defineStore('user', () => {
    // state
    const token = ref(localStorage.getItem('token') || '');
    const userInfo = ref(null);

    // getters
    const isLoggedIn = computed(() => !!token.value);
    const userName = computed(() => userInfo.value?.name || '');
    const isAdmin = computed(() => userInfo.value?.role === 'admin');

    // actions
    async function login(loginForm) {
    const res = await loginApi(loginForm);
    token.value = res.token;
    localStorage.setItem('token', res.token);
    await fetchUserInfo();
    return res;
    }

    async function fetchUserInfo() {
    if (!token.value) return;
    userInfo.value = await getUserInfoApi();
    }

    async function logout() {
    await logoutApi();
    token.value = '';
    userInfo.value = null;
    localStorage.removeItem('token');
    }

    return {
    token,
    userInfo,
    isLoggedIn,
    userName,
    isAdmin,
    login,
    fetchUserInfo,
    logout
    };
    });

    7.5 Pinia 持久化插件

    // plugins/piniaPersist.js
    export function piniaPersist({ store }) {
    // 监听 store 变化,自动持久化到 localStorage
    store.$subscribe((mutation, state) => {
    localStorage.setItem(store.$id, JSON.stringify(state));
    });

    // 初始化时从 localStorage 恢复
    const saved = localStorage.getItem(store.$id);
    if (saved) {
    store.$patch(JSON.parse(saved));
    }
    }

    // main.js
    import { createPinia } from 'pinia';
    import { piniaPersist } from '@/plugins/piniaPersist';

    const pinia = createPinia();
    pinia.use(piniaPersist);

    7.6 Pinia 最佳实践

    #实践说明
    1 按功能拆分 store 不要把所有状态放一个 store
    2 使用组合式语法 defineStore('id', () => {}) 比选项式更灵活
    3 持久化按需 只持久化需要持久化的 store
    4 不要滥用 store 组件局部状态用 ref/reactive,全局状态才用 Pinia
    5 服务端交互放 action API 请求放在 action 里,不要放组件里
    6 getter 纯函数 getter 不要有副作用,只做计算

    常见误区:

    • 所有状态都放 Pinia:错——组件局部状态用 ref/reactive 更轻量;
    • store 里放 UI 状态:错——loading、modal 开关等 UI 状态应该放组件里;
    • 直接修改 state:可以,但大型项目建议用 action 统一修改。

    7.7 Pinia 与服务端交互

    最佳实践:API 请求放在 Pinia 的 action 里:

    // stores/user.js
    import { defineStore } from 'pinia';
    import { ref } from 'vue';
    import api from '@/api';

    export const useUserStore = defineStore('user', () => {
    const userList = ref([]);
    const loading = ref(false);
    const error = ref(null);

    async function fetchUsers() {
    loading.value = true;
    error.value = null;
    try {
    const res = await api.get('/users');
    userList.value = res.data;
    } catch (err) {
    error.value = err;
    } finally {
    loading.value = false;
    }
    }

    async function createUser(userData) {
    loading.value = true;
    try {
    const res = await api.post('/users', userData);
    userList.value.push(res.data);
    return res.data;
    } catch (err) {
    error.value = err;
    throw err;
    } finally {
    loading.value = false;
    }
    }

    return {
    userList,
    loading,
    error,
    fetchUsers,
    createUser
    };
    });

    在组件中使用:

    <script setup>
    import { useUserStore } from '@/stores/user';
    import { onMounted } from 'vue';

    const userStore = useUserStore();

    onMounted(() => {
    userStore.fetchUsers();
    });
    </script>

    7.8 Pinia 的 DevTools 与调试

    Vue DevTools 是调试 Pinia 的利器:

    功能说明
    时间旅行 回溯到任意时间点的状态
    状态检查 查看每个 store 的当前状态
    Action 追踪 查看 action 的调用栈和参数
    状态编辑 直接在 DevTools 里修改状态

    调试技巧:

    // 1. 在 store 里打印状态
    console.log(userStore.$state);

    // 2. 订阅 store 变化
    userStore.$subscribe((mutation, state) => {
    console.log('User store changed:', mutation, state);
    });

    // 3. 重置 store
    userStore.$reset();

    常见问题:

    • store 不响应:检查是否在 setup 外使用;
    • 状态不同步:检查是否直接修改 state;
    • DevTools 不显示:检查 Vue DevTools 版本是否支持 Pinia。

    7.9 Pinia 的常见陷阱

    #陷阱解决方案
    1 store 不响应 确保在 setup 或组件里使用
    2 直接修改 state 不触发 Pinia 允许直接修改,但大型项目建议用 action
    3 store 之间互相引用 可以,但要注意循环依赖
    4 持久化不生效 检查插件配置和存储 key
    5 SSR 中 store 不共享 用 Nuxt 的 useState 替代
    6 TypeScript 类型不推导 确保 store 有正确的类型标注

    最佳实践:

    • 按功能拆分 store:不要把所有状态放一个 store;
    • 使用组合式语法:defineStore('id', () => {}) 比选项式更灵活;
    • API 请求放 action:不要在组件里直接调 API;
    • getter 纯函数:getter 不要有副作用,只做计算。

    8. Vite 构建工具

    8.1 为什么是 Vite

    Vite 是 Vue3 推荐的构建工具——它解决了 webpack 的启动慢问题:

    维度webpackVite
    启动时间 慢(打包后启动) 极快(原生 ESM,无需打包)
    热更新 慢(重新打包) 极快(按需编译)
    构建 webpack Rollup(生产构建)
    配置 复杂 简单

    8.2 Vite 的核心优势

    #mermaid-svg-IFGjd4LaRzUIWpci{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IFGjd4LaRzUIWpci .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IFGjd4LaRzUIWpci .error-icon{fill:#552222;}#mermaid-svg-IFGjd4LaRzUIWpci .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IFGjd4LaRzUIWpci .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IFGjd4LaRzUIWpci .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IFGjd4LaRzUIWpci .marker.cross{stroke:#333333;}#mermaid-svg-IFGjd4LaRzUIWpci svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IFGjd4LaRzUIWpci p{margin:0;}#mermaid-svg-IFGjd4LaRzUIWpci .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-IFGjd4LaRzUIWpci .cluster-label text{fill:#333;}#mermaid-svg-IFGjd4LaRzUIWpci .cluster-label span{color:#333;}#mermaid-svg-IFGjd4LaRzUIWpci .cluster-label span p{background-color:transparent;}#mermaid-svg-IFGjd4LaRzUIWpci .label text,#mermaid-svg-IFGjd4LaRzUIWpci span{fill:#333;color:#333;}#mermaid-svg-IFGjd4LaRzUIWpci .node rect,#mermaid-svg-IFGjd4LaRzUIWpci .node circle,#mermaid-svg-IFGjd4LaRzUIWpci .node ellipse,#mermaid-svg-IFGjd4LaRzUIWpci .node polygon,#mermaid-svg-IFGjd4LaRzUIWpci .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IFGjd4LaRzUIWpci .rough-node .label text,#mermaid-svg-IFGjd4LaRzUIWpci .node .label text,#mermaid-svg-IFGjd4LaRzUIWpci .image-shape .label,#mermaid-svg-IFGjd4LaRzUIWpci .icon-shape .label{text-anchor:middle;}#mermaid-svg-IFGjd4LaRzUIWpci .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IFGjd4LaRzUIWpci .rough-node .label,#mermaid-svg-IFGjd4LaRzUIWpci .node .label,#mermaid-svg-IFGjd4LaRzUIWpci .image-shape .label,#mermaid-svg-IFGjd4LaRzUIWpci .icon-shape .label{text-align:center;}#mermaid-svg-IFGjd4LaRzUIWpci .node.clickable{cursor:pointer;}#mermaid-svg-IFGjd4LaRzUIWpci .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IFGjd4LaRzUIWpci .arrowheadPath{fill:#333333;}#mermaid-svg-IFGjd4LaRzUIWpci .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IFGjd4LaRzUIWpci .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IFGjd4LaRzUIWpci .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IFGjd4LaRzUIWpci .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IFGjd4LaRzUIWpci .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IFGjd4LaRzUIWpci .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IFGjd4LaRzUIWpci .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IFGjd4LaRzUIWpci .cluster text{fill:#333;}#mermaid-svg-IFGjd4LaRzUIWpci .cluster span{color:#333;}#mermaid-svg-IFGjd4LaRzUIWpci div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IFGjd4LaRzUIWpci .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IFGjd4LaRzUIWpci rect.text{fill:none;stroke-width:0;}#mermaid-svg-IFGjd4LaRzUIWpci .icon-shape,#mermaid-svg-IFGjd4LaRzUIWpci .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IFGjd4LaRzUIWpci .icon-shape p,#mermaid-svg-IFGjd4LaRzUIWpci .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IFGjd4LaRzUIWpci .icon-shape .label rect,#mermaid-svg-IFGjd4LaRzUIWpci .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IFGjd4LaRzUIWpci .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IFGjd4LaRzUIWpci .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IFGjd4LaRzUIWpci :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Vite 开发模式

    启动

    启动 dev server

    按需编译(浏览器请求哪个文件编译哪个)

    快(毫秒级)

    webpack 开发模式

    启动

    打包整个应用

    启动 dev server

    慢(大型应用数分钟)

    核心原理:Vite 利用浏览器原生的 ES Module——开发时不需要打包,浏览器请求哪个文件,Vite 就编译哪个文件,启动时间与应用大小无关。

    8.3 创建 Vue3 项目

    # 使用 create-vue(官方脚手架)
    npm create vue@latest my-app

    # 或使用 Vite
    npm create vite@latest my-app — –template vue

    8.4 Vite 配置与优化

    vite.config.js 常用配置:

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import { resolve } from 'path';

    export default defineConfig({
    plugins: [vue()],
    resolve: {
    alias: {
    '@': resolve(__dirname, './src')
    }
    },
    server: {
    port: 3000,
    open: true,
    proxy: {
    '/api': {
    target: 'http://localhost:8080',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\\/api/, '')
    }
    }
    },
    build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false,
    rollupOptions: {
    output: {
    manualChunks: {
    vendor: ['vue', 'vue-router', 'pinia']
    }
    }
    }
    }
    });

    Vite 优化要点:

    优化说明
    路径别名 @ 指向 src,简化导入路径
    开发代理 解决开发环境跨域问题
    代码分割 manualChunks 拆分 vendor 包
    生产 sourcemap 生产环境关闭,减小体积
    环境变量 .env.development / .env.production

    8.5 Vite 常见问题

    #问题解决方案
    1 开发环境跨域 配置 server.proxy 代理
    2 路径别名不生效 配置 resolve.alias
    3 生产构建慢 配置 manualChunks 拆分 vendor
    4 静态资源路径不对 配置 base 为子路径
    5 HMR 不生效 检查文件路径和缓存
    6 环境变量不生效 用 import.meta.env.VITE_XXX

    环境变量使用:

    // .env.development
    VITE_API_BASE_URL=http://localhost:8080/api

    // .env.production
    VITE_API_BASE_URL=https://api.example.com

    // 代码中使用
    const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;

    8.6 Vite 与 webpack 迁移

    迁移步骤:

    步骤工作内容
    1 安装 Vite + @vitejs/plugin-vue
    2 创建 vite.config.js
    3 迁移 index.html 到根目录
    4 迁移静态资源到 public/
    5 替换 webpack 特有的 loader/plugin
    6 测试开发环境和生产构建

    常见迁移坑:

    坑解决方案
    require 不支持 用 import 替代
    require.context 不支持 用 import.meta.glob 替代
    process.env 不支持 用 import.meta.env 替代
    静态资源路径不对 迁移到 public/ 或用 import 引入

    8.7 Vite 的插件生态

    插件说明
    @vitejs/plugin-vue Vue 3 支持
    @vitejs/plugin-vue-jsx JSX 支持
    @vitejs/plugin-react React 支持
    vite-plugin-windicss Windi CSS(原子化 CSS)
    vite-plugin-mock Mock 数据
    vite-plugin-compression 生产构建压缩
    vite-plugin-pwa PWA 支持

    常用插件配置:

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import vueJsx from '@vitejs/plugin-vue-jsx';
    import WindiCSS from 'vite-plugin-windicss';

    export default defineConfig({
    plugins: [
    vue(),
    vueJsx(),
    WindiCSS()
    ]
    });

    8.8 Vite 的性能优化

    优化说明
    依赖预构建 Vite 自动预构建依赖,提升冷启动速度
    代码分割 manualChunks 拆分 vendor 包
    懒加载 路由级懒加载,按需加载
    压缩 生产构建自动压缩(gzip/brotli)
    CDN 静态资源用 CDN 加速
    缓存 利用浏览器缓存,提升二次访问速度

    性能优化最佳实践:

    // vite.config.js
    export default defineConfig({
    build: {
    rollupOptions: {
    output: {
    manualChunks: {
    // 拆分第三方库
    vendor: ['vue', 'vue-router', 'pinia'],
    // 拆分 UI 组件库
    ui: ['element-plus', 'ant-design-vue']
    }
    }
    },
    // 构建压缩
    minify: 'terser',
    terserOptions: {
    compress: {
    drop_console: true // 生产环境移除 console
    }
    }
    }
    });


    9. Vue Router 4

    9.1 Vue Router 4 的变化

    Vue Router 4 是 Vue3 对应的路由版本:

    // router/index.js
    import { createRouter, createWebHistory } from 'vue-router';

    const routes = [
    {
    path: '/',
    name: 'Home',
    component: () => import('@/views/Home.vue')
    },
    {
    path: '/about',
    name: 'About',
    component: () => import('@/views/About.vue')
    }
    ];

    const router = createRouter({
    history: createWebHistory(),
    routes
    });

    export default router;

    9.2 在组件中使用路由

    <script setup>
    import { useRouter, useRoute } from 'vue-router';

    const router = useRouter();
    const route = useRoute();

    // 编程式导航
    function goAbout() {
    router.push('/about');
    }

    // 获取路由参数
    console.log(route.params.id);
    </script>

    9.3 Vue Router 4 进阶:导航守卫

    // router/index.js
    import { createRouter, createWebHistory } from 'vue-router';
    import { useUserStore } from '@/stores/user';

    const routes = [
    {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue')
    },
    {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    meta: { requiresAuth: true } // 需要登录
    },
    {
    path: '/admin',
    name: 'Admin',
    component: () => import('@/views/Admin.vue'),
    meta: { requiresAuth: true, requiresAdmin: true } // 需要管理员
    }
    ];

    const router = createRouter({
    history: createWebHistory(),
    routes
    });

    // 全局前置守卫
    router.beforeEach((to, from, next) => {
    const userStore = useUserStore();

    // 需要登录的页面
    if (to.meta.requiresAuth && !userStore.isLoggedIn) {
    next('/login');
    }
    // 需要管理员的页面
    else if (to.meta.requiresAdmin && !userStore.isAdmin) {
    next('/dashboard');
    }
    // 正常放行
    else {
    next();
    }
    });

    export default router;

    路由懒加载:

    // 路由级代码分割,按需加载
    const routes = [
    {
    path: '/about',
    component: () => import('@/views/About.vue') // 懒加载
    }
    ];

    9.4 Vue Router 4 常见问题

    #问题解决方案
    1 路由守卫不生效 检查守卫注册位置和 next() 调用
    2 路由参数不响应 用 watch(() => route.params.id) 监听
    3 动态路由不更新 给 router-view 加 :key="$route.fullPath"
    4 路由跳转死循环 检查守卫条件,避免无限重定向
    5 404 页面不生效 配置 path: '/:pathMatch(.*)*' 通配路由
    6 路由元信息不生效 检查 meta 配置和类型声明

    最佳实践:

    <script setup>
    import { useRoute } from 'vue-router';

    const route = useRoute();

    // 监听路由参数变化
    watch(() => route.params.id, (newId) => {
    // 根据新 id 重新加载数据
    loadData(newId);
    });
    </script>

    9.5 Vue Router 的懒加载与代码分割

    路由懒加载是优化首屏加载的关键手段:

    // 1. 路由级懒加载
    const routes = [
    {
    path: '/dashboard',
    component: () => import('@/views/Dashboard.vue')
    }
    ];

    // 2. 按组分包
    const routes = [
    {
    path: '/admin',
    component: () => import('@/layouts/AdminLayout.vue'),
    children: [
    {
    path: 'users',
    component: () => import('@/views/admin/Users.vue')
    },
    {
    path: 'settings',
    component: () => import('@/views/admin/Settings.vue')
    }
    ]
    }
    ];

    // 3. 预加载(prefetch)
    const Dashboard = defineAsyncComponent({
    loader: () => import('@/views/Dashboard.vue'),
    loadingComponent: LoadingSpinner,
    delay: 200, // 200ms 后显示 loading
    timeout: 3000 // 3s 超时
    });

    代码分割最佳实践:

    实践说明
    路由级懒加载 每个路由一个 chunk
    按组分包 相关路由放一个 chunk
    大组件懒加载 弹窗、表格等大组件懒加载
    第三方库分包 vendor 包单独打包

    10. Nuxt 3 SSR/元框架

    10.1 什么是 Nuxt

    Nuxt 是基于 Vue3 的元框架(Meta-framework)——它提供 SSR(服务端渲染)、SSG(静态站点生成)、文件路由等能力:

    特性说明
    SSR 服务端渲染,首屏更快
    SSG 静态站点生成,SEO 友好
    文件路由 文件即路由,无需配置
    自动导入 组件、composables 自动导入
    布局系统 可复用的布局组件

    10.2 Nuxt 3 的核心优势

    #mermaid-svg-moR14p6xOPQC7yBq{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-moR14p6xOPQC7yBq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-moR14p6xOPQC7yBq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-moR14p6xOPQC7yBq .error-icon{fill:#552222;}#mermaid-svg-moR14p6xOPQC7yBq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-moR14p6xOPQC7yBq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-moR14p6xOPQC7yBq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-moR14p6xOPQC7yBq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-moR14p6xOPQC7yBq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-moR14p6xOPQC7yBq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-moR14p6xOPQC7yBq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-moR14p6xOPQC7yBq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-moR14p6xOPQC7yBq .marker.cross{stroke:#333333;}#mermaid-svg-moR14p6xOPQC7yBq svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-moR14p6xOPQC7yBq p{margin:0;}#mermaid-svg-moR14p6xOPQC7yBq .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-moR14p6xOPQC7yBq .cluster-label text{fill:#333;}#mermaid-svg-moR14p6xOPQC7yBq .cluster-label span{color:#333;}#mermaid-svg-moR14p6xOPQC7yBq .cluster-label span p{background-color:transparent;}#mermaid-svg-moR14p6xOPQC7yBq .label text,#mermaid-svg-moR14p6xOPQC7yBq span{fill:#333;color:#333;}#mermaid-svg-moR14p6xOPQC7yBq .node rect,#mermaid-svg-moR14p6xOPQC7yBq .node circle,#mermaid-svg-moR14p6xOPQC7yBq .node ellipse,#mermaid-svg-moR14p6xOPQC7yBq .node polygon,#mermaid-svg-moR14p6xOPQC7yBq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-moR14p6xOPQC7yBq .rough-node .label text,#mermaid-svg-moR14p6xOPQC7yBq .node .label text,#mermaid-svg-moR14p6xOPQC7yBq .image-shape .label,#mermaid-svg-moR14p6xOPQC7yBq .icon-shape .label{text-anchor:middle;}#mermaid-svg-moR14p6xOPQC7yBq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-moR14p6xOPQC7yBq .rough-node .label,#mermaid-svg-moR14p6xOPQC7yBq .node .label,#mermaid-svg-moR14p6xOPQC7yBq .image-shape .label,#mermaid-svg-moR14p6xOPQC7yBq .icon-shape .label{text-align:center;}#mermaid-svg-moR14p6xOPQC7yBq .node.clickable{cursor:pointer;}#mermaid-svg-moR14p6xOPQC7yBq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-moR14p6xOPQC7yBq .arrowheadPath{fill:#333333;}#mermaid-svg-moR14p6xOPQC7yBq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-moR14p6xOPQC7yBq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-moR14p6xOPQC7yBq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-moR14p6xOPQC7yBq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-moR14p6xOPQC7yBq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-moR14p6xOPQC7yBq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-moR14p6xOPQC7yBq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-moR14p6xOPQC7yBq .cluster text{fill:#333;}#mermaid-svg-moR14p6xOPQC7yBq .cluster span{color:#333;}#mermaid-svg-moR14p6xOPQC7yBq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-moR14p6xOPQC7yBq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-moR14p6xOPQC7yBq rect.text{fill:none;stroke-width:0;}#mermaid-svg-moR14p6xOPQC7yBq .icon-shape,#mermaid-svg-moR14p6xOPQC7yBq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-moR14p6xOPQC7yBq .icon-shape p,#mermaid-svg-moR14p6xOPQC7yBq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-moR14p6xOPQC7yBq .icon-shape .label rect,#mermaid-svg-moR14p6xOPQC7yBq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-moR14p6xOPQC7yBq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-moR14p6xOPQC7yBq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-moR14p6xOPQC7yBq :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    Nuxt SSR

    浏览器请求

    服务端渲染 HTML

    直接返回完整 HTML

    首屏快 + SEO 好

    传统 SPA

    浏览器请求 HTML

    下载 JS Bundle

    浏览器渲染

    首屏慢 + SEO 差

    适用场景:

    • 内容型网站(博客、文档、营销页):SSG 静态生成;
    • 电商/门户:SSR 服务端渲染,首屏快;
    • 大型应用:Nuxt 的文件路由和自动导入提升开发效率。

    10.3 Nuxt 3 实战:目录结构

    my-nuxt-app/
    ├── assets/ # 未编译的资源(样式、图片)
    ├── components/ # 组件(自动导入)
    ├── composables/ # composable 函数(自动导入)
    ├── layouts/ # 布局组件
    ├── middleware/ # 路由中间件
    ├── pages/ # 页面(文件路由)
    ├── plugins/ # 插件
    ├── public/ # 静态文件
    ├── server/ # 服务端 API
    └── nuxt.config.ts # Nuxt 配置

    文件路由示例:

    pages/
    ├── index.vue # → /
    ├── about.vue # → /about
    ├── products/
    │ ├── index.vue # → /products
    │ └── [id].vue # → /products/:id
    └── blog/
    └── [slug].vue # → /blog/:slug

    自动导入:

    <script setup>
    // 不需要 import,Nuxt 自动导入
    const products = ref([]);

    // useFetch 是 Nuxt 内置的 composable
    const { data: posts } = await useFetch('/api/posts');
    </script>

    10.4 Nuxt 3 常见问题

    #问题解决方案
    1 服务端/客户端不兼容 用 process.server 或 client-only 组件
    2 SSR hydration 失败 检查模板不一致,避免服务端渲染时访问 window
    3 API 请求重复执行 用 useFetch 的缓存策略
    4 路由守卫不生效 用 middleware/ 目录的中间件
    5 静态生成失败 检查 nuxt generate 错误日志
    6 SEO 不友好 用 useHead 设置 meta 标签

    最佳实践:

    <script setup>
    // SSR 兼容:只在客户端执行
    onMounted(() => {
    // 这里的代码只在客户端执行
    initChart();
    });

    // SEO:设置页面标题和 meta
    useHead({
    title: '首页 – My App',
    meta: [
    { name: 'description', content: '这是我的 Nuxt 3 应用' }
    ]
    });
    </script>

    10.5 Nuxt 3 的部署方案

    部署方式说明适用场景
    Node.js 服务器 运行 node .output/server/index.mjs SSR 应用
    Vercel/Netlify 自动部署 静态站点/SSR
    Docker 容器化部署 企业级部署
    静态生成 nuxt generate 生成静态文件 博客/文档

    部署示例:

    # 1. 构建生产版本
    npm run build

    # 2. 启动生产服务器
    node .output/server/index.mjs

    # 3. 静态生成
    npm run generate
    # 输出到 .output/public/

    部署注意事项:

    • 环境变量要正确配置;
    • 服务端和客户端的 API 地址要区分;
    • 静态资源路径要正确配置;
    • SSR 应用需要 Node.js 运行环境。

    11. 新特性:Teleport/Suspense/Fragment

    11.1 Teleport:传送门

    Teleport 让你把组件渲染到 DOM 的其他位置——常用于模态框、通知:

    <template>
    <button @click="showModal = true">打开模态框</button>

    <!– 模态框渲染到 body 下,而不是组件内部 –>
    <Teleport to="body">
    <div v-if="showModal" class="modal">
    模态框内容
    <button @click="showModal = false">关闭</button>
    </div>
    </Teleport>
    </template>

    11.2 Suspense:异步组件

    Suspense 让你处理异步组件的加载状态:

    <template>
    <Suspense>
    <!– 异步组件 –>
    <template #default>
    <AsyncComponent />
    </template>
    <!– 加载中显示 –>
    <template #fallback>
    <div>加载中…</div>
    </template>
    </Suspense>
    </template>

    11.3 Fragment:多根节点

    Vue3 支持组件模板有多个根节点——不再需要包裹一层 <div>:

    <!– Vue2:必须单根 –>
    <template>
    <div>
    <header>…</header>
    <main>…</main>
    <footer>…</footer>
    </div>
    </template>

    <!– Vue3:多根节点(Fragment) –>
    <template>
    <header>…</header>
    <main>…</main>
    <footer>…</footer>
    </template>

    11.4 v-model 多参数与新特性

    Vue3 的 v-model 支持多参数——一个组件可以有多个 v-model:

    <!– 子组件 –>
    <script setup>
    const modelValue = defineModel(); // 默认 v-model
    const title = defineModel('title'); // v-model:title
    </script>

    <template>
    <input v-model="modelValue" />
    <input v-model="title" />
    </template>

    <!– 父组件 –>
    <ChildComponent v-model="content" v-model:title="pageTitle" />

    其他新特性:

    特性说明
    defineModel() 简化 v-model 声明
    defineSlots() 类型化插槽
    defineExpose() 暴露组件内部方法
    useTemplateRef() 获取模板引用(Vue 3.5+)
    onEffectCleanup() 副作用清理(Vue 3.5+)

    12. Vue3 性能优化

    12.1 编译时优化

    Vue3 的编译器做了大量优化:

    优化说明
    静态提升 静态节点提升到渲染函数外,避免重复创建
    Patch Flag 动态节点标记,只更新变化的部分
    Block Tree 树状结构,跳过静态子树
    缓存事件 事件处理函数缓存,避免重复绑定

    12.2 响应式优化

    优化说明
    shallowRef/shallowReactive 浅层响应式,只监听顶层
    markRaw 标记对象为"不响应式"
    computed 缓存 计算属性自动缓存,依赖不变不重新计算

    12.3 运行时优化

    优化说明
    v-once 只渲染一次,后续不更新
    v-memo 记忆组件,依赖不变不重新渲染
    异步组件 路由级代码分割
    懒加载 组件按需加载

    12.4 性能优化实战案例

    案例 1:大型列表渲染优化

    <!– 优化前:每次滚动都重新渲染 –>
    <template>
    <div v-for="item in largeList" :key="item.id">
    {{ item.name }}
    </div>
    </template>

    <!– 优化后:虚拟滚动,只渲染可视区域 –>
    <template>
    <RecycleScroller
    :items="largeList"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
    >
    <div>{{ item.name }}</div>
    </RecycleScroller>
    </template>

    案例 2:图片懒加载

    <script setup>
    // 使用 Intersection Observer 实现图片懒加载
    const imgRef = ref(null);

    onMounted(() => {
    const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
    if (entry.isIntersecting) {
    const img = entry.target;
    img.src = img.dataset.src;
    observer.unobserve(img);
    }
    });
    });
    observer.observe(imgRef.value);
    });
    </script>

    <template>
    <img ref="imgRef" data-src="/large-image.jpg" alt="…" />
    </template>

    性能优化检查清单:

    #优化项优先级
    1 路由懒加载 ⭐⭐⭐⭐⭐
    2 组件懒加载 ⭐⭐⭐⭐
    3 虚拟滚动(长列表) ⭐⭐⭐⭐
    4 图片懒加载 ⭐⭐⭐⭐
    5 v-memo 优化大列表 ⭐⭐⭐
    6 shallowRef/shallowReactive ⭐⭐⭐
    7 markRaw 跳过响应式 ⭐⭐

    12.5 性能监控与分析

    性能指标:

    指标说明目标
    FCP(First Contentful Paint) 首次内容绘制 < 1.8s
    LCP(Largest Contentful Paint) 最大内容绘制 < 2.5s
    FID(First Input Delay) 首次输入延迟 < 100ms
    CLS(Cumulative Layout Shift) 累积布局偏移 < 0.1

    性能分析工具:

    工具说明
    Chrome DevTools Performance 分析渲染性能
    Vue DevTools 分析组件渲染次数
    Lighthouse 综合性能评分
    Web Vitals 监控核心指标

    性能优化流程:

  • 测量:用 Lighthouse 跑一遍性能评分;
  • 分析:用 Performance 面板看渲染瓶颈;
  • 优化:针对瓶颈做优化;
  • 验证:再跑一遍 Lighthouse 看提升。
  • 12.6 常见性能问题与解决方案

    #问题解决方案
    1 首屏加载慢 路由懒加载、代码分割、静态生成
    2 列表渲染慢 虚拟滚动、v-memo、shallowRef
    3 响应式更新慢 shallowReactive、markRaw、减少深层响应式
    4 打包体积大 manualChunks、按需加载、Tree-shaking
    5 内存泄漏 清理定时器、清理事件监听、onUnmounted
    6 频繁重渲染 computed 缓存、v-memo、避免不必要的响应式

    性能优化口诀:

    先测量,再优化;先找瓶颈,再动手;先做高收益,再做低收益。


    13. 迁移:从 Vue2 到 Vue3

    13.1 迁移路线图

    #mermaid-svg-AwV1T9CrwcqLTyrp{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AwV1T9CrwcqLTyrp .error-icon{fill:#552222;}#mermaid-svg-AwV1T9CrwcqLTyrp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AwV1T9CrwcqLTyrp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AwV1T9CrwcqLTyrp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AwV1T9CrwcqLTyrp .marker.cross{stroke:#333333;}#mermaid-svg-AwV1T9CrwcqLTyrp svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AwV1T9CrwcqLTyrp p{margin:0;}#mermaid-svg-AwV1T9CrwcqLTyrp .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp .cluster-label text{fill:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp .cluster-label span{color:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp .cluster-label span p{background-color:transparent;}#mermaid-svg-AwV1T9CrwcqLTyrp .label text,#mermaid-svg-AwV1T9CrwcqLTyrp span{fill:#333;color:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp .node rect,#mermaid-svg-AwV1T9CrwcqLTyrp .node circle,#mermaid-svg-AwV1T9CrwcqLTyrp .node ellipse,#mermaid-svg-AwV1T9CrwcqLTyrp .node polygon,#mermaid-svg-AwV1T9CrwcqLTyrp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AwV1T9CrwcqLTyrp .rough-node .label text,#mermaid-svg-AwV1T9CrwcqLTyrp .node .label text,#mermaid-svg-AwV1T9CrwcqLTyrp .image-shape .label,#mermaid-svg-AwV1T9CrwcqLTyrp .icon-shape .label{text-anchor:middle;}#mermaid-svg-AwV1T9CrwcqLTyrp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AwV1T9CrwcqLTyrp .rough-node .label,#mermaid-svg-AwV1T9CrwcqLTyrp .node .label,#mermaid-svg-AwV1T9CrwcqLTyrp .image-shape .label,#mermaid-svg-AwV1T9CrwcqLTyrp .icon-shape .label{text-align:center;}#mermaid-svg-AwV1T9CrwcqLTyrp .node.clickable{cursor:pointer;}#mermaid-svg-AwV1T9CrwcqLTyrp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AwV1T9CrwcqLTyrp .arrowheadPath{fill:#333333;}#mermaid-svg-AwV1T9CrwcqLTyrp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AwV1T9CrwcqLTyrp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AwV1T9CrwcqLTyrp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AwV1T9CrwcqLTyrp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AwV1T9CrwcqLTyrp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AwV1T9CrwcqLTyrp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AwV1T9CrwcqLTyrp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AwV1T9CrwcqLTyrp .cluster text{fill:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp .cluster span{color:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AwV1T9CrwcqLTyrp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AwV1T9CrwcqLTyrp rect.text{fill:none;stroke-width:0;}#mermaid-svg-AwV1T9CrwcqLTyrp .icon-shape,#mermaid-svg-AwV1T9CrwcqLTyrp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AwV1T9CrwcqLTyrp .icon-shape p,#mermaid-svg-AwV1T9CrwcqLTyrp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AwV1T9CrwcqLTyrp .icon-shape .label rect,#mermaid-svg-AwV1T9CrwcqLTyrp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AwV1T9CrwcqLTyrp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AwV1T9CrwcqLTyrp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AwV1T9CrwcqLTyrp :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    1 依赖升级Vue2.7 + Vite

    2 响应式迁移defineProperty → Proxy

    3 API 迁移Options → Composition

    4 生态迁移Vuex → Pinia + Router 4

    5 新特性采用Teleport/Suspense

    6 性能优化编译时 + 响应式

    13.2 常见迁移坑

    #坑对策
    1 this.$set 还在用 Vue3 不需要了,直接赋值
    2 Options API 全保留 逐步迁移到 Composition API
    3 Vuex 还在用 迁移到 Pinia
    4 Vue Router 3 还在用 迁移到 Vue Router 4
    5 webpack 还在用 迁移到 Vite
    6 生命周期改名 beforeDestroy → onBeforeUnmount

    13.3 迁移检查清单

    第一步:环境准备

    • 升级 Node.js 到 18+
    • 升级 npm/pnpm/yarn
    • 安装 Vue 3 + Vite

    第二步:依赖升级

    • Vue 2.x → Vue 3.x
    • Vue Router 3 → 4
    • Vuex → Pinia
    • 组件库升级到 Vue3 版本(Element Plus / Ant Design Vue 等)

    第三步:代码迁移

    • 生命周期改名(beforeDestroy → onBeforeUnmount)
    • 移除 Vue.set / Vue.delete
    • 逐步迁移到 Composition API
    • 修复 v-model 语法变化
    • 修复事件总线($on/$off 已移除)

    第四步:测试验证

    • 单元测试迁移
    • E2E 测试迁移
    • 功能回归测试
    • 性能对比测试

    13.4 迁移实战案例

    案例:中型企业管理系统迁移

    阶段工作内容周期
    准备 升级 Node.js、安装 Vue3 + Vite 1 天
    依赖 升级组件库(Element Plus)、Pinia 2 天
    代码 迁移 50 个页面、100 个组件 2 周
    测试 功能回归、性能对比 1 周
    上线 灰度发布、监控 3 天
    总计 约 4 周

    迁移中的坑:

  • 组件库不兼容:Element UI → Element Plus,API 有变化;
  • Vuex → Pinia:需要重写状态管理逻辑;
  • 事件总线移除:$on/$off 已移除,需要用 Pinia 替代;
  • 过滤器移除:Vue3 移除了过滤器,需要用 computed 或方法替代。
  • 13.5 迁移后的优化与改进

    迁移到 Vue3 后,可以做这些优化:

    优化说明
    性能优化 利用 Proxy 响应式和编译时优化提升性能
    代码组织 用 Composition API 重构大型组件
    TypeScript 逐步引入 TypeScript,提升类型安全
    开发体验 用 Vite 提升开发效率
    生态升级 用 Pinia 替代 Vuex,用 Nuxt 3 做 SSR

    迁移后的收益:

    • 性能提升 1~2 倍:Proxy 响应式 + 编译时优化;
    • 开发效率提升:Vite 极快启动 + 热更新;
    • 代码质量提升:Composition API + TypeScript;
    • 维护性提升:Vue2 已 EOL,Vue3 持续迭代。

    14. 总结与未来方向

    14.1 Vue3 的核心价值

    维度价值
    性能 Proxy 响应式 + 编译时优化
    代码组织 Composition API 解决大型应用
    TypeScript 原生支持
    生态 Vite + Pinia + Nuxt3

    14.2 未来方向

    方向说明
    Vue 3.5+ 持续改进(Reactive Props Destructure 等)
    Vue Vapor Mode 无虚拟 DOM 的编译时优化
    Nuxt 3 SSR/SSG 生态成熟
    TypeScript 持续增强类型推导

    14.3 最后的话

    Vue3 不是"Vue2 的升级版",而是从头重写的新一代前端框架——Proxy 响应式、Composition API、Vite、Pinia、Nuxt3,每一层都是一次换代。Vue2 已 EOL,Vue3 是事实上的标准——新项目直接选 Vue3,老项目按节奏迁移。

    升级 Vue3 的最大收益不是"性能提升",而是"代码组织"——Composition API 让大型应用的代码不再散落在四个选项里,而是集中在同一功能块。这是 Vue3 最被低估、也是最有价值的改进。

    14.4 学习路线与资源推荐

    学习路线:

    阶段内容周期
    入门 Vue3 基础语法 + Composition API 1~2 周
    进阶 Pinia + Vue Router + Vite 1~2 周
    实战 完整项目开发 2~4 周
    深入 响应式原理 + 源码阅读 持续

    推荐资源:

    资源说明
    Vue 官方文档 https://cn.vuejs.org — 最权威的文档
    Vue 官方博客 https://blog.vuejs.org — 最新版本动态
    Vue Mastery 付费视频课程,质量高
    Vue School 付费视频课程,实战导向
    GitHub awesome-vue 生态资源大全

    生态推荐:

    生态说明
    UI 组件库 Element Plus / Ant Design Vue / Naive UI
    状态管理 Pinia(官方推荐)
    路由 Vue Router 4(官方推荐)
    构建工具 Vite(官方推荐)
    元框架 Nuxt 3(SSR/SSG)
    工具库 VueUse(实用 composable 集合)

    14.5 总结与展望

    Vue3 的核心价值:

  • 性能提升:Proxy 响应式 + 编译时优化,渲染性能提升 1~2 倍;
  • 代码组织:Composition API 解决大型应用的逻辑复用;
  • TypeScript:原生 TS 支持,类型推导更自然;
  • 生态成熟:Vite + Pinia + Nuxt3 组成新一代生态。
  • 未来展望:

    方向说明
    Vue Vapor Mode 无虚拟 DOM 的编译时优化,性能再提升
    Vue 3.6+ 持续改进,更多新特性
    Nuxt 4 Nuxt 4 已发布,基于 Vue 3
    AI 集成 Vue DevTools 集成 AI 辅助开发

    最后的建议:

    • 新项目:直接用 Vue3 + <script setup> + Pinia + Vite;
    • 老项目:按节奏迁移,不要一次性大爆炸;
    • 学习路径:先掌握基础语法,再深入原理和源码;
    • 生态选择:优先用官方推荐的生态(Vite/Pinia/Nuxt)。

    Vue3 不是"Vue2 的升级版",而是"新一代前端框架"——它从响应式原理、代码组织、类型支持、构建工具每一层都做了换代。2026 年的今天,Vue2 已 EOL,Vue3 是事实上的标准——这是你的不二之选。

    14.6 常见问题 FAQ

    #问题解答
    1 Vue3 学习曲线陡吗? 平缓,模板语法和 Vue2 类似,Composition API 是新东西但不难学
    2 Vue2 的项目要马上迁移吗? 不强制,但 Vue2 已 EOL,建议按节奏迁移
    3 Vue3 性能比 Vue2 好多少? 渲染性能提升 1~2 倍,内存占用更低
    4 Vue3 支持 TypeScript 吗? 原生支持,从设计之初就考虑了类型推导
    5 Vue3 能用 Element UI 吗? 用 Element Plus(Vue3 版本),不是 Element UI
    6 Vue3 的构建工具是什么? 推荐 Vite,开发启动极快
    7 Pinia 和 Vuex 哪个好? Pinia 是官方推荐,Vuex 已停止维护
    8 Vue3 支持 SSR 吗? 支持,用 Nuxt 3 做 SSR/SSG

    学习建议:

    • 新手:从官方文档入门,掌握基础语法;
    • Vue2 老司机:重点学 Composition API 和 Pinia;
    • 进阶:深入响应式原理和源码;
    • 实战:做一个完整项目,把生态工具都用一遍。

    修订记录

    版本日期修订说明
    v1.0 2026-09-21 初版发布,覆盖响应式原理、Composition API、Pinia、Vite、Nuxt3、迁移

    本文档基于 2026 年 9 月 Vue 官方资料整理;Vue 3.5 “Tengen Toppa Gurren Lagann” 为当前最新小版本;具体 API 请以官方文档为准。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Vue3完全指南从原理到实战
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!