20251015-Vue3八股文整理

🌸 一、背景:为什么要有 Composition API?

在 Vue2(Options API)时代,组件的逻辑是"按类型分区"的:

  • 数据放 data
  • 计算属性放 computed
  • 方法放 methods
  • 监听器放 watch

这种写法一开始挺整齐,但一旦组件一大、功能一多......

就像厨房把"菜刀区、锅区、碗区"分开,但每道菜都得跑来跑去拿材料,累死厨师 🥵

👉 Vue3 推出了 Composition API,让你能:

"按功能组织代码",每个功能的逻辑都能放在一起,互不打扰。

就像为每道菜配备独立的工作台,炒完就上菜,高内聚、低耦合 🍳


🧩 二、Options API 是怎么写的(Vue2 写法)

图片中第2页展示了这种模式👇

css 复制代码
export default {
  data() {
    return {
      功能A的数据,
      功能B的数据,
    };
  },
  methods: {
    功能A的方法,
    功能B的方法,
  },
  computed: {
    功能A的计算,
    功能B的计算,
  },
  watch: {
    功能B的监听,
  }
}

🧠 问题: 每个功能(比如登录逻辑、计数逻辑)被切得七零八落:

  • A 的数据在 data
  • A 的方法在 methods
  • A 的计算属性在 computed
  • A 的监听又跑到 watch

你想改个功能A,还得在文件里来回跳几十行------就像炒一杯奶茶要跑三层楼取珍珠、糖浆、杯子 😵‍💫

所以,当组件很大时,这种碎片化结构非常难维护。 图中五颜六色的代码块,就是在提醒你:逻辑被"颜色切碎"了。


🧠 三、Composition API 是怎么写的(Vue3 写法)

然后在第5页,你能看到 Composition API 的代码👇

💡 第一步:把相关逻辑封装进一个函数(组合函数)

ini 复制代码
function useCount() {
  let count = ref(10);
  let double = computed(() => count.value * 2);
​
  const handleCount = () => {
    count.value = count.value * 2;
  };
​
  return { count, double, handleCount };
}

🧃通俗解释:

  • ref(10) 就像定义一个能"自动更新"的变量(计数器)。
  • computed 是派生值,比如 "count 的两倍"。
  • handleCount 是一个方法,让 count * 2。
  • 最后 return 把它们全打包"输出"。

就像你把「奶茶计数功能」放进一个盒子里(useCount 工具包)。 以后别的地方要用计数逻辑,直接打开盒子就行~ 🎁


💡 第二步:在组件里使用这个功能

arduino 复制代码
export default defineComponent({
  setup() {
    const { count, double, handleCount } = useCount();
​
    return {
      count,
      double,
      handleCount,
    };
  },
});

🧃通俗解释: 这一步就像在「店铺里启用」你的计数功能盒子。 useCount() 就像拿出之前封好的"计数逻辑包",直接用,不用重新造轮子。


🌈 四、对比总结表(通俗总结)

项目 Options API(Vue2) Composition API(Vue3)
逻辑组织方式 按类型分区(data/methods/computed) 按功能聚合(逻辑函数)
代码位置 分散(上下乱跳) 集中(同一逻辑放一起)
可复用性 差,组件之间难共享逻辑 高,写成组合函数可多处使用
维护体验 像厨房乱放食材 像每道菜有独立工作台
适合场景 小组件 中大型项目

🧋 五、生活记忆法

来个可爱的奶茶店比喻帮你记住:

概念 奶茶店类比 含义
Options API 所有原料按种类分仓:糖放糖区、奶放奶区、茶放茶区。 找料麻烦,逻辑分散。
Composition API 每个奶茶师都有自己的配方区,糖奶茶的所有原料放一起。 高内聚、清晰、复用方便。

👉 所以记住口诀:

"Vue2 分门别类找原料,Vue3 一杯奶茶装一套。" 🍹


🚀 六、快速记忆重点

Composition API:按功能组织代码(高内聚、低耦合) Options API:按选项分散逻辑(低内聚、易碎片化)

🌈 一、逻辑复用的老方法:mixins(Vue2 的时代)

想象你开了一家奶茶店(一个 Vue 组件 🍹), 你发现很多饮品(不同组件)都有相同的功能,比如:

  • 监听顾客按键点单;
  • 显示当前鼠标位置;
  • 处理数量加减。

于是你不想在每个饮品文件里都重复写这些逻辑,就把公共逻辑抽出来成一个 mixins 配方包


🧩 代码解释(第7页)

javascript 复制代码
export const MoveMixin = {
  data() {
    return { x: 0, y: 0 }; // 鼠标坐标初始值
  },
  methods: {
    handleKeyup(e) {
      console.log(e.code);
      // 根据按键方向修改坐标
      switch (e.code) {
        case "ArrowUp":
          this.y--; break;
        case "ArrowDown":
          this.y++; break;
        case "ArrowLeft":
          this.x--; break;
        case "ArrowRight":
          this.x++; break;
      }
    },
  },
  mounted() {
    window.addEventListener("keyup", this.handleKeyup); // 挂载事件监听
  },
  unmounted() {
    window.removeEventListener("keyup", this.handleKeyup); // 销毁时移除监听
  },
};

🧠 通俗解释:

  • 这是一个"键盘监听配方包",能监听你按 ↑ ↓ ← →,然后改变坐标。
  • 用的时候只要 mixins: [MoveMixin],组件立刻拥有这套逻辑。
  • 就像店里所有奶茶都能共享一包"搅拌逻辑"或"加糖逻辑"。

🍵 组件中使用(第8页)

xml 复制代码
<template>
  <div>Mouse position: x {{ x }} / y {{ y }}</div>
</template>
​
<script>
import mousePositionMixin from './mouse'
export default {
  mixins: [mousePositionMixin]
}
</script>

这就是"引入配方包"的过程。 只要引入 mixin,这个组件就能显示坐标信息。


⚠️ 但问题来了!

如果你用了很多 mixin,比如:

ini 复制代码
mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]

