🌸 一、背景:为什么要有 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]
就会像这样出大问题:
- 命名冲突 各 mixin 都可能定义
data()
、methods
、甚至同名变量, 到底谁覆盖谁?模糊不清。😵 → 就像不同员工都往奶茶里加糖,你根本不知道最后多甜。 - 数据来源不清晰 组件里到底
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()
让坐标能自动响应变化。onMounted
和onUnmounted
相当于"开店时装上摄像头 / 关店时拔掉摄像头"。
💡 第二步:在组件里使用(第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.x
和position.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,但只用到了 ref
和 reactive
, 打包时其他没用到的部分会自动删掉,结果体积更小~
🍵 类比:
就像点奶茶时说"我只要珍珠不要椰果", 店员就不会给你装满整杯料,只保留你要的。
⚡ 三、更快(第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) 🧩 管理更清晰(模块化) 💬 客户体验更好(开发更顺畅)
🌈 一、目标:用 Vue3 实现一个 Modal 弹窗组件
Modal 就是"弹出层",常见场景有:
- 点"新增""编辑"按钮时,弹出一个输入框;
- 点击"确认删除",弹出"确定要删除吗?"对话框。
通俗讲:
Modal 就像奶茶店里的"透明点餐窗口"🍹------ 顾客点单时出现(弹出),点完关掉(消失),主界面依然在后面静静等着。
🧩 二、设计思路(第18页)
图片里画了三块思维导图:
Vue3 实现 Modal 组件的三步走:
- 组件设计
- 需求分析
- 实现流程
🧠 三、组件设计(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 只是负责框架,内容你随意填。
📦 六、总结整段代码逻辑:
- Teleport 把弹窗移到 body 层;
- 遮罩层(mask)点击可关闭;
- 标题栏显示弹窗标题;
- 内容区通过插槽渲染;
- 按钮区提供"确认 / 取消"操作;
- 国际化 支持多语言(通过
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);
🧠 含义:
- 通过
Vue.extend()
生成一个组件类; - 创建实例;
- 挂载到
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 小,是因为做了两件事:
- 移除了部分不常用 API(轻装上阵)
- 使用 Tree Shaking 技术(只打包你用到的部分)
🍬 举个例子
比如你点奶茶:
Vue2:不管你要什么,先把珍珠、布丁、红豆、仙草全装上。 Vue3:只加你点的那几种料,没点的不打包。
这就是 Tree Shaking(摇树优化) 。 打包时只留下用到的 ref
、reactive
、computed
, 没用到的模块直接丢掉。
📘 编译后示例代码:
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' // ❌ 不会触发
💣 问题:
- 删除属性无法监听
- 新增属性无法监听
- 数组变化(如 push/pop)也难检测
- 递归层级太深,性能差
💡 Vue3 的 Proxy 完美解决:
arduino
const state = reactive({ name: '小可爱' })
delete state.name // ✅ 响应式生效
state.age = 20 // ✅ 响应式生效
Proxy 就像一个万能"拦截器", 可以拦截所有对象操作:get
、set
、deleteProperty
、has
等。
🍵 类比:
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 的聪明做法:
- 用 Proxy 让数据变化更快、监听更精准。
- 用 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
当我们在组件里加上 computed
和 watch
:
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 就像"智能奶茶机"------ 只打你点的料,不浪费、不多装, 每一口(每一行代码)都是刚刚好!😋✨