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 就像"智能奶茶机"------ 只打你点的料,不浪费、不多装, 每一口(每一行代码)都是刚刚好!😋✨

相关推荐
To_OC6 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy7 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志7 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble8 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯9 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜10 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司10 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g10 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森10 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http