就会像这样出大问题:

  1. 命名冲突 各 mixin 都可能定义 data()methods、甚至同名变量, 到底谁覆盖谁?模糊不清。😵 → 就像不同员工都往奶茶里加糖,你根本不知道最后多甜。
  2. 数据来源不清晰 组件里到底 x 是 mixin 提供的?还是自己定义的?难以追踪。 → 像厨房拿到一杯奶茶,不知道是谁做的。

🚀 二、Vue3 的新方式:Composition API 替代 mixin

Vue3 提供了一个更聪明的解决方案: 用 函数 组织逻辑,而不是混入文件。

每个功能写成一个"组合函数(hook)",清晰又可复用。


💡 第一步:写一个组合函数(第9页)

javascript 复制代码
import { onMounted, onUnmounted, reactive } from "vue";
​
export function useMove() {
  const position = reactive({ x: 0, y: 0 }); // 响应式对象,存位置
​
  const handleKeyup = (e) => {
    switch (e.code) {
      case "ArrowUp": position.y--; break;
      case "ArrowDown": position.y++; break;
      case "ArrowLeft": position.x--; break;
      case "ArrowRight": position.x++; break;
    }
  };
​
  onMounted(() => {
    window.addEventListener("keyup", handleKeyup); // 监听键盘
  });
​
  onUnmounted(() => {
    window.removeEventListener("keyup", handleKeyup); // 组件卸载时清除
  });
​
  return { position }; // 返回数据给组件使用
}

🧠 小白版解释:

  • useMove() 就像"自定义逻辑盒子"。
  • 里面封装了坐标 + 按键监听。
  • reactive() 让坐标能自动响应变化。
  • onMountedonUnmounted 相当于"开店时装上摄像头 / 关店时拔掉摄像头"。

💡 第二步:在组件里使用(第10页)

xml 复制代码
<template>
  <div>Mouse position: x {{ x }} / y {{ y }}</div>
</template>
​
<script>
import { useMove } from "./useMove";
import { toRefs } from "vue";
​
export default {
  setup() {
    const { position } = useMove();
    const { x, y } = toRefs(position); // 把响应式对象解包成变量
    return { x, y };
  },
};
</script>

✨ 通俗理解:

  • 这里的 useMove() 就像你调用"位置追踪模块"。
  • toRefs() 就是把 position.xposition.y 拆成独立变量,方便模板使用。
  • 整体逻辑一目了然、数据来源清晰、不会命名冲突。

🧋 三、对比记忆(mixin vs Composition API)

对比点 mixin(旧时代) Composition API(新时代)
组织形式 对象混入 函数组合
数据来源 不清晰(命名冲突) 清晰(每个函数独立)
可读性
可复用性 一般 极强
类比 各店员工共用一桶糖浆,容易搞混 每个员工有独立糖包,随取随用

🧠 四、小结(第10页总结)

  • Composition API 在逻辑组织和复用上完胜 mixin。
  • 函数化,类型推断更好。
  • 对 tree-shaking 友好(没用的逻辑会被打包器自动删)。
  • 没有 this 的困扰,干净简单。
  • 小型组件仍然能继续用 Options API。

🍰 生活口诀记忆法:

Vue2:mixins 混在一起,像多人共煮一锅粥; Vue3:Composition API 像每人有独立便当盒,干净又清楚!🍱

🧠 一、Vue3 的设计目标(第11页)

Vue3 的目标可以总结为四个词:

更小、更快、更友好、可优化

来,我们把它翻译成人话👇

目标 通俗解释 奶茶店比喻
更小 减少打包体积 用轻型机器代替笨重设备,节省空间
更快 性能优化 冲一杯奶茶更快出杯(渲染速度更快)
更友好 开发体验更顺滑 界面更整洁,操作逻辑更清晰
优化方案 结构更科学 厨房布局改进,员工配合更顺畅

🧃 二、更小(第12页)

Vue3 移除了不常用的 API,并引入了 Tree-shaking 技术。

📘 Tree-shaking 是什么?

它的意思是:"把你没用的代码都摇掉,只保留真正用到的部分。"

比如你引入了 Vue,但只用到了 refreactive, 打包时其他没用到的部分会自动删掉,结果体积更小~

🍵 类比:

就像点奶茶时说"我只要珍珠不要椰果", 店员就不会给你装满整杯料,只保留你要的。


⚡ 三、更快(第12页)

Vue3 的"快"体现在 4 个方面:

优化点 说明 比喻
diff 算法优化 比对新旧节点更聪明,少做无用更新 服务员记住老顾客,不每次都重问"加糖吗?"
静态提升 静态内容只生成一次 固定菜单不每次都重写
事件侦听缓存 事件绑定更高效 重复动作直接走缓存
SSR 优化 服务端渲染速度更快 外卖奶茶打包更快送达

🤝 四、更友好(第12页)

Vue3 兼容旧的 Options API,同时引入新的 Composition API, 让代码逻辑更清晰、可复用性更强。

💡 例子:获取鼠标位置

ini 复制代码
import { toRefs, reactive, onMounted, onUnmounted } from 'vue';
​
function useMouse() {
  const state = reactive({ x: 0, y: 0 });
​
  const update = e => {
    state.x = e.pageX;
    state.y = e.pageY;
  };
​
  onMounted(() => window.addEventListener('mousemove', update));
  onUnmounted(() => window.removeEventListener('mousemove', update));
​
  return toRefs(state);
}

🧠 解释:

  • reactive():让对象变成响应式。
  • onMounted():组件挂载时监听鼠标。
  • onUnmounted():组件销毁时取消监听。
  • toRefs():把响应式对象里的属性拿出来用。

🍵 比喻:

以前每个饮品师傅都要自己写"记录顾客坐标"的逻辑(重复造轮子), 现在 Vue3 直接提供一个函数 useMouse(),随拿随用,省心省力。


🛠 五、优化方案(第13页)

Vue3 的优化分三层:

分类 解释
源码 重构结构,分模块管理
性能 更快的运行与渲染
语法 API 更干净、更易读的代码

🧩 源码管理(第14页)

Vue3 把整个代码库拆成多个模块(monorepo 结构):

vbnet 复制代码
packages/
├── compiler-core
├── compiler-dom
├── compiler-sfc
├── reactivity
├── runtime-core
├── runtime-dom
├── shared
└── vue

每个模块都负责一类功能,比如:

  • reactivity 管响应式;
  • compiler-core 管模板编译;
  • runtime-dom 管渲染 DOM。

💡 好处:

  • 更清晰、好维护;
  • 可以单独使用某个模块(比如只要响应式系统,不要整套 Vue)。

🍵 比喻:

就像奶茶工厂现在把各部门独立出来: 调奶组、封杯组、出单组......谁坏了不影响别人。


💻 TypeScript 支持(第14页)

Vue3 全部用 TypeScript 重写。

🧠 意思是:

代码更安全,错误更早发现,还能自动补全提示。

🍵 比喻:

就像奶茶机加了智能检测系统,一旦加错糖立刻报警。


🚀 六、性能优化(第15页)

Vue3 用 Proxy 替代了 Vue2 的 Object.defineProperty 来实现响应式。


Vue2 的写法(旧的)

javascript 复制代码
Object.defineProperty(data, 'a', {
  get() { /* track */ },
  set() { /* trigger */ }
});

⚠️ 缺点:

  • 不能检测对象的新增/删除;
  • 嵌套对象监听麻烦;
  • 性能开销大。

Vue3 的写法(新的)

Vue3 用 Proxy 直接监听整个对象:

css 复制代码
const data = {
  a: {
    b: {
      c: { d: 1 }
    }
  }
};

💡 Proxy 可以监听到任何层级的变化,也能监听新增、删除属性。 Vue3 的监听方式是在 getter 中递归,只有访问到的属性才会被监听,不浪费性能。

🍵 比喻:

Vue2:每次都要手动在每个房间装摄像头。 Vue3:在大门口装一个智能安防系统,自动识别里面谁动了。


✅ 七、总结一句话

Vue3 就像升级版的智能奶茶工厂: 🍶 体积更小(去掉冗余设备) ⚙️ 出杯更快(优化流水线) 🧠 员工更聪明(Composition API + TS) 🧩 管理更清晰(模块化) 💬 客户体验更好(开发更顺畅)

Modal 就是"弹出层",常见场景有:

  • 点"新增""编辑"按钮时,弹出一个输入框;
  • 点击"确认删除",弹出"确定要删除吗?"对话框。

通俗讲:

Modal 就像奶茶店里的"透明点餐窗口"🍹------ 顾客点单时出现(弹出),点完关掉(消失),主界面依然在后面静静等着。


🧩 二、设计思路(第18页)

图片里画了三块思维导图:

Vue3 实现 Modal 组件的三步走:

  1. 组件设计
  2. 需求分析
  3. 实现流程

🧠 三、组件设计(3.1)

组件的本质:

把重复的、可抽象的功能打包成一个独立的"积木", 以后任何地方都能复用。

🍵 举例: Modal 就像"可重复使用的弹窗积木", 不论是「新增商品」还是「编辑商品」弹窗,只是里面内容不同,骨架是一样的。


📋 四、需求分析(3.2)

我们要做的 Modal 至少要有这些元素:

功能 说明
遮罩层 背景半透明,防止用户误操作
标题内容 如"编辑资料"、"删除确认"等
主体内容 表单、文字提示等
确定/取消按钮 用来执行操作或关闭弹窗

💡 Modal 一般要挂在 HTML 的 body 外层(因为是全局弹窗), Vue3 中可以用 Teleport 实现这个"传送"功能。


🧱 五、实现流程(3.3)

1️⃣ 目录结构(第19页)

arduino 复制代码
plugins/
└── modal/
    ├── Content.tsx       // Modal 内容区(支持 JSX)
    ├── Modal.vue         // 核心组件
    ├── config.ts         // 全局配置
    ├── index.ts          // 入口文件
    ├── locale/           // 国际化相关
    │   ├── index.ts
    │   └── lang/
    │       ├── en-US.ts
    │       ├── zh-CN.ts
    │       └── zh-TW.ts
    └── modal.type.ts     // TypeScript 类型定义

🧠 讲人话:

  • 整个 Modal 是一个插件(放在 plugins/modal 里);
  • 国际化语言文件说明它支持中英文;
  • modal.type.ts 定义类型(让代码更聪明、更安全)。

2️⃣ 组件内容(第20页)

看代码👇

ini 复制代码
<Teleport to="body" :disabled="!isTeleport">
  <div v-if="modeValue" class="modal">
    <div class="mask" @click="maskClose && handleCancel()"></div>
​
    <div class="modal_main">
      <div class="modal_title">
        <span>{{ title || t("r.title") }}</span>
        <span v-if="close" @click="handleCancel()">×</span>
      </div>
​
      <div class="modal_content">
        <slot v-if="typeof content !== 'function'">{{ content }}</slot>
        <Content v-else :render="content" />
      </div>
​
      <div class="modal_btns">
        <button @click="handleConfirm">{{ t("r.confirm") }}</button>
        <button @click="handleCancel">{{ t("r.cancel") }}</button>
      </div>
    </div>
  </div>
</Teleport>

🍬 解释:

🪄 <Teleport>

这是 Vue3 的黑科技。 可以让组件"瞬移"到页面其他地方,比如 body 标签下。

就像"弹窗"被传送到舞台最前面,不会被别的元素挡住。

🌫️ <div class="mask">

遮罩层,点击空白处可关闭弹窗。

就像顾客离开点单窗口,透明玻璃慢慢合上。

🏠 .modal_main

弹窗的主体部分。 里面包含:

  • 标题栏(modal_title
  • 内容区(modal_content
  • 底部按钮(modal_btns

🧾 <slot>

用于插槽,表示这里放"自定义内容"。

开发者可以把任意 HTML 或 Vue 组件"塞进来", Modal 只是负责框架,内容你随意填。


📦 六、总结整段代码逻辑:

  1. Teleport 把弹窗移到 body 层;
  2. 遮罩层(mask)点击可关闭;
  3. 标题栏显示弹窗标题;
  4. 内容区通过插槽渲染;
  5. 按钮区提供"确认 / 取消"操作;
  6. 国际化 支持多语言(通过 t("r.confirm") 等函数实现)。

🍵 七、生活类比总结

想象你在一个大型奶茶店系统中:

  • 顾客点击"下单" → 弹出一个"确认下单窗口"(Modal)
  • 背景变灰(mask 遮罩)
  • 弹窗前台有标题(title)
  • 中间是订单详情(content)
  • 底部有"确定下单 / 取消"按钮(confirm/cancel)

而整个弹窗并不是盖在订单区域上,而是瞬移(Teleport) 到最上层展示。

所以 Modal 的逻辑就像一套「可复用的点单窗口系统」, 每次用的"菜不一样",但窗口和按钮都是共通的。🍰


🧠 八、核心记忆口诀

「Vue3 Modal 三步走」 🏗️ 设计结构(组件思维) 🧩 分析需求(遮罩+标题+内容+按钮) ⚙️ 组合实现(Teleport + slot + 事件)

一句话总结:

Modal 是传送门弹窗,用 Teleport 实现全局显示,用 slot 装载自定义内容。

🧩 一、Modal 内容显示方式(第21页)

xml 复制代码
<div class="modal__content">
  <Content v-if="typeof content === 'function'" :render="content" />
  <slot v-else>{{ content }}</slot>
</div>

🧠 这段代码的意思是:

  • 如果 content 是函数,就执行它(动态渲染)
  • 如果不是,就直接显示字符串或插槽内容

💬 举例说明:

就像奶茶店的"自定义奶茶":

  • 你传进来字符串 "hello world",那就是写死的文字。
  • 你传进来函数 () => <div>动态内容</div>,那就是"现做"的内容。

🧁 两种使用方式:

✅ 方式1:默认插槽

xml 复制代码
<Modal v-model="show" title="演示 slot">
  <div>hello world~</div>
</Modal>

→ 写在 <Modal> 里面的内容直接显示。

✅ 方式2:字符串模式

ini 复制代码
<Modal v-model="show" title="演示 content" content="hello world~" />

→ 传字符串作为内容。


🍰 二、通过 API 动态调用 Modal(第21-22页)

Vue3 可以不写 <Modal> 标签,而是直接用代码调用弹窗


✨ 方式1:函数式调用(h函数)

javascript 复制代码
$modal.show({
  title: '演示 h 函数',
  content(h) {
    return h('div',
      { style: 'color:red;', onClick: e => console.log('clicked', e.target) },
      'hello world~'
    )
  }
})

🧠 h() 是 Vue 的虚拟节点函数,用代码写出"要渲染的 DOM"。

💡 类比:

就像你打电话点奶茶:"给我来一杯红茶,红色杯子,点一下冒泡。" Vue 就在内存里造好那杯茶,然后"传送"上屏幕。


✨ 方式2:JSX 写法(第22页)

javascript 复制代码
$modal.show({
  title: '演示 JSX 语法',
  content() {
    return (
      <div onClick={e => console.log('clicked', e.target)}>
        hello world~
      </div>
    )
  }
})

JSX 写法更简洁(直接用 HTML-like 语法), 可以像 React 一样写组件内容。

🍵 类比:

h函数 是"打电话点单"; JSX 是"自己拿菜单点"; 两种方式都能做奶茶,只是下单方式不同。


⚙️ 三、实现 API 调用形式(第22-23页)

Vue2 的做法(老版)

ini 复制代码
import Modal from './Modal.vue';
const ComponentClass = Vue.extend(Modal);
const instance = new ComponentClass({ el: document.createElement("div") });
document.body.appendChild(instance.$el);

🧠 含义:

  1. 通过 Vue.extend() 生成一个组件类;
  2. 创建实例;
  3. 挂载到 body 上。

Vue3 的改进版

Vue3 没有 Vue.extend 了,改用虚拟节点(VNode):

ini 复制代码
import Modal from './Modal.vue';
const container = document.createElement('div');
const vnode = createVNode(Modal);
render(vnode, container);
const instance = vnode.component;
document.body.appendChild(container);

💬 翻译成人话:

Vue3 先创建一个"虚拟盒子", 再渲染 Modal 进去,最后把盒子塞进 body。

🍵 比喻:

像 Vue2 是"手搓奶茶杯",Vue3 是"用模具直接生产杯子", 更高效,也能批量操作。


🧠 四、全局挂载(第23页)

在 Vue2,我们常这样写:

javascript 复制代码
export default {
  install(vue) {
    vue.prototype.$create = create
  }
}

→ 就是往全局 Vue 原型上挂一个 $create 方法。 你可以在任何组件中用 this.$create()


Vue3 没有 this 了,所以换成:

javascript 复制代码
export default {
  install(app) {
    app.config.globalProperties.$create = create
  }
}

💡 通俗解释:

"以前每个员工(组件)都能喊 this.$create, 现在 Vue3 把权限放到 app.config.globalProperties 这个总部注册表里。"

🍵 类比:

就像 Vue3 把"奶茶配方表"放进公司总部, 每个分店都能用总部统一的 $create


🎮 五、事件处理(第23-25页)

Vue3 的 Modal 里要处理两种事件:

  • ✅ 确定(confirm)
  • ❌ 取消(cancel)

Modal.vue 内部逻辑(第24页)

javascript 复制代码
setup(props, ctx) {
  let instance = getCurrentInstance(); // 获取当前组件实例
​
  onBeforeMount(() => {
    instance._hub = {
      'on-cancel': () => {},
      'on-confirm': () => {}
    }
  });
​
  const handleConfirm = () => {
    ctx.emit('on-confirm');
    instance._hub['on-confirm'](); // 触发外部监听
  };
​
  const handleCancel = () => {
    ctx.emit('on-cancel');
    ctx.emit('update:modelValue', false);
    instance._hub['on-cancel']();
  };
​
  return { handleConfirm, handleCancel };
}

💬 翻译成人话:

  • _hub 就像一个"事件中转站"。
  • 当点击"确定/取消"时, 同时会通知内部自己 + 外部监听的逻辑。

🍵 类比:

就像奶茶机上有个"按钮总控台" _hub, 你按"确定",机器不止执行自己逻辑,还能通知外部系统(比如打印机)。


注册全局事件(第25页)

ini 复制代码
app.config.globalProperties.$modal = {
  show({ onConfirm, onCancel }) {
    const { props, _hub } = instance;
​
    const _closeModal = () => {
      props.modelValue = false;
      container.parentNode.removeChild(container);
    };
​
    Object.assign(_hub, {
      async 'on-confirm'() {
        if (onConfirm) {
          const fn = onConfirm();
          if (fn && fn.then) { // 如果是异步Promise
            try {
              props.loading = true;
              await fn;
              props.loading = false;
              _closeModal();
            } catch (err) {
              console.error(err);
              props.loading = false;
            }
          } else _closeModal();
        }
      },
      'on-cancel'() {
        onCancel && onCancel();
        _closeModal();
      }
    });
  }
}

💡 讲人话解释:

  • 当点击"确定"时,会调用传入的 onConfirm()
  • 如果返回 Promise(比如异步提交表单),就等执行完再关闭;
  • 出错时不关闭,并打印错误;
  • 点击"取消"就立刻关闭。

🍵 类比:

你点"确认下单",系统要先验证库存(异步), 有货 → 关闭窗口; 没货 → 提示"下单失败"并保留弹窗。


🧠 六、总结口诀记忆

模块 功能 比喻
Teleport 把弹窗传送到 body 顶层 "舞台前方灯光聚焦"
slot/content 填充内容 "窗口里放自定义配料"
h / JSX 动态内容渲染 "电话下单 vs 自助下单"
createVNode 生成实例 "模具成型生产组件"
globalProperties 注册全局方法 "总部配置表"
_hub 事件中转中心 "操作台总控"

🎯 一句话总结:

Vue3 实现 Modal: 用 Teleport 弹出 → 用 API 创建 → 用 globalProperties 注册 → 用 _hub 管事件。

通俗点说: "Vue3 弹窗就像总部统一管理的智能点单系统, 你点'确定',它能自动执行回调、支持异步、还能优雅地关闭窗口。" 🍵

🧩 一、Vue3 性能提升的三大方向

图中写了三点👇

编译阶段、源码体积、响应式系统

咱先聚焦第一个:编译阶段优化(compile time optimization) 。 这部分是 Vue3 的"隐形提速神器"。 🚀


🍬 4.1 编译阶段优化

🧠 背景回顾

在 Vue2 中,每个组件都有一个 watcher(观察者) , 当数据变化时就会触发 watcher 让组件重新渲染。

🧩 举例:

假如你家有个"监控系统(watcher)", 它监视着你冰箱(data)里的牛奶量(属性)。 每次你倒掉一点(setter),它就提醒你"要补货咯~"然后更新显示。


🍰 4.1.1 diff 算法优化(重点)

Vue3 相比 Vue2,给 diff 算法加了"静态标记 flag"⚑

💬 你可以理解为------

Vue3 给那些不会变化的地方打上标签, 这样下次对比(diff)的时候就跳过它,不再浪费性能。


🍭 举个例子(第28页):

css 复制代码
<div>
  <p>HelloWorld</p>
  <p>{{ msg }}</p>
</div>

上面第二个 <p> 里绑定了变量 msg,只有它是动态内容。

在 Vue3 的 diff 过程中,编译器会自动标记:

css 复制代码
p.textmsg flag: 1

意思是:"这个节点可能变,其他节点不用管。"

🔖 所以,下一次更新页面时,Vue 只重新渲染绑定 msg 的那一个 <p>

💡 类比:

就像快递分拣时贴标签:

  • 红色贴纸:要更新的包裹
  • 灰色贴纸:静态的不用动 工人就不会一遍遍重复检查所有包裹。

🧱 PatchFlags(第28页代码块)

ini 复制代码
export const enum PatchFlags {
  TEXT = 1, // 动态文本
  CLASS = 1 << 1, // 动态 class
  STYLE = 1 << 2, // 动态样式
  PROPS = 1 << 3, // 动态属性
  ...
  HOISTED = -1, // 静态节点,永远不 diff
}

🧠 记忆法:

TEXT = 1,CLASS = 2,STYLE = 4...... 就像超市商品有不同"条形码",Vue3 用数字代表不同类型的变化。


💎 4.1.2 静态提升(Static Hoisting)

Vue3 会把不参与更新的内容提前缓存起来,只创建一次


看例子(第29页):

css 复制代码
<span>你好</span>
<div>{{ message }}</div>

🪫 没有静态提升前:

kotlin 复制代码
return _createVNode("span", null, "你好"),
       _createVNode("div", null, _toDisplayString(_ctx.message))

→ 每次渲染都要重新创建 "你好" 这个节点。


⚡ 静态提升后:

kotlin 复制代码
const _hoisted_1 = _createVNode("span", null, "你好")
return _hoisted_1, _createVNode("div", null, _toDisplayString(_ctx.message))

→ "你好" 这个部分被提取出来,只创建一次,后续直接复用。

💡 类比:

第一次做奶茶时(渲染)要洗杯子(创建节点), Vue3 记住了这杯"静态奶茶",下次直接复用杯子,不用再洗。


🧩 _hoisted_1 标记解释:

  • _hoisted_1:表示被提升的静态节点;
  • PatchFlag = -1:说明这部分永远不会参与 diff

📌 记忆口诀:

"_hoisted 一次做,PatchFlag 负一不再动。"


🪄 4.1.3 事件监听缓存(Event Handler Caching)

看例子(第30页):

xml 复制代码
<div>
  <button @click="onClick">点我</button>
</div>

🧩 没有事件缓存时:

php 复制代码
_createVNode("button", { onClick: _ctx.onClick }, "点我")

每次渲染都要重新创建一个新的函数引用,性能浪费。


⚡ 开启事件缓存后:

javascript 复制代码
_createVNode("button", {
  onClick: _cache[1] || (_cache[1] = (...args) => _ctx.onClick(...args))
}, "点我")

→ Vue3 会把事件处理函数缓存在 _cache 里, 后面直接拿缓存的版本用。

💬 类比:

就像点外卖时,你第一次填了地址(函数引用), Vue3 会帮你保存,下次直接点"复用上次地址",更快更省。


🧠 4.1.4 SSR 优化(服务端渲染优化)

SSR(Server-Side Rendering)时,Vue3 遇到静态内容会直接生成 HTML

css 复制代码
createStaticVNode('<div>你好</div>', 1)

→ 不需要再在浏览器端重新创建节点。 节省了运行时内存,也减少首屏渲染时间。

🍵 类比:

Vue2 是"到现场才摆盘"; Vue3 是"厨房预装好端出来",上菜更快。


🎯 总结口诀(小白速记)

优化点 作用 生活类比
diff 静态标记 跳过不变的内容 "贴标签跳过包裹"
静态提升 缓存静态节点 "复用旧杯子不洗杯"
事件缓存 保存函数引用 "外卖地址记一次复用"
SSR 优化 提前生成静态 HTML "厨房预制菜端上桌"

💡 一句话总结:

Vue3 就像一个超级聪明的员工: 不重复劳动、不重新绑定、不多余监听, 把静态的都提前做好,动态的精准更新。

✨ "只动该动的地方"------这就是 Vue3 快的秘密。 🚀

🧩 一、4.2 源码体积优化(第31页)

✨ Vue3 更轻的原因

Vue3 的源码比 Vue2 小,是因为做了两件事:

  1. 移除了部分不常用 API(轻装上阵)
  2. 使用 Tree Shaking 技术(只打包你用到的部分)

🍬 举个例子

比如你点奶茶:

Vue2:不管你要什么,先把珍珠、布丁、红豆、仙草全装上。 Vue3:只加你点的那几种料,没点的不打包。

这就是 Tree Shaking(摇树优化) 。 打包时只留下用到的 refreactivecomputed, 没用到的模块直接丢掉。


📘 编译后示例代码:

javascript 复制代码
import { mergeProps } from "vue";
import { ssrRenderAttrs, ssrInterpolate } from "vue/server-renderer";
​
export function ssrRender(_ctx, _push) {
  const _cssVars = { style: { color: _ctx.color } };
  _push(`<div style="${_cssVars.style.color}">你好 ${_ctx.message}</div>`);
}

💡 翻译:

Vue3 的编译结果更干净、更短, 生成的渲染函数直接输出字符串,减少中间层处理。


⚙️ 二、4.3 响应式系统(第32页)

这是 Vue 的灵魂部分 ✨ 它能让数据变,界面自动更新。


Vue2 的做法:Object.defineProperty()

Vue2 用"逐个绑 getter / setter"的方式,让数据变动能被监听到。

比如👇

javascript 复制代码
Object.defineProperty(obj, "foo", {
  get() { return val },
  set(newVal) {
    val = newVal
    updateUI()
  }
})

💬 讲人话:

Vue2 像个"保姆",一个个去盯家里每个碗(属性)。 碗多了(对象层级深),保姆就忙不过来了。


Vue3 的做法:Proxy

Vue3 直接包住整个对象:

php 复制代码
const state = reactive({
  name: '小可爱',
  age: 18
})

💡 Proxy 可以监听整个对象,不用一个个绑 getter/setter。

🍵 比喻:

Vue3 像装了"全屋监控摄像头", 不论你加碗、删碗、换碗,它都能立刻看到。


✅ Proxy 的优势:

Vue2 (defineProperty) Vue3 (Proxy)
只能监听已有属性 能监听新增、删除属性
数组变化检测困难 数组索引、length 都能监听
需要深层递归 一层 Proxy 即可监听全部
不支持 Map、Set Proxy 支持各种数据结构

🧠 三、为什么 Vue3 要用 Proxy(第33页)

Vue3 把响应式"重写", 原因很简单:defineProperty 太笨重了

🔍 Object.defineProperty() 工作原理:

当访问一个属性时,会触发:

  • get:读取时调用
  • set:修改时调用

🧩 代码演示(第34页)

javascript 复制代码
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`get ${key}: ${val}`);
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal;
        update();
      }
    }
  });
}
​
function update() {
  app.innerText = obj.foo;
}

💡 讲人话:

每次你访问 obj.foo,都会执行 get(); 每次你改 obj.foo = 100,都会触发 set() 更新视图。


🧩 自动监听(observe)

当对象有多个属性时,就得"遍历"绑定:

ini 复制代码
function observe(obj) {
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
  });
}

🍵 比喻:

就像你要在家里每个房间都装摄像头, Vue2 得一个个去装,很慢很累。


🧱 四、defineProperty 的缺陷(第35页)

arduino 复制代码
const obj = { foo: 'foo', bar: 'bar' }
observe(obj)
​
delete obj.foo  // ❌ 不会触发
obj.jar = 'xxx' // ❌ 不会触发

💣 问题:

  1. 删除属性无法监听
  2. 新增属性无法监听
  3. 数组变化(如 push/pop)也难检测
  4. 递归层级太深,性能差

💡 Vue3 的 Proxy 完美解决:

arduino 复制代码
const state = reactive({ name: '小可爱' })
delete state.name   // ✅ 响应式生效
state.age = 20      // ✅ 响应式生效

Proxy 就像一个万能"拦截器", 可以拦截所有对象操作:getsetdeletePropertyhas 等。

🍵 类比:

Vue2 是"保姆挨个盯碗", Vue3 是"智能监控全屋", 无论你加家具、换门锁、搬家它都知道!📹


🎯 五、总结对比记忆口诀

对比点 Vue2(defineProperty) Vue3(Proxy)
实现方式 遍历属性添加 getter/setter 整体代理对象
深层监听 需要递归 自动监听
数组支持 不完善 支持索引和 length
新增/删除属性 不支持 支持
性能 慢,递归多 快,统一入口

🧠 小白速记口诀:

Vue2 是"逐个绑",Vue3 是"一锅端"。 defineProperty 是"保姆式监听", Proxy 是"智能摄像头"。


💬 一句话总结:

Vue3 用 Proxy 让"响应式"更轻、更快、更聪明, 不管对象多复杂,都能优雅地监听变化~

🌿 就像从"手工点单"升级到"无人奶茶机"🍹------ 一切变化 Vue 都能自动捕捉、自动更新。

🧩 一、5.1.2 defineProperty 的问题(第36页)

在 Vue2 时代,Vue 用 Object.defineProperty 给每个属性加监听。 但它有几个大坑 💣:

⚠️ 主要问题

scss 复制代码
const arrData = [1,2,3,4,5]
arrData.forEach((val, index) => {
  defineProperty(arrData, index, val)
})
​
arrData.push(6)  // ❌ 不触发响应
arrData.pop()    // ❌ 不触发响应
arrData[0] = 99  // ✅ 触发响应

🧠 翻译成生活例子:

Vue2 就像请了个"保姆", 她只能盯着家里现有的五个房间(下标0~4), 如果你新建了第6个房间(push),她完全不知道。😅


🔧 Vue2 的临时修补方案:

它后来给 Vue 增加了:

scss 复制代码
Vue.set()  
Vue.delete()

来手动通知变化。

💡 比喻:

保姆盯不过来,就让你主动喊她一声:"喂,我加了个房间,快来打扫一下!"


🪄 二、5.2 Proxy 实现原理(第37页)

Vue3 换成了 Proxy, 相当于"装了全屋智能摄像头 📷", 任何操作(取值、设置、删除)都能自动被捕捉。


✅ 简化版实现

javascript 复制代码
function reactive(obj) {
  if (typeof obj !== 'object' || obj === null) return obj
​
  const observed = new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key)
      console.log(`获取 ${key}: ${res}`)
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      console.log(`设置 ${key}: ${value}`)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log(`删除 ${key}`)
      return res
    }
  })
  return observed
}

🔍 测试代码

arduino 复制代码
const state = reactive({ foo: 'bar' })
​
state.foo          // 获取 foo
state.foo = 'new'  // 设置 foo
delete state.foo   // 删除 foo

💬 翻译成生活例子:

Proxy 就像安装了一套"语音助手 + 监控": 你一动碗(修改数据),它立刻说"检测到你动了碗!"


⚙️ 三、监听嵌套对象(第38页)

问题:Vue3 要是监听嵌套结构,比如:

ini 复制代码
const state = reactive({
  bar: { a: 1 }
})
state.bar.a = 10  // ❌ 不触发

需要在 get 时,递归再包一层 Proxy


✅ 解决方案:

javascript 复制代码
function reactive(obj) {
  if (typeof obj !== 'object' || obj === null) return obj
​
  const observed = new Proxy(obj, {
    get(target, key) {
      const res = Reflect.get(target, key)
      return typeof res === 'object' ? reactive(res) : res
    }
  })
  return observed
}

💡 类比:

Proxy 原本只能看到客厅里的动静; 现在加了"子摄像头",连卧室、厨房、地下室的变化都能监控到了!📹


💎 四、Proxy vs defineProperty 对比总结(第38页)

特性 Vue2 defineProperty Vue3 Proxy
监听方式 遍历属性绑定 getter/setter 一次性代理整个对象
嵌套对象 需递归多层绑定 动态递归自动代理
数组支持 不完善,需重写方法 原生支持(push/splice等)
新增/删除属性 无法监听 自动监听
性能 慢,递归多 快,代理一层搞定

🧠 一句话记忆:

Vue2 是"保姆挨个盯碗", Vue3 是"摄像头一次盯全屋"。


🧠 五、数组的监听(第39页)

Vue2 监听数组时非常麻烦,要重写所有数组 API

javascript 复制代码
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
  arrayProto[method] = function() {
    originalProto[method].apply(this, arguments)
    dep.notice()
  }
})

🍵 类比:

Vue2 在厨房门口贴了"摄像头", 但对每个操作(push、pop、shift)都得加单独的感应器,太费事!


而 Vue3 的 Proxy:

scss 复制代码
const obj = [1, 2, 3]
const proxObj = reactive(obj)
proxObj.push(4)  // ✅ 自动触发

Proxy 自带对数组方法的监听能力,不用手动重写!💪


✅ Proxy 还能监听更多行为:

apply、ownKeys、deleteProperty、has... 这些都是 defineProperty 做不到的!


🌿 六、6. Tree Shaking(第40页)

这是 Vue3 提升性能的另一个"瘦身神器"。


🧠 Tree Shaking 是什么?

Tree Shaking = "摇掉没用的代码" 🍂

本质是删除没有被使用的函数或变量 。 专业名字叫 Dead Code Elimination


🍵 举例说明:

javascript 复制代码
// utils.js
export function add(a, b) { return a + b }
export function sub(a, b) { return a - b }
​
// main.js
import { add } from './utils.js'
console.log(add(3, 5))

👉 打包后,只会留下 add()sub() 因为没用到,会被"摇"掉,不进包。🎒


💡 类比:

Tree Shaking 就像奶茶店配料员: 你点了"红豆奶茶",他只加红豆,没点珍珠就不放。 打包文件更小,加载更快。


🧠 七、Proxy + Tree Shaking = Vue3 提速核心

✨ Vue3 的聪明做法:

  1. Proxy 让数据变化更快、监听更精准。
  2. Tree Shaking 让打包体积更小、加载更轻。

🪄 小白速记口诀表

概念 通俗理解 生活比喻
defineProperty 旧式保姆挨个看房间 "手动巡逻"
Proxy 一次性智能监控全屋 "摄像头自动追踪"
数组监听 Proxy原生支持 "全自动识别操作"
Tree Shaking 删除没用代码 "红豆奶茶不加珍珠"

🎯 一句话总结:

Vue3 就像一间智能奶茶店 🍹: Proxy 是自动监控系统,能感知所有变化; Tree Shaking 是瘦身助手,只打包你需要的料。 结果就是------"更轻、更快、更聪明"。✨

🪴 一、什么是 Tree Shaking(第 41 页)

Tree Shaking(摇树优化) 是一种:

"把没用的代码从打包结果中摇掉"的优化技术。

📖 学名叫:

Dead Code Elimination(无用代码消除)


🍳 举个通俗的例子(原文的"鸡蛋比喻"):

你要做一个鸡蛋糕:

  • 传统做法(Vue2) : 把鸡蛋壳、蛋白、蛋黄、空气全丢进烤箱, 烤完后再挑出没用的部分(浪费又费时💨)。
  • Tree Shaking(Vue3)做法: 先筛选出要用的蛋黄(有用的 import 模块), 再放烤箱,一开始就"少负担上阵",轻盈又快。🥚✨

📦 简单理解:

javascript 复制代码
// 传统打包(Vue2)会全都塞进来:
import Vue from "vue"
Vue.nextTick(() => {}) // 即使你没用,也被打包!
​
// Vue3 的 Tree Shaking 打包:
import { nextTick, observable } from "vue"
nextTick(() => {}) // ✅ 只打包用到的模块!

💡 Vue3 把所有 API 拆开成独立模块(按需引入), 没用到的模块,不会被打包


🧩 二、Tree Shaking 是怎么做到的?(第 41~42 页)

Tree Shaking 依赖 ES6 模块机制(import / export) 。 它在编译阶段就能知道哪些模块被用、哪些没被用。

🧠 它做了两件事:

1️⃣ 编译器先判断模块之间的依赖关系 2️⃣ 再把没用到的模块删掉


🪄 举个例子

lua 复制代码
vue create vue-demo

安装 Vue2 和 Vue3 的项目来对比:


🍵 三、Vue2 项目打包效果(第 42 页)

javascript 复制代码
export default {
  data: () => ({
    count: 1,
  }),
}

🔹 打包体积:

makefile 复制代码
chunk-vendors.js: 89.59 KiB
app.js: 2.07 KiB

当我们在组件里加上 computedwatch

javascript 复制代码
export default {
  data: () => ({
    count: 1,
    question: '',
  }),
  computed: {
    double() {
      return this.count * 2;
    }
  },
  watch: {
    question() {
      this.answer = 'xxxx';
    }
  }
}

打包结果竟然 没变 😮!

📦 原因是:

Vue2 的 API 都是集中在一个 Vue 实例里, 无论你用不用,都会被打包进去


💡 比喻:

就像你点了一杯奶茶 🍹, 她把珍珠、椰果、布丁全给你放进去了。 你可能只要红豆,但她说:"不好意思,这是全家桶套餐!" 😂


☘️ 四、Vue3 项目打包效果(第 43 页)

✅ Vue3 组件代码

javascript 复制代码
import { reactive, defineComponent } from "vue";
​
export default defineComponent({
  setup() {
    const state = reactive({ count: 1 });
    return { state };
  },
});

🔹 打包结果:

makefile 复制代码
chunk-vendors.js: 78.91 KiB
app.js: 1.92 KiB

👉 比 Vue2 少了整整 10KB+

因为 Vue3 的模块是按需引入的。 没用的部分(比如 computed / watch)完全没进包!


🌸 五、加上 computed 和 watch 再试(第 44 页)

javascript 复制代码
import { reactive, defineComponent, computed, watch } from "vue";
​
export default defineComponent({
  setup() {
    const state = reactive({ count: 1 });
    const double = computed(() => state.count * 2);
​
    watch(
      () => state.count,
      (newVal, oldVal) => {
        console.log(newVal, oldVal);
      }
    );
​
    return { state, double };
  },
});

📦 打包结果:

makefile 复制代码
chunk-vendors.js: 79.95 KiB
app.js: 2.15 KiB

💡 对比发现:

加了 computed / watch 后, 体积才稍微增大了一点点(1KB 左右) 。 因为这两个函数确实用到了,才被打包进来。


🧃 比喻:

Vue3 的 Tree Shaking 就像你在点奶茶:

  • 只加你选的料(比如红豆+波霸)
  • 其他没选的(比如布丁、仙草)自动不加!

所以打包结果又轻又快💨!


💎 六、Tree Shaking 的作用总结(第 44 页)

优化点 具体表现
✅ 减少程序体积 打包更小,传输更快
⚡ 减少运行时间 启动更快,性能更好
🧩 更好架构优化 API 模块化、可独立加载

📘 记忆口诀

Vue2 是"大锅乱炖",Vue3 是"点单制作" 🍵

  • Vue2:全家桶套餐(用不用都打包)
  • Vue3:Tree Shaking(按需装料)

最后结果:Vue3 更轻、更快、更可控 💪


🎯 一句话总结:

Vue3 的 Tree Shaking 就像"智能奶茶机"------ 只打你点的料,不浪费、不多装, 每一口(每一行代码)都是刚刚好!😋✨

相关推荐
ruanCat3 小时前
记一次因 vue-router 升级而导致的 uniapp 故障
前端·vue.js
Damon小智3 小时前
基于 Rokid JSAR 打造精致的 3D 白色飞机模型
前端·虚拟现实
Mintopia3 小时前
🌌 知识图谱与 AIGC 融合:
前端·javascript·aigc
三十_3 小时前
TypeORM 基础篇:项目初始化与增删改查全流程
前端·后端
小时前端3 小时前
事件委托性能真相:90%内存节省背后的数据实证
前端·dom
半木的不二家3 小时前
全栈框架Elpis实战项目-里程碑一
前端
超能996要躺平4 小时前
用三行 CSS 实现任意多列等分布局:深入掌握 Grid 的 repeat() 与 gap
前端·css
我叫黑大帅4 小时前
面对组件的不听话,我还是用了它…………
前端·javascript·vue.js