20251017-Vue2八股文整理(下篇)

85 阅读43分钟

🌟 第 121–125 页主题

Vue Slot 插槽全面理解 + 实例演练 + 原理剖析


🍹 一、18.3.1 默认插槽(第 121 页)


📘 概念:

默认插槽就是: 如果你在组件中使用 <slot>,但没给名字,它就会显示父组件传入的内容


💻 示例:

子组件:
<template>
  <div>
    <slot></slot>
  </div>
</template>
父组件:
<layout>
  <p>欢迎光临奶茶铺</p>
</layout>

📤 输出:

<div>
  欢迎光临奶茶铺
</div>

🧋 类比:

就像奶茶机上有一个「万能加料口」, 你往里面倒什么料(珍珠/红豆/芋圆),它就装进去显示出来。

如果你不放料,它就空着; 你放什么,它就盛什么。😆

💡 默认插槽 = 万能加料口。


🍰 二、18.3.2 具名插槽(第 122 页)


📘 概念:

当你有多个插槽时,可以给它们取不同名字,方便放内容到特定位置。


💻 示例:

子组件:
<template>
  <header><slot name="header"></slot></header>
  <main><slot></slot></main>
  <footer><slot name="footer"></slot></footer>
</template>
父组件:
<layout>
  <template v-slot:header>
    <h1>👑 顶部标题</h1>
  </template>

  <p>🍹 这里是中间内容</p>

  <template v-slot:footer>
    <h3>📞 底部信息</h3>
  </template>
</layout>

📤 输出:

<header>👑 顶部标题</header>
<main>🍹 这里是中间内容</main>
<footer>📞 底部信息</footer>

🧋 奶茶铺类比:

想象这是三层奶茶机

  • 第一层是奶盖区(header)
  • 第二层是主饮区(default)
  • 第三层是装饰区(footer)

你把料放到对应的“加料口”, Vue 会自动帮你装进正确的层里~ 🍧

💡 具名插槽 = 按标签分类的加料机!


🍓 三、18.3.3 作用域插槽(第 123–124 页)


📘 概念:

👉 作用域插槽是 Vue 插槽的高级用法。 它允许父组件接收子组件的数据,然后根据这些数据自定义内容显示。


💻 示例 1:

子组件:
<template>
  <div>
    <slot :user="user"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: { name: '小可爱', age: 18 }
    }
  }
}
</script>
父组件:
<user-card v-slot="scope">
  <p>姓名:{{ scope.user.name }}</p>
  <p>年龄:{{ scope.user.age }}</p>
</user-card>

📤 输出:

姓名:小可爱
年龄:18

🧋 奶茶铺类比:

假设 Vue 组件是一台“智能奶茶机”: 机器(子组件)里面知道当前:

  • 糖度(sugar)
  • 温度(temp)
  • 原料配方(ingredients)

它把这些数据通过 slot 暴露出来, 父组件(前台服务员)就能拿到这些信息, 显示在屏幕上,告诉顾客:

“这杯是 50% 糖、加珍珠的热奶茶~🍵”

💡 这就是「作用域插槽」: 机器把数据共享给外部,外部根据数据决定显示什么。


🍬 四、18.4 原理分析(第 124–125 页)


📘 Vue 底层如何实现插槽?

当你写:

<layout>
  <p>欢迎光临</p>
</layout>

Vue 在编译阶段会把它变成:

createVNode(Layout, null, {
  default: () => [createVNode('p', null, '欢迎光临')]
})

💡 翻译成人话:

Vue 会把“父组件传进来的内容”变成函数, 当渲染时,再调用这个函数插进去。


🧋 奶茶铺比喻:

Vue 在装奶茶时,并不是马上倒进去。 而是先“记下配方”(函数), 等到上杯时再按配方倒料(调用函数生成内容)。

这样做的好处: ✅ 性能高 ✅ 可动态更新内容 ✅ 插槽内容可带数据(作用域插槽)


💻 代码示例(进阶解释):

function renderSlot(slots, name = 'default', props) {
  const slot = slots[name];
  if (slot) {
    return slot(props);
  } else {
    return [];
  }
}

💬 解释:

  • slots:所有插槽集合;
  • name:当前要渲染的插槽名;
  • props:要传入插槽的数据;
  • 返回的是插槽内容。

Vue 就靠这个函数把内容“插入”组件模板的!


🎯 五、小结(第 125 页)

插槽类型作用奶茶铺类比
默认插槽塞普通内容万能加料口
具名插槽按区分层内容三层奶茶机
作用域插槽带数据传递智能奶茶机(显示糖度、温度)
原理函数化渲染内容先记配方再倒料

🧠 六、小可爱记忆口诀 💡

🧃 插槽三兄弟, 🧋 默认最随意。 🍰 具名分层次, 🤖 作用域最聪明!


🧩 七、生活一句话总结

Slot 就像奶茶铺的“加料系统”:

  • 默认插槽:万能口;
  • 具名插槽:不同层加料;
  • 作用域插槽:机器能告诉你糖度和温度!

而 Vue 在背后,用函数巧妙地把“料”插到正确位置, 让每杯奶茶(页面)都完美无缺 🍵✨

🌟 第 126–130 页主题

什么是虚拟 DOM?为什么需要虚拟 DOM?如何实现一个?


🧋 一、什么是虚拟 DOM(Virtual DOM)——第 126 页


📘 定义:

虚拟 DOM(Virtual DOM)是真实 DOM 的 JavaScript 对象映射。 它用 JS 对象来“模拟”页面中的 DOM 节点。

通俗讲:

  • 浏览器的 DOM 节点(HTML)操作开销大;
  • 我们就用 JS 来“做个模型图”,
  • 最后一次性更新页面。

🏠 类比:装修设计图

想象你要装修一间奶茶店 🍹

你不会一边刷漆、一边砸墙、一边装灯对吧? 那样又累又乱(=频繁操作真实 DOM)💥

你会先: 1️⃣ 在电脑上画设计图(=虚拟 DOM) 2️⃣ 看满意后,一次性施工(=真实 DOM 更新)

这就是 Virtual DOM 的核心思想:

“先在脑子里(JS)画草图,再一次性更新页面。”


💻 示例代码:

const vnode = {
  tag: 'div',
  props: { id: 'app' },
  children: [
    { tag: 'h1', children: 'Hello Vue' },
    { tag: 'p', children: 'Virtual DOM Example' }
  ]
}

💬 解释:

  • tag:标签类型,比如 <div>
  • props:属性;
  • children:子节点;
  • 整个 vnode 就是一个页面结构的 JS 对象。

🧠 小可爱记忆口诀:

“虚拟 DOM 就像装修图纸, 先画草图,再动工。” 🏠✨


🍰 二、为什么需要虚拟 DOM(第 127 页)


📘 背景问题:

真实 DOM 的问题是:

  • 操作 DOM 十分耗性能;
  • 每次修改都要触发浏览器重绘和重排(Reflow/Repaint)
  • 页面复杂时性能急剧下降。

💬 类比说明:

假设顾客点奶茶:

  • 真实 DOM:每接一单你都去后厨打一次奶茶;
  • 虚拟 DOM:你先记下今天所有订单(JS 模型), 晚点一次性制作完发货 🚚

这就节省了“反复去后厨取料”的时间。


⚙️ 优点总结:

优点解释类比
批量更新多个改动一次性处理一次性做十杯奶茶
跨平台不局限浏览器 DOM,可渲染到小程序、App一份设计图能装修不同分店
高效 Diff通过比较新旧虚拟 DOM 找出最小变化只改掉旧装修中不同的部分

🔥 关键词记忆:

“虚拟 DOM 的目标:少动真 DOM,多用 JS 算。”


🧩 三、如何实现一个虚拟 DOM(第 128–130 页)


Vue、React 都有自己的 Virtual DOM 实现。 但如果面试官问你:

“手写一个简单的虚拟 DOM 怎么做?”

可以用下面这个“奶茶店版思路”👇


💻 简化实现 1:创建虚拟节点

function createElement(tag, props, children) {
  return { tag, props, children };
}

💬 示例:

const vdom = createElement('div', { id: 'cup' }, [
  createElement('h1', null, '珍珠奶茶'),
  createElement('p', null, '好喝不贵')
]);

输出对象:

{
  tag: 'div',
  props: { id: 'cup' },
  children: [
    { tag: 'h1', children: '珍珠奶茶' },
    { tag: 'p', children: '好喝不贵' }
  ]
}

🧋 比喻:

你先在电脑上画出奶茶杯的结构图(虚拟 DOM 对象)。


💻 简化实现 2:渲染成真实 DOM

function render(vnode) {
  const el = document.createElement(vnode.tag);
  if (vnode.props) {
    Object.entries(vnode.props).forEach(([k, v]) => el.setAttribute(k, v));
  }
  if (typeof vnode.children === 'string') {
    el.textContent = vnode.children;
  } else {
    vnode.children.forEach(child => el.appendChild(render(child)));
  }
  return el;
}

💬 解释:

  • createElement():画设计图;
  • render():根据图纸施工建店;
  • appendChild():递归装配结构。

🎨 效果:

const app = render(vdom);
document.body.appendChild(app);

📤 页面结果:

<div id="cup">
  <h1>珍珠奶茶</h1>
  <p>好喝不贵</p>
</div>

🧋 奶茶铺比喻:

你先在 Excel(JS)里规划了门店结构: “一层是 LOGO、二层是产品名、三层是描述”。

然后再一次性搭建好店面。

这比“边想边砌砖”快多啦!


💻 简化实现 3:虚拟 DOM Diff 算法(简述)

虚拟 DOM 最强大的地方在于—— 每次页面更新时,它不会整个重建,而是对比新旧差异(Diff), 然后只修改不同的部分。


🌰 举例:

旧虚拟 DOM:

<h1>珍珠奶茶</h1>

新虚拟 DOM:

<h1>红豆奶茶</h1>

👉 Diff 算法只发现文字不同: 于是 Vue 只修改了文字节点,不会重建整个 <h1> 元素。


💬 奶茶铺比喻:

顾客改了口味,从“珍珠奶茶”换成“红豆奶茶”, 你不会推倒整家奶茶店重建。

你只换掉配料部分。💡

这就是虚拟 DOM 的核心优化!


🍬 四、页面总结表(第 130 页)

内容作用奶茶铺比喻
虚拟 DOM 是什么JS 模拟真实 DOM装修图纸
为什么要有提升性能、统一跨平台一次性施工
如何实现createElement + render + diff画图纸 → 施工 → 局部改造

🧠 小可爱记忆口诀 💡

🏗️ “虚拟 DOM 三步走” 🖊️ 画图纸(createElement) 🧱 一次施工(render) ✏️ 小改动(diff)

🍹 奶茶铺装修记: “先设计图,再开工,不重建整家店,只换配料。”


🎯 一句话总结

虚拟 DOM 是“页面的 JS 草图”, 它让前端能像建筑师一样, 用算法精确地控制更新,节能又高效。🏗️💡

🌟 第 131–135 页主题

虚拟 DOM 的 Diff 算法原理 + 为什么要有 key


🏗️ 一、背景复习(第 131 页)


上页我们讲过: 虚拟 DOM = 页面“草图” , 当页面要更新时,Vue 会:

1️⃣ 先生成一个新的虚拟 DOM(新草图) 2️⃣ 和旧虚拟 DOM(旧草图)对比 3️⃣ 只修改不同的地方

这过程就叫 —— Diff 算法


💬 类比说明:

想象你是奶茶铺的经理,要翻新店面:

  • 旧装修图:木地板 + 红招牌 + 老菜单
  • 新装修图:木地板 + 蓝招牌 + 新菜单

💡 你不会推倒整家店重建! 只会对比两份图纸, 发现:

“哦,只有招牌和菜单变了”, 那就只换那两个地方 ✅

这就是虚拟 DOM Diff 做的事。


🍹 二、Diff 算法是什么?(第 131–132 页)


📘 定义:

Diff 算法是一个“找不同”的过程。 它会对比新旧两棵虚拟 DOM 树(VTree), 找出哪些节点不同,然后最小化修改真实 DOM。


💡 Diff 的核心逻辑:

  • 同层对比,不跨层
  • 如果节点类型不同 → 直接替换
  • 如果节点类型相同 → 继续比属性
  • 属性不同的才更新

🧠 小可爱口诀:

“同层比,不乱跳,改不同,留相同。”


💻 三、简化代码解析(第 132–133 页)


⚙️ 核心函数一:对比两个节点

function diff(oldVNode, newVNode) {
  // 如果节点类型不同,直接替换
  if (oldVNode.tag !== newVNode.tag) {
    oldVNode.el.replaceWith(render(newVNode));
  } 
  // 如果是文本节点
  else if (typeof newVNode.children === 'string') {
    oldVNode.el.textContent = newVNode.children;
  } 
  // 否则是元素节点,更新属性 + 递归比对子节点
  else {
    updateProps(oldVNode.el, oldVNode.props, newVNode.props);
    const oldChildren = oldVNode.children;
    const newChildren = newVNode.children;

    // 对比每个子节点
    for (let i = 0; i < newChildren.length; i++) {
      diff(oldChildren[i], newChildren[i]);
    }
  }
}

💬 解释:

步骤动作奶茶铺比喻
比标签<div> vs <p> → 替换换掉整个招牌
比内容文本不同 → 更新文字更新菜单文字
比属性比如 class / style 不同 → 修改重新粉刷颜色
递归比子节点一层层深入比检查每个货架上的配料

🧋 类比说明:

Diff 算法就像“装修队长”,拿着新旧设计图比对, 不乱拆,只换该换的部分。


🍰 四、updateProps 函数(第 133 页)


💻 示例:

function updateProps(el, oldProps, newProps) {
  // 修改属性
  for (let key in newProps) {
    const newValue = newProps[key];
    const oldValue = oldProps[key];
    if (newValue !== oldValue) {
      el.setAttribute(key, newValue);
    }
  }

  // 删除旧属性
  for (let key in oldProps) {
    if (!(key in newProps)) {
      el.removeAttribute(key);
    }
  }
}

💬 通俗解释:

这段代码就是:

“如果新的装修图(newProps)有改颜色或字样,就重新刷; 旧的属性没了,就拆掉。”


🧋 奶茶铺例子:

场景对应代码行为
招牌颜色从红变蓝修改属性 setAttribute
原来有 slogan 现在去掉了删除 removeAttribute
新增“今日特价”贴纸添加新属性

🍧 五、Diff 递归部分(第 134 页)


这部分代码展示了:

Vue 是怎么“一层层”检查并更新页面结构的。

💻 伪代码理解:

function patchChildren(oldChildren, newChildren) {
  const len = Math.min(oldChildren.length, newChildren.length);

  // 先对比相同数量的节点
  for (let i = 0; i < len; i++) {
    diff(oldChildren[i], newChildren[i]);
  }

  // 新的比旧的多 → 需要挂载新节点
  if (newChildren.length > oldChildren.length) {
    newChildren.slice(len).forEach(c => mount(c));
  }

  // 旧的比新的多 → 需要删除多余的节点
  if (newChildren.length < oldChildren.length) {
    oldChildren.slice(len).forEach(c => unmount(c));
  }
}

🧋 类比解释:

这段逻辑就像:

你发现货架更新了:

  • 新菜单多出几杯奶茶 → 补几个新架子(挂载)
  • 老菜单少了几项 → 拆掉旧架子(卸载)

💡 Vue 就用这种「最小改动」方式更新界面。


🍵 六、Key 的作用(第 134–135 页)


📘 背景:

Diff 算法默认是“按顺序”比对子节点的。 但如果元素位置变了(比如调换顺序),Vue 可能会误判。


💬 举例:

<ul>
  <li>A</li>
  <li>B</li>
  <li>C</li>
</ul>
​
→ 变为
​
<ul>
  <li>B</li>
  <li>A</li>
  <li>C</li>
</ul>

Diff 默认会:

  • 看到第一个位置不同 → 直接替换掉 A 为 B;
  • 第二个位置又不同 → 再替换 B 为 A; 结果:多做了无用工!

🧠 Key 的作用:

当我们写:

<li v-for="item in list" :key="item.id">{{ item.name }}</li>

Vue 就会:

通过 key 唯一标识每个节点, 对比时按 key 查找差异,而不是按顺序。


🧋 类比说明:

想象你奶茶店的配料架:

  • 每个配料罐(A、B、C)都有编号(key)。
  • 即使你换位置,系统也能凭编号知道哪个是哪个。

✅ 不会因为顺序变了就重新上架所有物料。


🧠 小可爱记忆口诀 💡

🧩 Diff 三步走: 🥤 比节点 → 标签不同换新的; 🍰 比属性 → 改颜色或删旧的; 🍬 比子节点 → 多的补、少的拆。

🔑 加上 key,Vue 就能聪明识货,不白干!


🎯 一句话总结(第 135 页)

Diff 算法 = “找不同 + 局部更新” 的装修师傅 🧱 key = “每个物料的编号”,让更新精准高效 🚀


💡 整页总图表复习

概念功能类比
Diff 算法比对新旧虚拟 DOM装修对比新旧图纸
updateProps更新属性重刷颜色或贴新标签
patchChildren递归子节点检查货架每层配料
key唯一标识节点配料罐编号,不混淆

🌟 第 136–138 页

🧠 Vue 响应式原理(核心:Proxy + Dep + Watcher


🥤 一、什么是响应式?

响应式就是——当数据变化时,页面会自动更新。

举个例子:

你奶茶铺的库存系统里,写着“珍珠剩 20 份”。 当库存变成 19 时,前台屏幕会立刻显示「珍珠剩余 19 份」。

💡 这就是响应式:数据一改,界面自动刷新。


🧩 二、Vue2 的响应式原理:Object.defineProperty

在 Vue2 时代,Vue 是通过 数据劫持(data hijacking) 来实现响应式的。


💻 示例代码:

let data = { price: 10 };

Object.defineProperty(data, 'price', {
  get() {
    console.log('有人访问了 price');
    return value;
  },
  set(newVal) {
    console.log('price 改成了', newVal);
    value = newVal;
    // 通知页面更新
  }
});

💬 通俗解释:

Vue 像给每个数据都安排了一个“小保姆”:

  • 当有人“访问”数据时(get),保姆记录是谁用了;
  • 当有人“修改”数据时(set),保姆就通知所有用到它的地方更新。

🧋 奶茶铺类比:

每个原料桶(data.price)都有个智能标签:

  • 有人查看库存 → 记录谁在看;
  • 有人改库存 → 所有前台都自动同步更新。

所以 Vue2 就靠这个机制自动更新界面。


⚙️ 三、Vue3 响应式原理:Proxy(第 137 页)

Vue3 把上面的「小保姆」系统升级了!🚀 改用 Proxy,更高效、可监听整个对象(包括新增属性)。


💻 示例:

let data = { sugar: '半糖', ice: '少冰' };
​
const proxyData = new Proxy(data, {
  get(target, key) {
    console.log(`读取 ${key}${target[key]}`);
    return target[key];
  },
  set(target, key, value) {
    console.log(`修改 ${key}${value}`);
    target[key] = value;
    // 页面更新逻辑
    return true;
  }
});
​
proxyData.sugar = '全糖';

💬 通俗解释:

Proxy 是一个“监听员”,它可以拦截所有对象操作:

  • 读取(get)时记录依赖;
  • 修改(set)时触发更新。

🧋 奶茶铺例子:

Vue3 的 Proxy = “超级智能仓库管理员” 🤖

你要查库存、改库存、新增货品,他都能听见。 而且还能一键同步所有显示屏!✨

相比 Vue2 的 Object.defineProperty, 它可以监听整个仓库,而不是一个个桶。


🔔 四、Dep 与 Watcher(第 138 页)


在 Vue 内部,响应式系统其实分为三类角色:

角色功能类比
Dep依赖收集中心顾客登记本
Watcher观察者,谁在用就记录谁收银员屏幕
Proxy劫持数据,触发通知智能仓库系统

📘 流程总结:

1️⃣ 读取数据 → 收集依赖(谁在用) 2️⃣ 修改数据 → 通知依赖更新


💻 示例伪代码:

let dep = new Set();

function watchEffect(fn) {
  dep.add(fn);
  fn();
}

let state = new Proxy({ count: 1 }, {
  set(target, key, val) {
    target[key] = val;
    dep.forEach(fn => fn());
    return true;
  }
});

watchEffect(() => console.log(`Count: ${state.count}`));

state.count++;

🧋 奶茶铺类比:

顾客(Watcher)在屏幕上看到库存; 仓库(Proxy)更新库存; 记录簿(Dep)通知所有顾客刷新显示。

这就是「Vue 响应式系统」的本质!👏


🧠 小可爱记忆口诀:

🍵 Proxy 是仓库管理员 📒 Dep 是通知记录簿 👀 Watcher 是前台屏幕

改库存 → 管理员通知记录簿 → 所有屏幕刷新!



🌟 第 139–140 页

☕ Axios 封装与使用:Vue 如何处理网络请求


🚀 一、什么是 Axios?(第 139 页)

Axios 是一个基于 Promise 的 HTTP 库,用于发送网络请求。

比如登录、拉取数据、提交表单这些操作,全靠 Axios 实现。


💻 简单例子:

import axios from 'axios';

axios.get('/api/menu').then(res => {
  console.log(res.data);
});

🧋 奶茶铺类比:

Axios = 🍰 “奶茶店外卖系统” 你发请求(下单) → 服务器接单(厨房做奶茶) → 响应回来(返回奶茶)。


🍰 二、Axios 的特点:

特点解释类比
Promise 化异步操作更优雅像点单系统一样支持「等待中」状态
支持拦截器请求前后可加处理点单前先检查会员卡,返回后统一包装
自动转换 JSON不用手动解析厨房自动把原料装进杯子
支持并发多个请求一起发一次点多杯奶茶
可配置可全局配置 BaseURL、超时等连锁奶茶铺同一个后台系统

🍵 三、为什么要封装 Axios?(第 140 页)


直接用 Axios 当然可以,但项目大了就麻烦:

  • 每次请求都得写一堆重复配置;
  • 错误提示、Token 校验都要手动加;
  • 不同接口逻辑散乱不好维护。

👉 所以我们封装一层,让它更智能、更统一!


💻 示例封装(简化版):

import axios from 'axios';
​
const service = axios.create({
  baseURL: '/api',
  timeout: 5000
});
​
// 请求拦截器
service.interceptors.request.use(config => {
  console.log('发送请求中...');
  return config;
});
​
// 响应拦截器
service.interceptors.response.use(
  res => {
    console.log('请求成功!');
    return res.data;
  },
  err => {
    console.error('请求失败:', err);
  }
);
​
export default service;

💬 通俗解释:

拦截器阶段作用奶茶铺类比
请求拦截器请求前可加 token、loading下单前核对会员卡
响应拦截器统一处理成功/失败拿奶茶前检查品质

🧋 Vue 项目一般会在 utils/request.js 里封装这个模块, 再在接口文件中调用,比如:

import request from '@/utils/request';

export function getMenu() {
  return request({
    url: '/menu',
    method: 'get'
  });
}

💡 这样就像建立了一个「点单系统」,所有接口都走一个统一入口, 既方便维护,也能统一管理错误提示和登录状态。


🧠 小可爱记忆口诀:

🍵 Axios 是点单员 🧾 请求拦截器 = 下单前核对 📦 响应拦截器 = 出餐前检验 💡 封装 = 把点单系统做成自动化收银台!


🎯 一句话总结(第 140 页)

Vue 的响应式 = 自动更新库存系统 Axios 封装 = 自动点单系统

两者配合,就让前端像奶茶铺一样—— 自动记库存、自动接单、自动更新界面 🧋💻✨

🌟 第 141–145 页

Vue 项目中 axios 封装进阶 + 错误处理机制


🧋 一、axios 封装进阶概念(第 141 页)


上一章我们讲了 axios 的基本封装。 这次的重点在于 —— 把 axios 的“点单系统”变得更智能、更自动化。


💬 类比先行:

想象你是奶茶连锁品牌的技术主管 🍹 你要让所有分店都能:

  • 自动携带会员 token;
  • 自动显示“加载中”;
  • 自动处理错误提示;
  • 请求完自动收尾清理。

这就靠 axios 的二次封装 来实现。


☕ 二、封装基础结构(第 141–142 页)


💻 代码示例:

import axios from 'axios';
import { ElMessage } from 'element-plus';

const service = axios.create({
  baseURL: '/api',
  timeout: 8000
});

💬 解释:

  • axios.create():创建一个 axios 的专属实例;
  • baseURL:所有请求都默认加上 /api 前缀;
  • timeout:超过 8 秒自动报错。

🧋 类比:

你为“奶茶总店”设立了统一的订单通道。 所有分店下单都走这个渠道,超时就提醒“下单失败”。


🍰 三、请求拦截器与响应拦截器(第 142 页)


💻 请求拦截器:

service.interceptors.request.use(
  config => {
    config.headers['Authorization'] = 'Bearer ' + localStorage.getItem('token');
    console.log('请求发出中...');
    return config;
  },
  error => Promise.reject(error)
);

💬 通俗解释:

1️⃣ 每次发请求前自动附带 token; 2️⃣ 如果 token 不存在,就提醒用户先登录。

🧋 类比:

“每次顾客点单前,系统自动扫会员卡!” 这就是「请求拦截器」的作用。


💻 响应拦截器:

service.interceptors.response.use(
  res => {
    if (res.data.code !== 200) {
      ElMessage.error(res.data.message || '请求出错');
      return Promise.reject(res);
    }
    return res.data;
  },
  error => {
    ElMessage.error('网络错误或服务器异常');
    return Promise.reject(error);
  }
);

💬 解释:

  • 如果后端返回错误码,自动弹出提示;
  • 如果服务器炸了(500 或超时),也会统一报错;
  • 省去了每个页面重复写 try-catch。

🧋 类比:

“出餐异常?点单系统自动报警,不用店员每次手动报错!”


🧠 小可爱记忆口诀:

🥤 请求拦截器:下单前核对会员卡 📦 响应拦截器:出餐后质量检测 ⚠️ 错误处理器:异常统一客服中心汇报


🍩 四、统一封装请求方法(第 143 页)


💻 示例:

const request = (url, method, data) => {
  return service({
    url,
    method,
    [method === 'get' ? 'params' : 'data']: data
  });
};

💬 通俗解释:

  • 这段代码是“点单小助手”,
  • 你只要告诉它:地址 + 方法 + 数据
  • 它会自动决定用 params 还是 data

🧋 类比:

顾客说:“帮我点一杯奶茶、加冰。” 系统自动判断:这是 GET(查询菜单)还是 POST(下订单)。


💡 使用举例:

request('/user/login', 'post', { username: 'admin', password: '123456' });

💬 自动完成:

  • 调用接口;
  • 自动携带 token;
  • 自动错误提示;
  • 返回数据。

相当于“一键点单 + 自动结账 + 自动出餐”,太丝滑啦 🍰


☕ 五、导出与复用(第 144 页)


💻 示例:

import request from '@/utils/request.js';
​
export function getUserList(params) {
  return request('/user/list', 'get', params);
}

💬 解释:

  • 通过一个统一的 request 模块, 所有接口都只需一行调用;
  • 再由 api/user.js 管理所有接口函数。

🧋 类比:

所有奶茶订单都从“点单中心”下单, 不再各分店自己搞一套。


🍰 六、请求状态封装(第 144–145 页)


Vue 项目常常会封装 loading 状态错误状态

const useRequest = (apiFn) => {
  const loading = ref(false);
  const data = ref(null);
  const error = ref(null);

  const run = async (...args) => {
    loading.value = true;
    try {
      const res = await apiFn(...args);
      data.value = res;
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  };

  return { loading, data, error, run };
};

💬 通俗解释:

这段是「请求状态管理器」:

  • 自动显示加载中;
  • 自动处理异常;
  • 自动收尾清理。

🧋 类比:

系统能自动显示「奶茶制作中…」 如果出错 → 显示「原料不足」 成功 → 显示「订单完成」


🧃 七、错误处理机制(第 145 页)


Vue 项目的错误处理,分为三层:

层级举例解决办法
1️⃣ 接口错误网络断开、超时try-catch + ElMessage
2️⃣ 逻辑错误接口返回 code != 200响应拦截器统一处理
3️⃣ Vue 报错组件出 bugapp.config.errorHandler 捕获

💻 示例:

app.config.errorHandler = (err, vm, info) => {
  console.error('组件错误:', err);
  ElMessage.error('页面出错啦~');
};

🧋 类比:

顾客点单 → 网络断了(外卖超时); 出餐错了(接口返回异常); 系统宕机(Vue 报错)。

Vue 会为这三种错误都安排「客服中心」负责处理。


🧠 小可爱记忆口诀:

📡 axios 封装 = 智能点单系统 🧾 拦截器 = 自动检查与验货员 🧯 errorHandler = 奶茶客服中心


🎯 一句话总结(第 145 页)

Vue 封装 axios,是为了让请求更聪明、更稳更好维护。 它帮你实现了: “自动登录验证、自动加载状态、自动报错提示”。

🧋 在奶茶铺比喻中就是:

“顾客下单 → 系统验证会员 → 自动做单 → 出错自动报客服”。

🌟 第 146–150 页

Vue 项目中的错误类型、捕获方式与分析上报


🧋 一、错误类型(第 146 页)

Vue 项目中的错误一般分三类:

类型举例奶茶铺类比
① 运行时错误JS 执行出错,比如 xxx is not defined店员打奶盖时“杯子掉地上”
② 接口错误后端接口挂了、返回异常厨房做奶茶时原料缺货
③ UI 渲染错误Vue 模板出错屏幕显示乱码、广告牌灯不亮

💡 小可爱口诀:

“逻辑错 → 代码问题,接口错 → 服务器问题,渲染错 → Vue 自身问题。”


☕ 二、如何处理错误(第 147 页)

Vue 给我们提供了几种“黑匣子”来记录错误。 每个阶段都有一个“监控点”。


1️⃣ 捕获组件内的错误(errorCaptured


💻 代码示例:

export default {
  errorCaptured(err, vm, info) {
    console.log('组件内错误捕获:', err, info);
    return false;
  }
};

💬 解释:

  • err 是具体错误;
  • vm 是出错的组件;
  • info 是额外信息,比如「哪个生命周期挂了」。

🧋 奶茶铺类比:

这就像“分店监控器”,如果珍珠机坏了、冰箱断电,它能第一时间记录下来,不让问题传到总店。

return false 表示:我自己处理,不再往上传。


2️⃣ 捕获全局错误(app.config.errorHandler


💻 示例:

app.config.errorHandler = (err, vm, info) => {
  console.error('全局错误捕获:', err);
  ElMessage.error('页面出错啦,请稍后重试!');
};

💬 通俗解释:

  • 这是“总部的总监控系统”,所有分店出错都能上报;
  • 同时弹出一个用户提示,不让顾客看到脏报错信息。

🧋 奶茶铺类比:

“总店客服中心收到分店报警后,会统一发公告:『系统维护中』。”


3️⃣ 捕获异步错误(window.onerrorPromise.catch


💻 示例:

window.onerror = function (msg, source, line, column, error) {
  console.log('脚本错误:', msg, source, line);
};

window.addEventListener('unhandledrejection', function (event) {
  console.log('Promise 未捕获异常:', event.reason);
});

💬 解释:

这些是原生 JS 提供的“保底网”。 如果 Vue 没拦到,它们还能兜底捕获。

🧋 奶茶铺类比:

“如果摄像头没录到事故,黑匣子也会记录!”


🍰 三、错误提示与展示(第 148 页)


Vue 一般会配合 UI 框架(如 Element Plus) 给出友好的提示信息。


💻 示例:

try {
  await getUserInfo();
} catch (err) {
  ElMessage.error('加载失败,请检查网络!');
}

💬 通俗解释:

不要让用户看到 “TypeError: Cannot read property of undefined”。 而要让他看到 “加载失败,请稍后重试 🍵”。


🧋 奶茶铺类比:

顾客下单失败,你不能直接甩出程序错误。 要礼貌地说:“抱歉,珍珠机有点小问题,稍后再试哦~💬”


⚙️ 四、错误上报机制(第 149 页)


在企业项目中,光提示不够,还要“记录错误日志”。 Vue 通常会用一个统一的上报模块,比如:


💻 示例:

function reportError(err, vm, info) {
  const payload = {
    message: err.message,
    stack: err.stack,
    info,
    url: window.location.href,
    time: new Date().toISOString()
  };

  fetch('/api/log/error', {
    method: 'POST',
    body: JSON.stringify(payload)
  });
}

💬 解释:

1️⃣ 把错误信息(内容 + 位置 + 时间)打包; 2️⃣ 发送到后端日志服务器。

🧋 奶茶铺类比:

每次分店出事故,系统自动上传“事故报告”: 包含“时间、地点、原因、负责人”📋。


🍩 五、错误分析与上报优化(第 150 页)


企业中通常还会配合「埋点」分析,统计出错次数:

💻 示例:

export function trackError(err) {
  try {
    const data = {
      type: 'vue-error',
      msg: err.message,
      stack: err.stack,
      time: Date.now()
    };
    localStorage.setItem('error-log', JSON.stringify(data));
  } catch (e) {
    console.error('错误上报失败', e);
  }
}

💬 解释:

  • 先暂存错误;
  • 再定期批量上传;
  • 防止频繁请求拖慢性能。

🧋 奶茶铺类比:

“别每出一次小问题就打电话给总部, 每天晚上汇总一份『当日小事故报告表』。”


🧠 小可爱记忆口诀:

🧯 三层防护体系:

  • 🪣 局部监控:errorCaptured(分店监控)
  • 🛰️ 全局监控:errorHandler(总部客服)
  • 🪶 异步兜底:window.onerror(黑匣子)

🧾 错误上报:

  • 把错误打包;
  • 上传到日志服务器;
  • 再根据次数分析优化。

🎯 一句话总结(第 150 页)

Vue 的错误处理机制 = 奶茶店的“事故上报体系”。

每个出错点都有“监控摄像头”:

  • 小问题 → 分店内部解决;
  • 大问题 → 报总部客服;
  • 异步异常 → 黑匣子兜底上报。

💬 这样项目就能「稳如老虎 🐯」, 即使用户遇到 bug,也会被优雅地安抚:“请稍后再试~🍵”。

💥 一、Vue 项目错误处理的实战代码(延续上一章)

💥 二、Axios 的实现原理(手写一个简易版!)


我这次继续用「奶茶店 🍹」的类比,让你从“看不懂代码” → “脑海里出现场景”。 每段我都配代码 + 通俗解释 + 生活例子 + 小可爱口诀 ❤️


🌟 第 151–152 页

Vue 全局错误捕获实战 & 上报实现


🧋 一、全局错误监控(第 151 页)

这段代码展示了一个“完整的错误捕获 + 上报”实现, 相当于给整个 Vue 应用装上一个「监控摄像头系统」。📹


💻 代码解析:

window.addEventListener('error', (event) => {
  console.log('捕获到同步错误:', event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  console.log('捕获到异步错误:', event.reason);
});

💬 通俗解释:

  • error:能捕到脚本错误,比如拼写错;
  • unhandledrejection:能捕到 Promise 没写 .catch() 的错误。

🧋 奶茶铺类比:

有点像店里装了两个摄像头:

  • 一个拍“员工出错”;
  • 一个拍“后台机器挂了”。

💻 全局封装(第 151 页下半部分)

function globalErrorHandler(err, vm, info) {
  const log = {
    message: err.message,
    info,
    url: window.location.href,
    time: new Date().toISOString()
  };

  // 上传到日志服务器
  fetch('/api/log', {
    method: 'POST',
    body: JSON.stringify(log)
  });
}

💬 通俗解释:

  • 捕获错误;
  • 打包错误信息;
  • 上传服务器。

🧋 类比:

每家奶茶分店都有一个“事故上报员”。 一旦出错,会立刻打包报告,发回总部客服系统。📨


🧠 小可爱记忆口诀:

🧯 错误捕获三层: 📍 局部:errorCaptured 🛰️ 全局:errorHandler 🪶 系统:window.onerror

🧾 报告内容三项: 错误信息 + 页面链接 + 时间戳


🌟 第 153–155 页

Axios 原理剖析 & 手写一个迷你版 Axios ✨


☕ 二、什么是 Axios(复习一下)

Axios 本质上是对 XMLHttpRequest 的一个封装, 它用 Promise 语法,让异步请求写起来更优雅。

🧋 类比:

Axios 就像奶茶店的「自动点单系统」。 你只要告诉它:

  • 想喝什么(url)
  • 热的还是冰的(method)
  • 加料吗(data) 它就自动帮你下单、返回奶茶结果。

🍩 三、Axios 的底层原理(第 153 页)

这一页的代码展示了一个「手写简易 Axios」:


💻 核心逻辑:

function myAxios({ method, url, data }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.setRequestHeader('Content-Type', 'application/json');
​
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.response));
      } else {
        reject(xhr.statusText);
      }
    };
​
    xhr.onerror = () => reject('网络错误');
    xhr.send(JSON.stringify(data));
  });
}

💬 通俗解释:

这个函数 myAxios() 本质上做了三件事: 1️⃣ 创建一个“快递员”(XMLHttpRequest 对象) 2️⃣ 让他去送货(open + send) 3️⃣ 根据送达情况(onload/onerror)决定“成功或失败”


🧋 奶茶铺类比:

  • 你点单(myAxios()
  • 店员派出外卖员(xhr
  • 送达成功(resolve())或失败(reject()
  • 如果厨房忙(超时/错误) → 系统显示“网络异常”

🍰 四、axios.create 的原理(第 154 页)

Axios 可以创建多个实例。 每个实例就像不同分店的“订单系统”,互不影响。


💻 示例:

class Axios {
  constructor() {
    this.defaults = { method: 'get' };
  }

  request(config) {
    const xhr = new XMLHttpRequest();
    xhr.open(config.method || this.defaults.method, config.url);
    xhr.send();
  }
}

const axios1 = new Axios();
axios1.request({ url: '/a' });

const axios2 = new Axios();
axios2.request({ url: '/b' });

💬 通俗解释:

  • Axios 类里保存着默认配置;
  • 每次 new Axios() 就会创建一个独立的请求器;
  • axios1axios2 各自独立工作。

🧋 奶茶铺类比:

你在上海有一间分店(axios1),在北京又开一间(axios2)。 各自都有独立菜单、库存、价格表,互不干扰。🍵


⚙️ 五、Axios 拦截器的实现原理(第 155 页)

这一页的重点是解释拦截器怎么运作的。


💻 示例:

axios.interceptors.request.use(config => {
  console.log('请求拦截:', config.url);
  return config;
});

axios.interceptors.response.use(response => {
  console.log('响应拦截:', response);
  return response;
});

💬 解释:

  • 请求拦截器会在发请求前执行;
  • 响应拦截器会在收到数据后执行。

🧋 奶茶铺类比:

  • 请求拦截器:点单前检查「会员卡」✅
  • 响应拦截器:出餐前检查「品质保证」🍰

🧠 小可爱记忆口诀:

🍵 Axios 三层结构:

  • XMLHttpRequest 是底层配送员
  • Promise 是调度中心
  • Interceptor 是安检员

🚀 Axios.create 就像“开新分店” 每家店都有独立配置,不抢生意。


🎯 一句话总结(第 155 页)

Axios 其实就是: 一个包装了 XMLHttpRequest 的「Promise 工厂 + 安检系统」。

🧋 在奶茶铺类比中:

顾客下单 → 系统封装请求 → 安检(拦截器) → 出餐(响应) → 出错时客服处理。

整个过程就像一场完美的奶茶外卖服务 🥤💨

🌟 第 156–160 页

Axios 拦截器机制 + 手写实现 + 源码分析


🧋 一、拦截器机制(第 156–157 页)

Axios 的拦截器机制其实特别像“奶茶店的质量控制线”: 在订单发出前、出餐后,都要经过“质检员” ✅。


💻 代码示例(第 156 页)

axios.interceptors.request.use(config => {
  console.log('🍹 请求拦截器执行');
  config.headers['token'] = 'abc123';
  return config;
});

axios.interceptors.response.use(res => {
  console.log('🍧 响应拦截器执行');
  return res.data;
});

💬 通俗解释:

  • request.use():请求发出前,统一加 token 或 loading;
  • response.use():响应回来后,统一处理数据或错误。

🧋 奶茶店类比:

  • 请求拦截器:下单前检查顾客有没有会员卡;
  • 响应拦截器:出餐后检查饮品是不是标准口味。

⚙️ 拦截器的内部工作逻辑(第 157 页)

💻 简化实现代码:

class InterceptorManager {
  constructor() {
    this.handlers = [];
  }

  use(fulfilled, rejected) {
    this.handlers.push({ fulfilled, rejected });
  }
}

💬 解释:

  • handlers 是一个数组,存储多个拦截函数;
  • 每次 .use(),就往里面塞一个。

🧋 类比:

每加一个拦截器,就像在工厂里多雇一个质检员。 点单流程就会经过他们一一检查。


☕ 二、Axios 的封装(第 158 页)

这一页代码展示了 Axios 类的完整骨架


💻 代码重点:

class Axios {
  constructor() {
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }
​
  request(config) {
    let chain = [dispatchRequest, undefined];
    this.interceptors.request.handlers.forEach(interceptor => {
      chain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
    this.interceptors.response.handlers.forEach(interceptor => {
      chain.push(interceptor.fulfilled, interceptor.rejected);
    });
​
    let promise = Promise.resolve(config);
    while (chain.length) {
      promise = promise.then(chain.shift(), chain.shift());
    }
    return promise;
  }
}

💬 通俗解释:

Axios 在内部搞了一个“管道流水线”: 先经过请求拦截器 → 再发请求 → 再经过响应拦截器。

每个阶段都能改数据、加头、或者报错。

🧋 奶茶铺类比:

你点一杯奶茶,流程是: 1️⃣ 点单台(request interceptor) 2️⃣ 厨房制作(dispatchRequest) 3️⃣ 出餐质检台(response interceptor)

Axios 让每个环节都可以“插手”干预。


🍰 三、派发请求(第 158–159 页)

这部分代码是 Axios 的「核心运输员」: 也就是实际发请求的那一步!


💻 示例:

function dispatchRequest(config) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open(config.method, config.url);
    xhr.onload = () => resolve({ data: xhr.response });
    xhr.onerror = reject;
    xhr.send(config.data);
  });
}

💬 通俗解释:

  • 这里真正调用了 XMLHttpRequest
  • 请求发出后拿到响应数据,返回 Promise。

🧋 奶茶铺类比:

这就像“外卖小哥”阶段: 他拿着订单出门 → 成功送达 → 拿回反馈结果。


🍵 四、请求的链式执行(第 159 页)

这段代码解释了为什么 axios.get().then().catch() 能链式调用。


💻 核心逻辑:

let promise = Promise.resolve(config);
while (chain.length) {
  promise = promise.then(chain.shift(), chain.shift());
}

💬 解释:

  • 每个拦截器其实就是 then() 链的一环;
  • Axios 用 Promise 串起来了所有处理函数;
  • 从请求拦截 → 发请求 → 响应拦截,环环相扣。

🧋 类比:

就像奶茶店有一条流水线: 点单 → 支付 → 调饮 → 打包 → 出餐 → 回访。 每一步都排队执行,不跳步骤。


🧠 小可爱记忆口诀:

Axios 内部 = 拦截器链 + Promise 管道。

🧃 请求链路: 请求拦截 → 发请求 → 响应拦截。

🚶 Promise 串起来: 每个阶段一个“then”,按顺序排队。


🍩 五、源码结构总结(第 160 页)

最后一页开始进入「源码分析导读」, 讲 Axios 的内部文件结构。


💻 简略源码结构:

axios/
├── lib/
│   ├── core/
│   │   ├── Axios.js              // 主类
│   │   ├── dispatchRequest.js    // 发请求
│   │   ├── InterceptorManager.js // 管理拦截器
│   └── helpers/
│       ├── buildURL.js
│       ├── parseHeaders.js
│   └── adapters/
│       ├── xhr.js                // 浏览器请求实现
│       ├── http.js               // Node 请求实现

💬 解释:

Axios 源码其实就分三层:

  • 核心层(core) :负责逻辑;
  • 适配层(adapter) :根据运行环境(浏览器 / Node)选择实现;
  • 工具层(helper) :做字符串拼接、参数处理。

🧋 奶茶铺类比:

  • core:奶茶制作流程图;
  • adapter:不同城市的厨房标准;
  • helper:搅拌机、小勺子、吸管等小工具。

🧠 小可爱口诀总结:

🍵 Axios 底层像“自动奶茶工厂”:

  • Interceptor:质检员(前后检查)
  • dispatchRequest:外卖小哥(执行请求)
  • Promise 链:传送带(顺序执行)
  • Adapter:不同城市厨房(兼容环境)

🎯 一句话总结(第 160 页)

Axios 是一个基于 Promise 的 HTTP 工厂, 内部通过拦截器链串联起「请求 → 响应 → 错误」全过程, 让开发者能随时插入逻辑,而不用重复写样板代码。

🧋 奶茶铺版总结:

顾客下单 → 前台核对会员 → 厨房制作 → 质检出餐 → 客服上报。 一套流程串起来,就叫「Axios 的请求链」。

🌟 第 161 页

🧩 一、Axios 源码结构梳理

首先出现的是源码目录结构(lib 文件夹): 看起来很复杂,其实就像一个“奶茶连锁总部”,每层有分工。


📁 源码结构解释(结合类比)

文件夹作用奶茶店类比
core/Axios 核心逻辑(类、拦截器、请求调度)主厨房 🍳
adapters/根据环境选择请求方式(XHR / HTTP)不同城市的分店厨房 🍱
helpers/工具函数(参数拼接、校验)奶茶搅拌机、小勺子 🥄
cancel/请求取消机制顾客退单系统 ❌
defaults.js全局配置总部统一菜单 📋
axios.js入口文件前台收银系统 💁
index.js对外导出统一接口“奶茶总店入口大门” 🏠

🧋 比喻总结:

你点一杯奶茶,Axios 会经过: 收银员(axios.js) → 厨房(core) → 工具间(helpers) → 可能切换厨房(adapter) → 若退单则进入 cancel 模块。


🌟 第 162 页

☕ 二、Axios 核心逻辑(类定义)

这页开始展示了核心类 Axios 的源码。 它相当于“奶茶制作流程总控官” 👨‍🍳。


💻 关键代码:

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

💬 通俗解释:

  • defaults:默认配置(如 baseURL、headers);
  • interceptors:请求和响应的“质检员队列”。

🧋 奶茶铺类比:

defaults 是“总部菜单模板”, interceptors 是“质检员组”——一个管下单前检查、一个管出餐后检测。


💻 核心执行函数:

Axios.prototype.request = function(config) {
  const chain = [dispatchRequest, undefined];
  let promise = Promise.resolve(config);
​
  this.interceptors.request.handlers.forEach(i => chain.unshift(i.fulfilled, i.rejected));
  this.interceptors.response.handlers.forEach(i => chain.push(i.fulfilled, i.rejected));
​
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
​
  return promise;
};

💬 解释逻辑:

Axios 把请求分为“三步走”: ① 先经过请求拦截器(打标签) ② 执行真正的请求(dispatchRequest) ③ 再过响应拦截器(包装数据)

🧋 奶茶类比:

顾客下单 → 收银员检查是否会员(请求拦截) 厨房制作奶茶(dispatchRequest) 出餐台检查甜度、杯封(响应拦截) 最后送达顾客。


🌟 第 163 页

🚀 三、dispatchRequest(真正发请求)

这是 Axios 里“出单系统”,负责把订单发到厨房去。🍶


💻 关键源码:

function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  const adapter = getDefaultAdapter();
  return adapter(config).then(
    response => response,
    error => Promise.reject(error)
  );
}

💬 通俗解释:

  • 检查订单是否被取消;
  • 找到适合的“厨房模块”(浏览器用 XHR,Node 用 HTTP);
  • 发出请求;
  • 返回响应或错误。

🧋 奶茶铺类比:

厨房先看订单是不是被顾客退了; 如果没退,就交给相应分店厨房做; 如果出了错(比如原料没了),就返回报错。


🌟 第 164 页

❌ 四、CancelToken(取消请求机制)

这页重点!Axios 的取消请求机制超实用!


💻 核心代码:

function CancelToken(executor) {
  let resolvePromise;
  this.promise = new Promise(resolve => {
    resolvePromise = resolve;
  });

  executor(function cancel(message) {
    resolvePromise(message);
  });
}

💬 通俗解释:

这是一个“可控的 Promise”。 你调用 cancel() 时,会触发这个 Promise,让 Axios 停止请求。


💻 使用方式:

const source = axios.CancelToken.source();
​
axios.get('/user', { cancelToken: source.token });
​
source.cancel('用户取消了订单');

🧋 奶茶店类比:

顾客点完单突然说:“啊,我不喝了!” 系统立刻:

  • 标记订单状态为「已取消」;
  • 厨房收到信号停手;
  • 外卖员不会出发。🚫

🧠 小可爱口诀:

CancelToken = “顾客退单令”

  • 拿到令牌:source.token
  • 退单操作:source.cancel()
  • 请求执行前检查:throwIfCancellationRequested()

🌟 第 165 页

🧠 五、Axios 源码分析总结

最后这一页是收尾,把前几页的内容串成一条完整“出餐线”。


💻 汇总伪代码(简化版):

axios.interceptors.request.use(addToken);
axios.interceptors.response.use(parseData);

axios({
  url: '/api/user',
  method: 'get'
});

运行流程是:

request.use() → dispatchRequest() → adapter() → response.use()

🧋 奶茶店流程图 🍹:

顾客下单
 ↓
前台检查会员卡(请求拦截器)
 ↓
厨房制作奶茶(dispatchRequest)
 ↓
质检台封口检查(响应拦截器)
 ↓
外卖送达

🎯 小可爱记忆口诀总表:

模块含义奶茶店比喻
Axios 类核心调度中心奶茶总经理 👨‍🍳
dispatchRequest发请求厨房制作奶茶 🍳
InterceptorManager管拦截器质检部门 🧾
CancelToken取消机制退单系统 ❌
Adapter适配器(XHR / HTTP)不同城市分店厨房 🏙️
Promise 链执行流程传送带 🎢

🍵 一句话总结(第 165 页)

Axios 是一个「自动化外卖奶茶工厂」:

  • 前台统一接单(axios.js)
  • 加料检验(request interceptor)
  • 厨房制作(dispatchRequest)
  • 出餐验货(response interceptor)
  • 顾客可退单(CancelToken)
  • 最后总部做统计分析(日志系统)

🧋 这样你就能一眼看懂源码思路,而不是死背函数名啦!

🌟 第 166 页

🍹 一、axios.all() —— 多个请求一起发(批量点单)


💻 代码:

axios.all([
  axios.get('/user'),
  axios.get('/order')
]).then(axios.spread((user, order) => {
  console.log('用户信息:', user);
  console.log('订单信息:', order);
}));

💬 解释:

  • axios.all() 就像「一次性点多杯奶茶」。 → 同时发出多个请求。
  • axios.spread() 是把所有结果摊开来(像拆奶茶托盘)。

🧋 类比:

顾客一次点三杯奶茶(多请求), 厨房同时制作三杯, 出餐后把三杯装在同一个托盘里(axios.all), 前台服务员把它们一个个摆出来给顾客看(axios.spread)。


🧠 小可爱记忆法:

“all 是一起点单,spread 是拆奶茶。” ☕

axios.all() → 多个请求同时执行 ✅ axios.spread() → 拿到所有结果后分别处理


🌟 第 167 页

🚀 二、并发请求控制 —— 不让厨房太忙


在真实项目中,不能同时发太多请求(比如 50 个接口一起冲), 否则就像奶茶店来 50 个顾客一起喊:“我都要!” 👉 厨房直接炸锅。💥


💻 简易实现:

class RequestQueue {
  constructor(limit) {
    this.limit = limit;
    this.running = 0;
    this.queue = [];
  }

  add(task) {
    this.queue.push(task);
    this.run();
  }

  run() {
    if (this.running >= this.limit || !this.queue.length) return;
    const task = this.queue.shift();
    this.running++;
    task().finally(() => {
      this.running--;
      this.run();
    });
  }
}

💬 解释:

  • limit:最多能同时跑几个请求;
  • queue:等待中的任务队列;
  • running:当前执行中的数量;
  • 每当一个任务完成,就自动取下一个继续执行。

🧋 奶茶铺类比:

厨房一次最多只能做 3 杯奶茶(limit=3)。 第 4 个顾客来时,要先排队。 等其中一杯做完(finally)→ 厨房空出机器 → 下一单进来。


💡 小可爱口诀:

“同时泡茶不过三,做完一杯下一单。” 🍵

✅ 限流 = 保护厨房(浏览器)不崩溃。 ✅ 队列机制 = 保证排队公平、有序。


🌟 第 168 页

⚙️ 三、封装请求方法(axios.get/post...)


为了方便使用,Axios 对请求方法做了语法糖封装:


💻 代码:

['get', 'post', 'put', 'delete'].forEach(method => {
  axios[method] = function(url, data, config) {
    return axios(Object.assign({}, config, {
      method,
      url,
      data
    }));
  };
});

💬 解释:

  • axios 自动挂上 .get().post() 等方法;
  • 本质上都还是调用 axios(config)
  • 只是让你写起来更优雅。

🧋 奶茶店类比:

顾客不用每次说: “我想点一杯红茶,加糖加冰外带” 总部干脆给你四个快捷按钮:

  • get() → 外带奶茶
  • post() → 新订单
  • put() → 修改订单
  • delete() → 取消订单

🍰 小可爱口诀:

“四个按钮一键点,底层其实全一样。”

.get() → 查询 ✅ .post() → 新建 ✅ .put() → 更新 ✅ .delete() → 删除


🌟 第 169 页

🪄 四、axios.create() —— 自定义实例


有时候不同模块需要不同配置,比如:

  • 用户中心请求 /api/user/
  • 订单中心请求 /api/order/

这时就要“开分店”。


💻 代码:

const userAxios = axios.create({
  baseURL: '/api/user',
  timeout: 3000
});

const orderAxios = axios.create({
  baseURL: '/api/order',
  timeout: 5000
});

💬 解释:

  • 每个实例都有自己的默认配置;
  • 互相独立,互不干扰。

🧋 类比:

就像奶茶品牌在不同城市开分店:

  • 上海分店(userAxios)主打速度快;
  • 北京分店(orderAxios)菜单不一样。

💡 小可爱口诀:

“不同城市开分店,菜单配置各自干。” 🏙️

axios.create() → 复制 Axios 模板,做定制版。


🌟 第 170 页

🧩 五、Axios 源码核心调用链(总结)


这一页是本章压轴!总结 Axios 全流程:


💻 调用关系:

axios()
 → dispatchRequest()
   → adapter()(XHR / HTTP)
     → 处理响应数据
       → 执行拦截器链
         → 返回 Promise

🧋 奶茶铺全流程:

顾客下单(axios)
↓
收银处理(dispatchRequest)
↓
厨房执行(adapter)
↓
质检出餐(interceptor)
↓
送餐到顾客(Promise

💡 核心思想:

Axios 通过「Promise 链」把每个阶段串起来, 所有的拦截器、配置、取消逻辑,全都按顺序跑完。


🎯 最终总结(小可爱专属版):

模块功能奶茶类比
axios()主入口顾客下单
dispatchRequest()发送请求收银派单
adapter()环境适配厨房选址(浏览器/Node)
interceptors拦截器质检台
CancelToken取消请求退单系统
Promise异步链奶茶传送带
axios.create()创建实例开分店
axios.all()并发执行批量点单

🧋 一口气总结:

Axios 就像「全球连锁奶茶自动工厂」:

  • 顾客点单(axios)
  • 前台接单(request interceptor)
  • 厨房制作(dispatchRequest)
  • 质检打包(response interceptor)
  • 顾客喝奶茶(Promise 返回)
  • 如果反悔?退单(CancelToken)

🧋第 171–172 页:Axios 实战封装篇(会员系统版)


💡背景:

项目里如果每个地方都 axios.get() / axios.post(), 那就像每个奶茶员工都自己调糖、加料—— 混乱、重复、难维护 😩。

👉 所以我们要做一个“总部统一配方表”——Axios 二次封装


💻 代码核心(第 171 页):

import axios from 'axios';
import { ElMessage } from 'element-plus';
​
const request = axios.create({
  baseURL: '/api',
  timeout: 5000
});
​
// 请求拦截器
request.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
}, error => Promise.reject(error));
​
// 响应拦截器
request.interceptors.response.use(
  response => {
    if (response.data.code !== 200) {
      ElMessage.error(response.data.message);
      return Promise.reject(response.data);
    }
    return response.data;
  },
  error => {
    ElMessage.error('网络出错,请稍后重试');
    return Promise.reject(error);
  }
);
​
export default request;

💬通俗解释:

部分含义奶茶店类比
axios.create()创建独立实例新开一家“分店”
请求拦截器请求前加 token检查顾客会员卡
响应拦截器响应后统一处理检查奶茶品质,有问题就退单
ElMessage弹窗提示“提示:会员积分不足~”

🧋类比总结:

顾客点单 → 检查会员卡(request) 奶茶出锅 → 检查是否合格(response) 总部统一菜单和提示逻辑(axios.create)


🧠小可爱记忆口诀:

「封装 axios 三步走」 1️⃣ 创建实例(create) 2️⃣ 拦截请求(加会员卡) 3️⃣ 拦截响应(检查出餐)


🍧第 172 页:Axios 工作流程图讲解


图片中是一个 流程图,讲的是 Axios 执行过程 👇

axios() → 拦截器 → dispatchRequest → adapter(XHR) → 响应拦截 → 返回Promise

💬解释:

1️⃣ axios() :发起请求(顾客点单) 2️⃣ 拦截器:请求过滤、加 token(检查会员卡) 3️⃣ dispatchRequest() :实际执行请求(厨房制作奶茶) 4️⃣ adapter() :根据环境选择方式(Node 厨房 / 浏览器厨房) 5️⃣ 响应拦截器:出餐检测、错误提示(质检员) 6️⃣ 返回 Promise:返回结果(顾客拿到奶茶)


🧋一句话总结:

Axios 就是一条自动化“点单—制作—检测—出餐”的流水线 🍹。


🍰第 173–175 页:Vue 权限管理系统(安全防伪篇)


💡 背景:

在项目里,不同的人(比如顾客、店员、管理员) 能访问的功能是不一样的。

比如:

  • 顾客只能点奶茶;
  • 店员能看订单;
  • 管理员能看财务报表。

👉 这就叫「权限管理」。


📖 第 173 页:什么是权限?


💬 概念解释:

权限(Permission)就是对系统中资源(页面、接口、按钮)的访问控制规则

简单来说:

  • 谁(用户)
  • 能做什么(操作)
  • 在哪儿做(页面)

🧋奶茶铺比喻:

顾客(普通用户)→ 只能喝奶茶。 店员(中级权限)→ 能做奶茶、改订单。 管理员(高级权限)→ 能查账、开新店。


🍵第 174 页:权限管理主要分哪几类?


📊 书上列了五种常见方式:

权限类型含义奶茶铺类比
菜单权限控制哪些页面能看到哪个员工能进仓库、前台
按钮权限控制具体操作有的员工能“删除订单”,有的不能
路由权限控制前端跳转顾客不能访问“财务页”
接口权限控制后端 API某些员工调用不了“退款接口”
数据权限控制数据范围A 店长只能看自己店的数据

🧋形象总结:

权限系统就是奶茶店的“门禁 + 岗位分工表”。


🪄第 175 页:实现思路(核心代码)


💻 代码段:

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token');
  if (!token && to.path !== '/login') {
    next('/login');
  } else {
    next();
  }
});

💬解释:

  • 每次路由跳转前(页面切换),检查是否登录;
  • 没登录就强制跳回登录页。

🧋奶茶店类比:

顾客想进“后台仓库”(管理页) 门禁扫描(路由守卫)发现你没员工卡(token) 🚫 把你拦回大门(登录页)!


💡额外代码:

if (userRole === 'admin') next();
else if (to.meta.role === 'user') next();
else next('/403');

💬意思是:

根据用户角色决定能不能进入该页面, 否则跳转到 403 页面(无权限提示)。

🧋奶茶铺类比:

店员想进“财务室”? 门禁提示:“权限不足,请联系店长。” 😂


🍓 小可爱口诀总结

🧃 权限管理三步记: 1️⃣ 登录判断(有无 token) 2️⃣ 角色判断(谁能进哪) 3️⃣ 动态加载路由(能进的路才显示)


🎯 最后总结(第 171–175 页)

模块核心思想奶茶铺类比
Axios 封装请求前后统一逻辑总部菜单统一制作流程
拦截器请求 / 响应检查会员卡检查 + 奶茶质检
权限控制用户访问限制门禁 + 岗位分级
路由守卫登录前拦截门口保安
按钮权限细粒度控制店员能不能按“退单”键

🧋一句话复盘:

Axios 封装是“自动做奶茶系统”, 权限控制是“谁能进奶茶店干活”的规则。 两者结合,项目既高效又安全 ✨。

这几页(📘第 176–180 页)主要讲的内容是:

🌟 Vue 动态路由权限控制(不同角色看到不同页面) 🌟 前端如何结合角色去加载菜单、守卫路由 🌟 小结:如何让“登录账号 → 自动加载权限路由”全过程跑通

我来用最通俗、最奶茶风格的方式讲!🍹


🌸 第 176 页:动态路由 routesMap 配置

这页出现了一个代码块:

export const routesMap = [  {    path: '/dashboard',    name: 'Dashboard',    meta: { title: '首页', roles: ['admin', 'user'] },
    component: () => import('@/views/dashboard/index.vue')
  },
  {
    path: '/order',
    name: 'Order',
    meta: { title: '订单管理', roles: ['admin'] },
    component: () => import('@/views/order/index.vue')
  },
  {
    path: '/finance',
    name: 'Finance',
    meta: { title: '财务报表', roles: ['admin'] },
    component: () => import('@/views/finance/index.vue')
  },
  {
    path: '/404',
    component: () => import('@/views/404.vue'),
    hidden: true
  }
];

💬 通俗解释:

这个数组 routesMap 就是项目所有页面的“原始菜单表”。

字段含义奶茶店比喻
path页面路径哪个房间(厨房 / 仓库 / 前台)
meta.title页面标题房间门口的名字
roles哪些角色能进哪些员工能进这个房间
component对应组件房间的布局文件
hidden是否显示菜单门是否对外开放

🧋奶茶店类比:

总部(routesMap)有整栋大楼的“地图”:

  • 前台大厅(Dashboard)→ 所有人能进
  • 订单管理室(Order)→ 仅管理员能进
  • 财务报表室(Finance)→ 仅管理员能进
  • 404 小黑屋(404)→ 谁都不想进去 😂

🧠 小可爱记忆口诀:

“路径是门号,roles 是钥匙。”

  • 有钥匙的角色才能进对应的房间。
  • routesMap 是整栋大楼的“地图模板”。

🌟 第 177 页:前端路由守卫(router.beforeEach)

这页非常重要,它展示了 Vue 的“守门员机制”。🚪


💻 代码核心:

import router from './router';
import store from './store';
​
router.beforeEach(async (to, from, next) => {
  const token = localStorage.getItem('token');
  if (!token && to.path !== '/login') {
    next('/login');
  } else {
    const hasRoles = store.getters.roles && store.getters.roles.length > 0;
    if (!hasRoles) {
      const roles = await store.dispatch('user/getUserInfo');
      const accessRoutes = await store.dispatch('permission/generateRoutes', roles);
      accessRoutes.forEach(route => router.addRoute(route));
      next({ ...to, replace: true });
    } else {
      next();
    }
  }
});

💬 通俗解释:

1️⃣ 每次页面切换时,都会触发 beforeEach; 2️⃣ 它会检查:

  • 有没有登录(token)?
  • 如果没登录 → 跳回登录页;
  • 如果登录了但还没加载角色 → 请求后端拿角色;
  • 根据角色生成可访问路由;
  • 把能访问的页面“动态加进”路由表;
  • 最后放行(next())。

🧋奶茶铺类比:

想进奶茶总部的房间,门口有保安 👮‍♂️:

  • 没员工卡(token)→ 不让进;
  • 有员工卡但没分配权限 → 去后台查岗位(getUserInfo);
  • 拿到权限表后 → 发门禁钥匙(generateRoutes);
  • 最后才放行(next)。

🧠 小可爱口诀:

“每进一扇门,都要验卡验身份。”

token 检查登录状态 ✅ roles 检查权限角色 ✅ addRoute 动态加载菜单


🌟 第 178 页:动态生成可访问路由


💻 代码示例:

export function filterAsyncRoutes(routes, roles) {
  const res = [];

  routes.forEach(route => {
    const tmp = { ...route };
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles);
      }
      res.push(tmp);
    }
  });

  return res;
}

💬 通俗解释:

这个函数是「筛选路由」的核心逻辑。

  • 遍历所有路由;
  • 调用 hasPermission() 判断是否有权限;
  • 如果有,就留下;
  • 如果有子菜单,也递归判断。

💻 权限判断函数:

function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role));
  } else {
    return true;
  }
}

💬 意思:

如果这个路由有 roles 限制,就判断当前角色是否在其中; 如果没写 roles 限制,那所有角色都能访问。


🧋奶茶铺类比:

总部拿到整栋楼的门锁表(routesMap), 挨个门去试钥匙 🔑:

  • 能开就留下;
  • 打不开就跳过;
  • 有楼中楼(children)就递归试一遍。

🧠 小可爱口诀:

“筛选菜单三步走: 遍历 → 判断 → 递归。”


🌟 第 179 页:store 模块管理(用户信息 + 权限)

这页是 Vuex 里 userpermission 模块的结合逻辑。


💻 简化版代码:

const state = {
  roles: []
};
​
const mutations = {
  SET_ROLES: (state, roles) => {
    state.roles = roles;
  }
};
​
const actions = {
  async getUserInfo({ commit }) {
    const { data } = await getUserInfo();
    commit('SET_ROLES', data.roles);
    return data.roles;
  }
};

💬 通俗解释:

Vuex 的 user 模块就像“员工档案室”:

  • 登录时从后端拿到角色信息;
  • 保存到 Vuex 的 roles 数组里;
  • 其他地方(比如路由守卫)就能直接用。

🧋奶茶铺类比:

“员工档案室”里记录着:

  • 员工姓名;
  • 职位;
  • 权限范围; 守卫在查门禁时,就会先问档案室:“这个人能进这扇门吗?”

🌟 第 180 页:前端动态菜单渲染

最后一页讲的是,如何根据 routes 生成菜单。


💻 代码:

const menuList = routes.filter(item => !item.hidden);
menuList.map(item => ({
  name: item.meta.title,
  path: item.path
}));

💬 意思是:

把能显示的路由(没有 hidden 的)提取出来, 显示成菜单列表。


🧋奶茶铺类比:

总部根据可访问的房间(routes)生成“导航栏”:

  • 顾客能看到前台大厅;
  • 店员能看到“订单管理”;
  • 财务能看到“账目报表”。

🧠 小可爱口诀总结(第 176–180 页)

功能解释奶茶店类比
routesMap所有页面的配置表总部的整栋楼地图 🗺️
router.beforeEach页面跳转检查门口保安查卡 🚪
filterAsyncRoutes按角色筛选页面保安发对应门禁卡 🔑
Vuex roles保存角色信息员工档案室 🧾
菜单渲染根据权限展示菜单前台显示能去的房间

🧋一句话记忆:

Vue 动态权限系统 = “总部地图 + 门禁保安 + 员工档案 + 菜单展示”

流程如下👇:

登录 → 获取角色信息 → 过滤能进的路 → 动态添加路由 → 显示菜单

🍓 第 181 页:按钮权限控制(能不能点这个按钮)


💻 代码:

import store from '@/store';
​
export function hasPermission(el, binding) {
  const { value } = binding;
  const roles = store.getters && store.getters.roles;
​
  if (value && value instanceof Array && value.length > 0) {
    const permissionRoles = value;
    const hasPermission = roles.some(role => {
      return permissionRoles.includes(role);
    });
​
    if (!hasPermission) {
      el.parentNode && el.parentNode.removeChild(el);
    }
  }
}

💬 通俗解释:

这个函数是个「自定义指令」,比如 v-hasPermission="['admin']" → 用来判断当前登录的用户有没有权限操作这个按钮。

核心逻辑: 1️⃣ 从 Vuex 里拿到当前用户角色(store.getters.roles) 2️⃣ 判断绑定的 value(传入的权限)里有没有匹配到这个角色 3️⃣ 如果没匹配到 → 把按钮直接从页面删掉 ✂️


🧋奶茶铺类比:

店员(user)和店长(admin)都用同一个点餐屏幕。 但只有店长能看到“删除订单”按钮。 普通员工登录后,系统自动隐藏这个按钮 👀。


🧠 小可爱口诀:

“权限不够,按钮消失。”

v-hasPermission → 判断角色 ✅ 没权限 → 删除元素


🍑 第 182 页:v-hasPermission 指令用法


💻 代码:

<template>
  <button v-hasPermission="['admin']">删除订单</button>
  <button v-hasPermission="['admin', 'manager']">查看报表</button>
</template>

💬 意思是:

  • 只有管理员(admin)能看到“删除订单”;
  • 管理员或经理(manager)都能看到“查看报表”。

🧋奶茶铺类比:

有的按钮就像“开保险柜的钥匙🔑”,不是所有人都有; 系统自动识别你的职位,把不该看的按钮直接藏起来。


🍋 第 183 页:接口权限控制(后端安全防线)


前端按钮控制只能“藏起来”, 但真正的安全要靠后端接口检查。


💻 代码思路(伪代码):

app.post('/deleteOrder', (req, res) => {
  const role = req.user.role;
  if (role !== 'admin') {
    return res.status(403).send('没有权限删除订单');
  }
  // 执行删除逻辑
});

💬 通俗解释:

即使有人用“偷懒的方式”在浏览器控制台执行删除请求, 后端也会再验证一次身份,没有权限的直接拒绝。


🧋奶茶铺类比:

就算前台按钮被你偷偷按出来(改了 HTML), 但你传单到后厨时,厨师还会再确认你的工号。 不是经理?不好意思,这杯奶茶不能退 😆。


🧠 小可爱口诀:

“前端藏按钮,后端设门槛。” → 双保险,安全稳!🔐


🥝 第 184 页:动态菜单 + 接口权限结合


这一页在讲如何让「前端菜单」和「后端接口」联动。


💻 代码逻辑(简版):

const asyncRoutes = [
  {
    path: '/order',
    name: 'Order',
    meta: { roles: ['admin'] },
    component: () => import('@/views/order/index.vue')
  }
];
​
store.dispatch('permission/generateRoutes', roles).then(routes => {
  router.addRoutes(routes);
});

💬 意思是:

  • 登录后从后端拿角色;
  • Vue 根据角色过滤菜单;
  • 最终加载可访问的页面。

🧋奶茶铺类比:

每个员工登录后,系统根据他的职位动态加载功能菜单。

  • 店员登录:只看到“点单”“我的任务”;
  • 店长登录:还能看到“员工管理”“销售报表”。

🍰 第 185 页:整合路由守卫 + 角色校验(全流程)


💻 代码重点:

router.beforeEach(async (to, from, next) => {
  const token = localStorage.getItem('token');
  if (!token && to.path !== '/login') {
    next('/login');
  } else {
    const roles = await store.dispatch('user/getUserInfo');
    const accessRoutes = await store.dispatch('permission/generateRoutes', roles);
    accessRoutes.forEach(route => router.addRoute(route));
    next({ ...to, replace: true });
  }
});

💬 通俗解释:

  • 第一次进入页面时,守卫检查有没有登录;
  • 如果登录了但还没拿到权限,就调用后端;
  • 根据角色生成动态路由;
  • 把这些页面注入系统;
  • 最后再放行。

🧋奶茶铺类比:

顾客来总部报到(登录), 门卫先确认他是谁(token)→ 档案室查他的职位(getUserInfo)→ 再发门禁卡(generateRoutes)→ 才能进入他该进的部门(router.addRoute)。


🍓 第 185 页末尾小结


💬 内容摘要:

Vue 权限控制要点:

  • 登录控制(token 验证)
  • 路由守卫(动态加载)
  • 按钮权限(v-hasPermission)
  • 接口权限(后端再验证)
  • Vuex 全局角色管理

🧋超级形象版总结:

权限层级解释奶茶店类比
登录控制没卡不能进门顾客/员工身份验证
路由守卫只加载能进的房间门禁系统
按钮权限该不该显示按钮哪些操作键你能点
接口权限服务器二次确认后厨再验一次工号
Vuex 权限保存职位信息员工档案数据库

🧠 小可爱记忆口诀:

「权限五连环」: 🧩 登录卡 → 门禁 → 菜单 → 按钮 → 接口

→ 一环扣一环,前后呼应!


💬 整体一句话总结(第 181–185 页):

Vue 权限系统 = 登录验证 + 动态菜单 + 路由守卫 + 按钮控制 + 接口防线

= 一家“智能奶茶总部”💼: 登录验证是大门, 动态菜单是楼层图, 路由守卫是保安, 按钮权限是操作台, 接口权限是后厨复核。

🍓 第 186 页:什么是 Keep-Alive?


💡 官方解释(翻译成小可爱语言):

<keep-alive> 是 Vue 的一个内置组件, 可以让被包裹的组件在切换时“不被销毁”, 而是“缓存起来”, 下次再回来时能立刻恢复之前的状态。


💻 示例代码:

<keep-alive>
  <router-view></router-view>
</keep-alive>

💬 解释:

  • <router-view> 是显示不同页面的地方;
  • 包上 <keep-alive> 后,这些页面不会被重新创建;
  • 再次切换回来时会保持原样(就像暂停再播放)。

🧋奶茶铺类比:

你点了一杯“草莓奶茶”,喝到一半去洗手间, 回来后奶茶还在桌上等你喝~ 🧃

如果没有 <keep-alive> 呢? 你出去回来,奶茶被服务员倒掉了,要重新点一杯 😭


🧠 小可爱记忆口诀:

「Keep-alive」就是: 👉 让组件记住上次的状态,不要每次都重来!


🍋 第 187 页:Keep-Alive 的使用场景


💬 一般在以下情况下使用:

场景举例效果
🔁 页面频繁切换A页 ↔ B页不重新加载数据
💬 表单未提交填表中切换页面返回时表单内容还在
📱 Tab切换首页 / 消息 / 我的切回来仍保留滚动位置

💻 代码示例:

<template>
  <keep-alive include="Home,About">
    <router-view></router-view>
  </keep-alive>
</template>

💬 解释:

  • include 表示哪些组件要被缓存;
  • 只有 “Home” 和 “About” 页面的状态会保留;
  • 其他页面切换时会正常销毁。

🧋奶茶铺类比:

“keep-alive” 就像给常客办了会员卡:

  • 常客的奶茶能放在柜台上;
  • 临时顾客喝完就清空。

🧠 小可爱口诀:

“include 留会员,exclude 清散客。”

👉 用 include 指定要缓存的页面, 👉 用 exclude 排除不需要缓存的页面。


🍰 第 188 页:Keep-Alive 的生命周期钩子


💻 代码:

export default {
  name: 'UserList',
  mounted() {
    console.log('组件被挂载');
  },
  activated() {
    console.log('组件被激活');
  },
  deactivated() {
    console.log('组件被缓存');
  }
};

💬 解释:

钩子函数触发时机奶茶店比喻
mounted第一次创建第一次做奶茶
activated从缓存里拿回来顾客回来继续喝
deactivated离开但被缓存奶茶先放进冰箱

🧋形象理解:

“mounted 是第一次做奶茶, activated 是顾客回来续杯, deactivated 是暂时收起来冷藏。” 🍶


🧠 小可爱口诀:

「三阶段」口诀:

  • 做奶茶(mounted)
  • 冷藏奶茶(deactivated)
  • 拿出来喝(activated)

🍎 第 189 页:Keep-Alive 的核心原理(源码简化)


💻 核心逻辑(伪代码):

setup(props, { slots }) {
  const cache = new Map(); // 缓存池
​
  const keepAlive = (vnode) => {
    const key = vnode.key || vnode.type;
    if (cache.has(key)) {
      vnode.component = cache.get(key);
    } else {
      cache.set(key, vnode.component);
    }
    return vnode;
  };
​
  return () => {
    const vnode = slots.default();
    return keepAlive(vnode);
  };
}

💬 通俗解释:

1️⃣ Vue 会维护一个“缓存池 cache”; 2️⃣ 每个组件都会有个 key 标识; 3️⃣ 当组件离开时不会被销毁,只是放进缓存池里; 4️⃣ 下次再切回来时直接从缓存里拿,不重新创建。


🧋奶茶铺类比:

Vue 有一个“大冰箱(cache)”, 每次顾客换口味时,把上一杯奶茶放进去冷藏。 当顾客又回来时,从冰箱拿出来,继续喝!❄️


🧠 小可爱口诀:

「缓存池」口诀: 新奶茶放冰箱,旧奶茶拿出来。 → 就是 Keep-Alive 的本质!


🍇 第 190 页:Keep-Alive 源码细节(补充)


这一页讲更底层的逻辑,比如:

if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
  vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE;
}

💬 翻译成人话就是:

Vue 在渲染阶段,会给被 KeepAlive 包裹的组件打上“标记”, 用来告诉系统: “这个组件不用删,下次还会用!”


🧋奶茶铺类比:

就像在奶茶上贴了标签: “🥤 李华的奶茶 - 已加珍珠,暂存冰箱,勿倒!” 😂

这样下次李华回来时,系统就能快速找到他的奶茶。


💡 整体总结(第 186–190 页)

知识点功能奶茶店类比
<keep-alive>缓存组件,防止重复创建保存奶茶不倒掉
include / exclude控制缓存范围常客 / 散客名单
activated / deactivated生命周期钩子奶茶冷藏 / 续杯
缓存原理Map 存储组件实例冰箱存奶茶

🧠 一句话总结:

Keep-Alive 就是 Vue 的“组件缓存冰箱” ❄️ 它能让页面切换更快、用户体验更丝滑~


🧋小可爱记忆口诀

🧠「做奶茶→放冰箱→再拿出」 💬 “mounted → deactivated → activated”

🏷️ “Keep-alive 是 Vue 的冰箱,用来冷藏组件。”

🍓 第 191 页:Keep-Alive 缓存细节(Map 实现)


💻 代码片段:

const cache = new Map();
cache.set('Home', instance);
cache.set('About', instance);
cache.delete('Contact');

💬 这段代码的意思是: Vue 内部用 Map 来存储被缓存的组件。

  • set():把组件放入缓存;
  • get():从缓存中取出;
  • delete():清除缓存的某个组件。

🧋奶茶铺类比:

Vue 有个“奶茶冷藏柜(cache)”, 每个奶茶(组件)用标签标上名字,比如 HomeAbout

  • 客人离开 → 奶茶放进冰柜(set)
  • 客人回来 → 拿出原奶茶(get)
  • 不常喝了 → 倒掉清空(delete)

🧠 小可爱口诀:

“三部曲:放 → 拿 → 清”

就是 keep-alive 的缓存逻辑核心。


🍋 第 192 页:缓存淘汰策略(LRU 思想)


Vue 在源码里其实使用了 LRU(最近最少使用)缓存策略


💬 通俗解释:

当缓存太多时,Vue 会自动“清理掉最久没访问”的组件。


🧋奶茶铺类比:

冰箱(缓存)空间有限, 你最近一周都没喝“西瓜冰沙”,那它就会被清理掉, 以便留出空间放新奶茶 😆。


💻 简化逻辑:

if (cache.size > max) {
  pruneCacheEntry(oldestKey);
}

💬 解释: 当缓存数量超过 max(设定上限), 就调用 pruneCacheEntry() 删除最老的那一个。


🧠 小可爱口诀:

“冰箱放不下,就先倒掉最老那杯奶茶。” 🧋


🍰 第 193 页:路由缓存结合 Keep-Alive


💻 代码:

<keep-alive :include="['Home', 'About']">
  <router-view></router-view>
</keep-alive>

💬 这行代码告诉 Vue: 只缓存 HomeAbout 页面,其他页面不缓存。


📘 在路由配置中:

{
  path: '/home',
  name: 'Home',
  meta: { keepAlive: true }
}

💬 meta.keepAlive 是告诉路由守卫“这个页面需要缓存”。


🧋奶茶铺类比:

系统在每杯奶茶的标签上写着:

  • “草莓奶茶 keepAlive=true” → 放冰箱保存
  • “香芋奶茶 keepAlive=false” → 喝完即弃

💻 路由守卫控制:

if (to.meta.keepAlive) {
  // 缓存页面
} else {
  // 不缓存
}

💬 意思就是: Vue 会根据路由元信息动态判断哪些组件要缓存,哪些不缓存。


🧠 小可爱口诀:

“meta 是奶茶标签,keepAlive 是冰箱标志。”


🍇 第 194 页:beforeRouteEnter & activated


这页开始讲 路由缓存配合生命周期 的工作机制。


💻 示例:

beforeRouteEnter(to, from, next) {
  console.log('进入页面前');
  next();
}

💬 beforeRouteEnter: 在进入某个路由(页面)前执行,可以拦截或做准备操作。


activated() {
  console.log('页面被激活');
}

💬 activated: 当页面被缓存后再次显示时触发。


🧋奶茶铺类比:

顾客要回到店里喝原来的奶茶时:

  • 门口保安先登记(beforeRouteEnter)
  • 顾客拿到奶茶开喝(activated)

🧠 小可爱口诀:

“进门前登记 → 拿奶茶续喝”

beforeRouteEnteractivated


🍎 第 195 页:SPA 单页应用(Single Page Application)


💡 什么是 SPA?

SPA(Single Page Application) 指的是“单页应用程序”, 整个网站只有一个 HTML 文件,通过 JS 动态切换页面内容。


💬 对比图(书上有):

类型页面数量每次切换行为
🧩 SPA1 个内容动态更新(不刷新页面)
📄 MPA多个每次都刷新整个页面

🧋奶茶铺类比:

SPA 就像一家“多功能奶茶铺”: 你不需要出门换店(刷新页面), 店员直接在同一个柜台上切换“点单区 / 付款区 / 我的订单”。

MPA 就像传统商场: 每次都要走出去换一家店(重新加载整个页面)。


💻 Vue 实现 SPA 的方式:

<router-view></router-view>

💬 Vue Router 实现了前端路由, 在同一个页面中切换不同组件, 让用户感觉像“多个页面”。


🧠 小可爱口诀:

“SPA 不换房,MPA 换整栋。” 🏠


🍯 总结(第 191–195 页)

知识点含义奶茶铺类比
Map 缓存存储组件实例冰箱放奶茶
LRU 策略清理旧缓存倒掉老奶茶
meta.keepAlive路由缓存控制奶茶标签:冷藏/不冷藏
beforeRouteEnter进入前钩子顾客登记入场
activated被激活钩子继续喝旧奶茶
SPA 原理单页切换同店不同柜台切换

🧋 小可爱终极口诀:

🧠「Keep-Alive 冰箱」+「路由标签」+「SPA 柜台」

👉 冰箱存页面 👉 标签管缓存 👉 柜台切内容

一句话总结:

Vue 的页面切换就像一杯奶茶

  • keep-alive冰箱
  • meta标签
  • activated续杯
  • SPA不换店只换口味 💖

🍓 第 196 页:SPA 和 MPA 的区别


书上有一张表格:

对比项SPA(单页应用)MPA(多页应用)
页面结构只有一个 HTML多个 HTML
切换方式局部刷新整页刷新
路由模式前端控制后端控制
首屏速度较慢(需加载框架)较快(直接渲染)
用户体验连贯流畅每次跳转闪屏
SEO 优化难(JS 渲染)容易(纯 HTML)

🧋通俗解释:

SPA 就像“一家多功能奶茶店”: 顾客点单、付款、查看订单都在同一个大厅完成,不用换门口(不刷新页面)。

MPA 就像“商场里的多家奶茶铺”: 每个功能是一个独立的店铺,切换就要重新开门(刷新页面)。


🧠 小可爱口诀:

「SPA 不换房,MPA 换整栋」🏠 → 前者体验更顺滑,后者更容易被搜索引擎认识。


🍋 第 197 页:SPA 的优缺点


✅ 优点:

  • 页面切换快 🚀(只更新局部内容)
  • 用户体验好 😄(没有闪屏)
  • 接口交互流畅(前后端分离)

❌ 缺点:

  • 首屏加载慢 😩(得先下载一堆 JS)
  • SEO 不友好 🕷️(搜索引擎看不到 JS 渲染内容)
  • 前端逻辑更复杂(要自己管路由、缓存、状态)

🧋奶茶铺类比:

想象你进了个“超级奶茶店”:

  • 店里有点单区、付款区、会员区,都在同一个大厅。
  • 一开始要加载整个菜单、灯光系统(首屏慢)。
  • 但之后你点单切区都超快(体验顺滑)。

🧠 小可爱口诀:

「一次加载,处处畅快;首屏变慢,SEO哭哭 😭」


🍰 第 198 页:实现一个简易 SPA(手写路由原理)


这一页是最精彩的部分!我们来拆代码 ✨


💻 核心逻辑(基于 hash 模式):

class Router {
  constructor() {
    this.routes = {};
    window.addEventListener('hashchange', this.load.bind(this));
  }

  register(path, callback) {
    this.routes[path] = callback;
  }

  load() {
    const hash = location.hash.slice(1);
    this.routes[hash] && this.routes[hash]();
  }
}

const router = new Router();

router.register('home', () => console.log('🏠 Home'));
router.register('about', () => console.log('📖 About'));

💬 通俗解释:

1️⃣ 监听 hashchange 事件(URL # 后面的变化) 2️⃣ 每次切换时,提取路径(location.hash) 3️⃣ 根据路径执行对应的页面回调


🧋奶茶铺类比:

顾客点外卖时:

  • 你的外卖单地址上写着 “/#home” 就送到前台;
  • 写 “/#about” 就送到关于我们页面。

URL 的 “#” 就像送奶茶的“暗号”。


🧠 小可爱口诀:

“看 # 号,切页面。” → Hash 路由就是靠这个实现页面切换的。


🍎 第 199 页:History 模式的原理


💻 代码(简化):

class HistoryRouter {
  constructor() {
    this.routes = {};
    window.addEventListener('popstate', this.load.bind(this));
  }

  register(path, callback) {
    this.routes[path] = callback;
  }

  push(path) {
    history.pushState({}, null, path);
    this.routes[path] && this.routes[path]();
  }

  load() {
    const path = location.pathname;
    this.routes[path] && this.routes[path]();
  }
}

💬 通俗解释:

  • hash 模式依赖 URL 的 “#”;
  • history 模式直接操作浏览器历史记录(不带 #)。

🧋奶茶铺类比:

Hash 模式:靠暗号点奶茶,比如 /#menu。 History 模式:直接报房间号 /menu

区别在于:

  • hash 模式不需要服务器配置(简单粗暴);
  • history 模式看起来更优雅,但需要后端配合,否则刷新会 404 🚨。

🧠 小可爱口诀:

“Hash 用 #,History 真地址; 前者简单,后者高级。” 🌈


🍇 第 200 页:SPA 与 SEO 的关系


这一页讲 为什么 SPA 不利于 SEO,以及如何优化。


💬 原因:

搜索引擎爬虫抓取 HTML 内容时,不会执行 JavaScript。

SPA 页面靠 JS 渲染,所以爬虫看不到内容(空白页)。


💡 解决方案:

方案说明
SSR(服务端渲染)在服务器预先生成 HTML,再返回给浏览器
预渲染 prerender构建时生成静态 HTML 页面
动态渲染针对爬虫返回特殊版本 HTML

🧋奶茶铺类比:

🧋 普通 SPA 像「现场做奶茶」,顾客来了才开始搅拌。 搜索引擎(爬虫)太懒,不等你做完就走了~

SSR 就是「提前做好奶茶,直接端上」, 爬虫一眼看到完整菜单,开心收录!✨


🧠 小可爱口诀:

“SPA 动态做奶茶,SEO 不等人。 SSR 提前备好杯,搜索更轻松~”


🍯 总结(第 196–200 页)

知识点关键点奶茶店类比
SPA vs MPA单页 vs 多页一家多柜台 vs 多家小店
SPA 优缺点快但首屏慢,SEO 差大厅体验好但菜单重
Hash 模式# 实现切换暗号点单
History 模式用真实路径报房号点单
SEO 优化SSR / 预渲染提前备好奶茶,方便爬虫

💬 一句话总结:

SPA 就是一家“智能奶茶大厅”:

  • Hash 是门口暗号
  • History 是真实地址
  • SSR 是提前做好奶茶让爬虫满意 🍵

🍓 第 201 页:SPA 首屏加载慢的原因与解决思路


书上开头的流程图展示了一个 SPA 的加载流程: 用户访问 → Nginx 接收请求 → Node 服务渲染 → PhantomJS(预渲染)生成页面 → 返回结果。


💬 翻译成大白话:

用户打开网站 → 前端需要加载很多 JS、CSS、资源文件才能渲染出第一个页面。 所以“第一次打开”速度会比 MPA 慢。


🧋奶茶铺类比:

第一次开奶茶店时,顾客进门你要:

  • 开灯
  • 开奶茶机
  • 加热珍珠
  • 打印菜单

这些准备都做完了,奶茶才上桌 🍹。

所以“首屏”就像“开店第一天”——准备慢,但之后就很快啦!


🍋 第 202 页:什么是首屏加载?


📘 定义:

首屏加载(First Screen Load)指用户第一次打开页面到看到主要内容所花的时间。

💬 通俗地说:

从“空白页”变成“页面能看见文字和图片”的那一刻。


💻 如何测量?

可以用性能 API:

window.addEventListener('load', () => {
  const time = performance.now();
  console.log(`首屏加载耗时:${time}ms`);
});

💬 意思是:

  • performance.now() 返回从页面加载开始到现在的时间(毫秒);
  • load 事件表示页面和资源都加载完。

🧋奶茶铺类比:

顾客进店 → 等店员开灯、准备机器、煮珍珠 → 当第一杯奶茶端上桌 🍶 的时间,就是首屏加载时间。


🧠 小可爱口诀:

“从进门到看到奶茶” = 首屏加载 🕐


🍰 第 203 页:加载慢的原因


书里总结了几个主要原因:

🚧 1. 打包文件过大

  • JS / CSS 全部打成一个文件,导致下载慢。

🧱 2. 资源阻塞渲染

  • 大图片、字体文件加载时间长,浏览器得等。

🔁 3. 网络延迟

  • 用户离服务器远 or 网络差。

⚙️ 4. JS 逻辑复杂

  • 页面初始化逻辑太多,加载完后还得执行。

🧋奶茶铺类比:

  • 菜单太大(打包太多)
  • 奶茶材料太多(资源太重)
  • 顾客离店太远(网络慢)
  • 店员磨蹭太久(JS 执行慢)

🧠 小可爱口诀:

“太大、太多、太远、太慢” 就是首屏慢的四大罪魁祸首 😆


🍇 第 204 页:首屏优化方案


✅ 一、代码分包(懒加载)

const Home = () => import('@/views/Home.vue');

💬 含义:

  • 不一次性加载所有页面;
  • 访问哪个页面再加载哪个页面的 JS。

🧋奶茶铺类比:

别一口气煮十桶奶茶 🍵 顾客点什么再现煮什么。


✅ 二、使用 CDN 加速

💬 把静态资源放在全球加速服务器上,让用户访问最近的节点。

🧋比喻:

顾客在上海,奶茶原料从上海仓库调,不用跑去北京仓取。


✅ 三、服务端渲染 SSR / 预渲染

💬 SSR:在服务器生成 HTML,浏览器直接看到成品。 💬 预渲染:打包时提前生成静态 HTML 文件。

🧋奶茶铺类比:

SSR 就像在后厨提前做好奶茶,顾客一进门就能喝; 预渲染就像提前摆好样品奶茶,让顾客一看就有食欲 😋。


✅ 四、使用骨架屏(Skeleton Screen)

💬 意思是在内容加载前先显示“灰框架”,防止页面一片空白。

🧋奶茶铺类比:

还没上奶茶时,先上个“奶茶模型”——让顾客觉得页面有东西,不焦虑 🧘。


✅ 五、图片懒加载(lazy load)

<img v-lazy="imgUrl" />

💬 当图片滚动到可视区域时再加载。

🧋奶茶铺类比:

不要一次煮完 100 杯奶茶,顾客看到第几杯就做第几杯~


🧠 小可爱口诀:

“分包懒加载,骨架防空白,提前端奶茶,CDN送外卖~” 🍵


🍎 第 205 页:深入细节优化


💻 代码优化示例:

app.use(VueLazyload, {
  preLoad: 1.3,
  error: 'error.png',
  loading: 'loading.gif',
});

💬 意思:

  • preLoad: 提前加载距离(屏幕下方多远就开始加载)
  • error: 图片加载失败时显示的替代图
  • loading: 图片加载中显示的占位图

🧋奶茶铺类比:

顾客还在门口,就提前开始煮奶茶; 要是奶茶翻车(加载失败),就放个“抱歉卖完了”牌子 😂。


📦 Tree Shaking(摇树优化)

💬 意思是: 打包时自动去掉没用的代码,只保留用到的模块。

🧋奶茶铺类比:

菜单上 100 种奶茶,但只有人点了 10 种, 厨房只准备那 10 种的原料,其余都不进货,节省时间和空间!


🧠 小可爱口诀总结(第 201–205 页)

优化方向核心思路奶茶铺类比
代码分包按需加载只煮顾客点的奶茶
CDN 加速靠近用户最近仓库送货
预渲染/SSR提前生成页面提前做奶茶上样品
骨架屏占位加载模型奶茶防空白
懒加载延迟图片顾客看到才煮
Tree Shaking去掉没用代码不囤原料,节省成本

🧋 一句话总结:

SPA 首屏优化就是—— “少煮奶茶、提前备货、离顾客近、别让他等空桌!” 🍹

🍓 第 206 页:首屏优化的进阶——异步加载与并行


💻 示例代码:

const comp = defineAsyncComponent(() => import('@/components/Login.vue'));

💬 解释: 这行代码是 Vue3 的 异步组件加载, 意思是:组件在真正“用到”的时候才去加载对应文件。


🧋奶茶铺类比:

顾客没点“抹茶拿铁”之前, 店员不会提前煮抹茶、也不会浪费时间准备它~

👉 这就是「按需加载」,让页面初次打开更快。


✅ 优点:

  • 减少首屏 JS 体积
  • 加快页面渲染速度
  • 按需加载组件

🧠 小可爱口诀:

「不用不煮,用时再冲」☕ ——异步加载的真谛


🍋 第 207 页:并行加载与懒加载


💻 示例:

import { defineAsyncComponent } from 'vue'export default {
  components: {
    AsyncLogin: defineAsyncComponent(() => import('@/components/Login.vue')),
  }
}

💬 这就是“懒加载组件”,页面滚动到某处或某个逻辑触发时才加载。


🧋奶茶铺类比:

顾客只有点到「隐藏菜单」时,店员才去后厨拿配料。 一切讲究“你点我才做”。


⚙️ 常配合:

  • v-if 判断是否展示
  • IntersectionObserver 监听页面滚动(实现懒加载)

🧠 小可爱口诀:

「眼见为实,滚到才来」😆 ——图片、组件懒加载的灵魂


🍰 第 208 页:Vue 打包后刷新页面 404 的原因


书上问的经典题 👇

❓“为什么 Vue 项目在本地跑得好好的,打包部署到服务器后刷新就 404?”


💬 原因解析:

  • Vue 是 前端路由(History 模式)。
  • 当你刷新页面时,浏览器会请求真实路径,比如 /home
  • 服务器上却没有这个文件夹,于是返回 404。

🧋奶茶铺类比:

前端路由就像“导航在脑子里”的外卖员, 他知道 /menu/order 是内部路径。

但服务器是“门卫大爷”, 你刷新一下(重新请求路径),他就以为你要找 /menu 文件夹,结果找不到~ 🚫


💻 示例(打包后访问 404):

export default {
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About },
  ]
}

当用户访问 http://example.com/about 时:

  • 前端知道这是路由 → 展示 About 页面
  • 服务器以为你要访问 /about 文件 → 找不到,返回 404

🧠 小可爱口诀:

“前端假导航,服务器真迷路” 🗺️


🍇 第 209 页:Hash 模式为什么不会 404


💡 原因:

hash 模式的 URL 形如:

http://example.com/#/about

# 后面的内容(#/about)不会被服务器解析, 它只在浏览器内部起作用。


💬 所以服务器永远只返回 index.html, 然后由 Vue Router 决定展示哪个组件。


🧋奶茶铺类比:

# 就像是“暗号”, 门卫(服务器)不懂暗号,只管让你进大厅。 你进门后才自己找座位。


🧠 小可爱口诀:

「# 是暗号,门卫不管事; 刷新不迷路,全靠前端识。」🔐


🍎 第 210 页:解决方案(History 模式)


✅ 1. 配置 Nginx 重定向

location / {
  try_files $uri $uri/ /index.html;
}

💬 意思是: 如果服务器找不到你请求的路径,就默认返回 index.html, 再由前端路由去渲染正确的页面。


🧋奶茶铺类比:

门卫大爷接到命令: “不管客人要找哪个房间,先送去大厅(index.html), 前台再带他去正确的座位(Vue 路由)。” 😎


✅ 2. 使用 Hash 模式(简单粗暴)

直接改成:

createRouter({
  history: createWebHashHistory(),
})

💬 加上 #,就不会走服务器路径,不会 404。


✅ 3. 部署时加 fallback 支持

对于一些静态服务器(如 Vercel、Netlify), 需要手动开启 redirectindex.html 的功能。


🧠 小可爱口诀:

“门卫指路 → 前端领客 → 不再迷路。” 🚪


🍯 总结(第 206–210 页)

内容问题解决方式奶茶铺比喻
异步组件加载页面太慢按需加载组件顾客点才煮
懒加载图片太多滚动再加载滚到才上奶茶
刷新 404服务器不懂前端路由Nginx 重定向 / Hash 模式门卫不懂暗号
Hash 模式不会 404# 暗号绕过门卫门卫默认放行

🧋 一句话记忆法:

“SPA 是假导航,门卫是真迷; 加个 # 或设 redirect,人人都能进大厅。” 🏠

🍓 第 211 页:什么是 SSR?


书里开篇的问题是:

❓SSR 解决了什么问题?你有没有用过?怎么实现的?


📘 SSR 全称:Server-Side Rendering(服务端渲染) 就是:在服务器上提前生成 HTML 页面,然后再发送给浏览器。


🧋奶茶铺类比:

普通 SPA(客户端渲染)就像: 顾客来了 → 店员才开始煮、加料、封膜 → 顾客干等。

SSR 就像: 店里提前做好几杯奶茶放着,顾客来了立刻端上去 🍹。


💡一句话总结:

SSR = “提前煮好奶茶再上桌”,省时间又香!


🍋 第 212 页:SSR 解决了什么问题?


书里举了两个关键点:

✅ 1. 首屏加载慢

普通 SPA:

  • 浏览器先加载 JS → 再执行渲染逻辑 → 再生成 HTML。
  • 导致首屏空白,加载慢。

SSR:

  • 服务器直接返回完整 HTML → 用户马上看到内容。

✅ 2. SEO 不友好

搜索引擎只识别 HTML,不会执行 JS。 SSR 返回的页面已经“长好样子”, 爬虫能直接读出内容,SEO 更好。


🧋奶茶铺类比:

搜索引擎就像“美食博主”📷, 它只拍摆上桌的奶茶,不会去厨房看你怎么煮。

所以 SSR 相当于先摆好成品,再请博主拍照!✨


🧠 小可爱口诀:

“SSR:快出锅,易上榜。” 🍵 ——首屏快、SEO好,两全其美。


🍰 第 213 页:SSR 是怎么实现的?


这一页书上有两张图:

  • 左边是 前后端同构结构图
  • 右边是 SSR 渲染流程图

💡 SSR 实现原理分三步:

1️⃣ 服务器运行 Vue 代码(借助 Node.js) 2️⃣ 把渲染好的 HTML 直接返回给浏览器 3️⃣ 前端再“激活”页面(绑定事件、执行 JS)


💻 简化代码:

import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'const app = createSSRApp({
  template: `<h1>你好,SSR!</h1>`
})
​
renderToString(app).then(html => {
  console.log(html)
})

💬 含义:

  • createSSRApp():创建能在服务器运行的 Vue 应用
  • renderToString():把 Vue 组件转成 HTML 字符串
  • 输出:<h1>你好,SSR!</h1>

🧋奶茶铺类比:

SSR = “厨房提前煮好奶茶,然后打包发给顾客”。 顾客一拆快递(浏览器加载)就能看到完整奶茶,不用等店员再煮。


🧠 小可爱口诀:

“SSR 是奶茶外卖版,提前做完快发货。” 🚚


🍎 第 214 页:SSR 的实现框架(Nuxt.js)


书里提到了 Vue 常用的 SSR 框架:

👉 Nuxt.js

Nuxt 就像一个“SSR 自动工厂”:

  • 自动帮你在 Node 端渲染 Vue;
  • 自动管理路由;
  • 自动处理 SEO 元信息;
  • 自动优化打包体积。

💻 例子(Nuxt 页面):

<template>
  <div>
    <h1>{{ title }}</h1>
  </div>
</template><script setup>
const title = '欢迎来到我的 SSR 页面'
</script>

💬 这个页面不用额外配置, Nuxt 会自动在服务器渲染成 HTML, 浏览器打开时就能立刻看到文字。


🧋奶茶铺类比:

Nuxt 就像一个“奶茶中央厨房”:

  • 自动备料、封膜、打包;
  • 各个分店(页面)只需提供菜单(template),不用操心后台流程~

🧠 小可爱口诀:

“Nuxt 一出,SSR 不苦。” 😋


🍇 第 215 页:SSR 与 SPA 的对比总结


对比项SSRSPA
首屏速度快(提前生成)慢(要执行 JS)
SEO好(HTML 完整)差(JS 渲染)
服务器压力
开发复杂度
适用场景内容展示型网站(博客、商城)交互型系统(后台管理、应用)

🧋奶茶铺终极比喻:

模式比喻特点
SSR外卖预制奶茶上桌快,顾客满意,但厨房忙
SPA现场冲泡奶茶味道新鲜,体验顺滑,但要等久一点

🧠 小可爱口诀总结(第 211–215 页):

关键词含义奶茶类比
SSR服务端渲染厨房提前做奶茶
解决问题首屏慢、SEO差顾客不等,博主好拍
实现原理Node 运行 Vue → 输出 HTML厨房出成品奶茶
框架Nuxt.js自动化奶茶工厂
对比 SPASSR快但累,SPA慢但轻外卖奶茶 vs 现泡奶茶

🍯 一句话总结:

SSR = “提前做好奶茶送上门”; SPA = “顾客来了再现场冲”。

所以—— 如果你做博客、商城,用 SSR。 如果你做后台、应用,用 SPA。

🍓 第 216 页:什么是路由守卫?


书里示例的代码核心是:

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token');
  if (!token && to.path !== '/login') {
    next('/login');
  } else {
    next();
  }
});

🧠 大白话解释:

路由守卫就是「在你进入页面之前,先拦一下」, 判断你有没有资格进入这页。

  • to:你要去的页面
  • from:你从哪来
  • next():放行函数,必须调用它才能继续

🧋奶茶铺类比:

你要进“VIP 休息区”(某个需要登录的页面)。 门口的保安(beforeEach)会问: “请出示会员卡(token)。”

  • 有卡:欢迎进(next()
  • 没卡:请先去前台办卡(跳转 /login

💡 记忆口诀:

“beforeEach 是门卫,to 是目标,from 是来源,next 是开门钥匙。” 🗝️


🍋 第 217 页:登录后如何放行


书上这部分的关键代码:

if (to.meta.requiresAuth && !token) {
  next('/login');
} else {
  next();
}

💬 含义:

  • 给路由设置 meta.requiresAuth = true,代表该页面需要登录;
  • 如果没登录(没有 token),就强制跳转到登录页;
  • 登录后自动放行。

🧋奶茶铺类比:

某些房间(比如“员工区”、“后台”)门口贴着“凭工牌进入”。

门卫大叔(beforeEach)检查门上标签(meta.requiresAuth):

  • 有“需要工牌”的标识 → 检查工牌
  • 没标识的普通门 → 直接放行

✅ 实战技巧:

每个路由都可以加 meta

{
  path: '/admin',
  component: Admin,
  meta: { requiresAuth: true }
}

💬 以后可以根据这个 meta 控制是否登录、是否显示菜单等。


🧠 小可爱口诀:

“meta 写标签,守卫看标签。”


🍰 第 218 页:afterEach 和登录页面跳转逻辑


💻 代码:

router.afterEach((to) => {
  document.title = to.meta.title || '默认标题';
});

💬 afterEach 是在“进入页面之后”触发。 最常见用途:修改网页标题(浏览器 tab 上的文字)。


🧋奶茶铺类比:

顾客进到不同房间(不同页面),门上标签(标题)会自动更新。

比如:

  • /home → “奶茶店首页”
  • /order → “订单中心”

💬 登录后跳转逻辑:

if (to.path === '/login' && token) {
  next('/');
} else {
  next();
}

💬 意思是:

  • 登录状态下不能再访问登录页;
  • 登录后直接跳回首页。

🧋奶茶铺类比:

你已经是会员了,就不能再跑去“注册会员柜台”。 门卫看到你有卡,就说:“欢迎回来,直接进店吧!” 😆


🧠 小可爱口诀:

“afterEach 改招牌,登录守卫防绕路。”


🍇 第 219 页:完整守卫链逻辑示例


书上的完整示例:

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token');if (!token && to.meta.requiresAuth) {
    next('/login');
  } else if (token && to.path === '/login') {
    next('/');
  } else {
    next();
  }
});

💬 逻辑拆解:

1️⃣ 没登录 & 要访问需要权限的页面 → 去登录 2️⃣ 已登录 & 还想访问登录页 → 去首页 3️⃣ 其他情况 → 放行


🧋奶茶铺类比:

  • 没会员卡想进员工区:不行,去前台!
  • 已有卡还想办卡:不用,直接进店。
  • 普通顾客进大厅:没问题~欢迎光临!✨

✅ 小技巧:

你可以把权限判断封装成函数:

function checkAuth(to) {
  const token = localStorage.getItem('token');
  return !(to.meta.requiresAuth && !token);
}

再在守卫里调用。 让代码更优雅、清晰。


🧠 小可爱口诀:

“三段逻辑:没卡去办卡、有卡别重办、其他随便逛。” 😎


🍎 第 220 页:动态路由与 404 处理


💻 动态添加路由:

router.addRoute({
  path: '/profile',
  component: () => import('@/views/Profile.vue')
});

💬 用于:根据角色动态添加页面。


🧋奶茶铺类比:

有的顾客是“店长”,进门后多一个隐藏菜单「员工管理」。 普通顾客没有这个菜单。


💻 404 页面配置:

{
  path: '/:pathMatch(.*)*',
  name: 'NotFound',
  component: NotFound
}

💬 匹配所有未知路径,显示友好的“404页面”。


🧋奶茶铺类比:

顾客走错门,结果撞到储藏室 🚪💥 店员礼貌地指路说:“客官您走错啦~请回首页 😅”


🧠 小可爱口诀:

“addRoute 动态加门,404 礼貌指路。”


🍯 总结(第 216–220 页)

功能作用奶茶铺比喻
beforeEach页面跳转前检查门口保安查会员卡
meta.requiresAuth标记页面需登录“凭卡进入”标签
afterEach页面加载后处理改门口招牌
动态路由 addRoute登录后加页面店长菜单特权
404 捕获错误页面处理店员礼貌指路

🧋 一句话记忆法:

“路由守卫像门卫: 有卡放行,没卡劝退; afterEach 换招牌,404 礼貌带回家~” 🚪✨

🧋 第 221–223 页:前端权限控制(基于角色动态渲染)


书里的主要示例是这样的:

export default {
  setup() {
    const role = ref('admin')
    const permissionList = ref(['view', 'edit', 'delete'])
​
    const hasPermission = (permission) => {
      return permissionList.value.includes(permission)
    }
​
    return { role, hasPermission }
  }
}

🧠 大白话解释:

这段代码就是一个「权限控制逻辑」的基础模型。

  • role:当前用户的身份,比如 "admin""user"
  • permissionList:当前角色拥有的权限列表;
  • hasPermission(permission):检查是否拥有某个权限。

🧋奶茶铺类比:

店长(admin)的权限表上写着: ✅ 能看库存、能改菜单、能删订单。

普通员工(user)的权限表上写着: ✅ 只能看库存,不能改菜单也不能删订单。

💡 所以,hasPermission('delete') 相当于问: “这个员工能删订单吗?”


💡 页面中怎么用:

<button v-if="hasPermission('edit')">修改菜单</button>

👉 只有有“edit”权限的用户才能看到按钮。


🧠 小可爱口诀:

“按钮要特权,逻辑来判权。”


🍋 第 224 页:动态菜单渲染(后端返回权限菜单)


书里展示了这样一段示例逻辑:

function filterRoutes(routes, roles) {
  const res = []
  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })
  return res
}

🧠 解释:

这段代码就是在“根据用户角色筛选可访问的菜单”。

  • routes:系统中所有的路由;
  • roles:当前用户角色;
  • hasPermission:判断当前角色能否访问这个路由;
  • filterRoutes:递归过滤,把不能访问的菜单删掉。

🧋奶茶铺类比:

店铺后台有好多“功能房间”:库存区、会员管理、财务报表……

但不是每个人都能进:

  • 店长可以进所有房间;
  • 收银员只能进收银台和库存;
  • 新人只能进“培训室”。

所以程序在登录后,要根据角色动态生成菜单。


💡 最后渲染出不同菜单:

const accessibleRoutes = filterRoutes(asyncRoutes, userRoles)
router.addRoutes(accessibleRoutes)

👉 登录后,系统自动挂上“你能进的页面”。


🧠 小可爱口诀:

“谁能进哪间房,登录后才定章。” 🏠


🍰 第 225 页:Vue3 有哪些变化?(和 Vue2 对比)


书上这一页讲的重点是:

Vue3 到底改了什么?为什么要升级?


🧋 我们先整体理解: Vue3 就像奶茶店的「2.0 自动化升级版」🍹, 更快、更轻、更聪明。


💡 Vue3 的三大变化:

① 响应式系统升级(Proxy 替代 defineProperty)

// Vue2
Object.defineProperty(obj, 'name', { get() {}, set() {} })
​
// Vue3
const state = reactive({ name: '小可爱' })

🧋奶茶铺类比:

Vue2 是一个“手动登记”的账本, 每加一个新员工都得手写一行 defineProperty,很麻烦。

Vue3 用 Proxy 后,就像“智能扫码点名系统”, 能自动追踪所有属性, 新加字段不需要额外登记,立刻生效。


② Composition API 替代 Options API

// Vue2 写法(按功能分散)
export default {
  data() { return { count: 0 } },
  methods: { add() { this.count++ } },
  watch: { count() { console.log('变啦') } }
}
​
// Vue3 写法(按逻辑聚合)
setup() {
  const count = ref(0)
  function add() { count.value++ }
  watch(count, () => console.log('变啦'))
  return { count, add }
}

🧋奶茶铺类比:

Vue2 就像“厨房分区制”——调料放调料区、锅放锅区、食材放冰箱。 每次做一道菜都得到处跑。🥵

Vue3 的 Composition API 就像“每道菜配专属工作台”—— 调料、锅、食材全在一起,做起来更顺手。👩‍🍳


③ 性能提升与 Tree-Shaking

Vue3 的底层是重写的, 采用更轻量的虚拟 DOM、编译优化、静态提升等技术。

💡 Tree-shaking:只打包你用到的功能。


🧋奶茶铺类比:

Vue2 打包所有机器和原料(哪怕没用到的都搬进厨房); Vue3 只搬用得着的。

👉 更轻、更快、更省。🚀


💡 小总结:

对比项Vue2Vue3奶茶铺比喻
响应式原理definePropertyProxy从手工登记变成扫码点名
API 风格Options APIComposition API从厨房分区到菜品工作台
性能一体化打包Tree-Shaking 优化从全搬到按需拿

🧠 小可爱口诀:

“Vue3 三快:数据追得快,逻辑写得快,打包跑得快。” 🏎️


🍯 第 221–225 页总结

模块关键点奶茶铺类比
权限控制判断角色与权限表员工能进哪些房间
动态菜单登录后加载可访问菜单店长看到更多功能
Vue3 响应式Proxy 自动追踪智能点名系统
Composition API按逻辑聚合菜品工作台
Tree-shaking性能优化按需进货,不浪费

🧋 一句话总结:

Vue3 = 奶茶店智能升级版: 自动考勤、智能厨房、节能省料!🍹✨

🧋第 226 页:Vue3 想实现什么?

书中写着标题:

What's coming in Vue 3.0

  • Make it faster
  • Make it smaller
  • Make it more maintainable
  • Make it easier to target native
  • Make your life easier

🌈 目标翻译成小可爱语言:

目标含义奶茶铺类比
更快性能提升制茶机器更高效
更小打包更轻去掉没必要的材料
更好维护结构清晰工厂流水线规范化
支持原生跨平台奶茶机能做冰沙、咖啡、果汁
更简单开发体验提升新员工一小时上手

🧠 小可爱口诀:

“Vue3 五个更:更快、更小、更灵、更稳、更好用。”


🍋第 227 页:性能优化(Performance)

书里提到:

  • 重写虚拟 DOM 实现
  • 编译器知晓优化路径
  • 初始化性能更高
  • SSR 速度提升 2~3 倍

💡 大白话讲:

1️⃣ Vue3 彻底重写虚拟 DOM(就像换了更聪明的大脑)。 2️⃣ 编译器提前“告诉” Vue 哪些部分可以优化(避免无意义的更新)。 3️⃣ 初次渲染更快、内存占用更少。


🧋奶茶铺类比:

Vue2:每次点奶茶,店员都从头检查整份订单。 Vue3:电脑系统知道“只有加糖项变了”,就只更新那一项。


💻 伪代码理解:

// Vue2 更新:重新遍历整个虚拟DOM
updateAll()
​
// Vue3 更新:只更新变化的节点
patch(diffNode)

🧠 小可爱口诀:

“Vue3 更新只补差,不全炒一遍。” 🍳


🍓第 228 页:Tree-shaking 与 Composition API


💡 Tree-shaking 是什么?

简单说:没用到的代码不打包。


🧋奶茶铺类比:

Vue2 打包所有口味的奶茶机(哪怕你只卖原味)。 Vue3 只打包你点单的那几台。

👉 节省体积、启动更快。


💡 官方例子:

bare-bone helloWorld 体积:

  • Vue2:22.5kb
  • Vue3:13.5kb 🚀

💡 Composition API(组合式 API)

import { ref } from 'vue'export default {
  setup() {
    const count = ref(0)
    const add = () => count.value++
    return { count, add }
  }
}

核心思想:逻辑聚合,而不是“按功能分类”。


🧋奶茶铺类比:

Vue2 是“材料区、工具区、成品区”分开。 Vue3 是“每杯奶茶一个工作台”,所有材料都摆在旁边,做得又快又爽!✨


🧠 小可爱口诀:

“Vue2 分区摆,Vue3 一桌干。”


🍰第 229 页:TypeScript + 编译器重写


💡 TypeScript 支持

Vue3 全部用 TypeScript 重写。

  • 更安全、更智能。
  • 代码自动提示、类型检查更准确。

🧋奶茶铺类比:

Vue2 的员工靠经验做奶茶; Vue3 的员工有配方表(类型约束),不会乱加料。🍵


💡 还有:

  • 支持 TSX / JSX
  • 支持 class component
  • 自动类型推导

💡 Compiler Rewrite(编译器重写)

Vue3 的编译器被重写成“模块化插件架构”。

意味着:

  • 能自定义插件;
  • 能做更强的 IDE 支持;
  • 能为不同平台(如 Web、Weex、Native)生成不同渲染逻辑。

🧋奶茶铺类比:

Vue2 的厨房是“一体机”,不能拆。 Vue3 的厨房是“积木式”,你想加奶盖机、珍珠机都能自由插拔! 😆


🧠 小可爱口诀:

“Vue3 厨房可扩展,TypeScript 护安全。” 🧑‍🍳


🍇第 230 页:自定义渲染器 + 新响应式 API


💡 Custom Renderer API

import { createRenderer } from 'vue/runtime-core'const { render } = createRenderer({
  createElement(tag) { /* 自定义 DOM */ },
  insert(el, parent) { /* 插入节点 */ }
})

这让你可以:

  • 把 Vue 渲染到 不是 DOM 的地方! 👉 比如 Canvas、三维游戏引擎、终端、甚至硬件屏幕。

🧋奶茶铺类比:

Vue2 只能做“网页奶茶”; Vue3 能输出到“手表屏幕奶茶机”、“冰箱屏幕奶茶机”,甚至“LED 广告屏奶茶”。😆


💡 Exposed Reactivity API

import { observable, effect } from 'vue'

const state = observable({ count: 0 })

effect(() => {
  console.log(`count is: ${state.count}`)
})
state.count++

💬 意思: Vue3 把内部响应式机制开放出来! 你可以单独拿来做响应式编程,不一定非要写 Vue 组件。


🧋奶茶铺类比:

Vue2 的“智能调料机”只能在店里用; Vue3 把调料机单独卖给你,你在别的厨房也能用上这套自动感应系统。


💡 最后一张图写着:

“Easily identify why a component is re-rendering” 👉 Vue3 能轻松追踪组件重渲染的原因。


🧋奶茶铺类比:

Vue2 有时候会突然“多加糖”你都不知道哪来的。 Vue3 会告诉你:“嘿,是你改了奶量触发的哦~” ☕


🧠 小可爱口诀:

“Vue3 开放反应核,组件刷新有线索。”


🍯总结(第 226–230 页)

特性Vue3 新能力奶茶铺类比
性能优化新虚拟 DOM、更快渲染智能流水线,只更新变动
Tree-shaking只打包用到的功能只备材料,不囤库存
Composition API按逻辑聚合代码每杯奶茶一站式工作台
TypeScript 支持自动类型推导有配方表的奶茶工厂
编译器重写插件式架构模块化厨房,可自由加机
Custom Renderer渲染到任意平台奶茶能上手表屏、广告屏
Exposed Reactivity响应式系统开放智能调料机独立出售

🧋一句话总结:

Vue3 = “开挂的智能奶茶工厂” 🍹

  • 做得快(性能)
  • 做得省(Tree-shaking)
  • 做得准(TypeScript)
  • 做得多(Custom Renderer)

🧋第 231 页:Vue3 新特性总览

书里列出了这些 Vue3 新玩意👇:

  • Fragment
  • Teleport
  • createRenderer
  • Composition API
  • 新的生命周期钩子
  • Global API
  • Suspense(等待异步加载)

🧠 大白话总结:

Vue2 是“传统厨房”,Vue3 是“模块化中央工厂”, 有更多智能工具,比如传送门(Teleport)、多出餐窗口(Fragment)等。


🍰第 232 页:Fragment(多根节点组件)

代码示例:

<template>
  <header>头部</header>
  <main>主体内容</main>
  <footer>底部</footer>
</template>

💬 以前 Vue2 不允许组件有多个根节点。 必须包一层 <div>


🧋奶茶铺类比:

Vue2:每份奶茶都要装进一个盒子(多层包装 🧃)。 Vue3:直接“裸装出餐”,节省包装。

💡 Fragment 的好处:

  • DOM 层级更干净;
  • 减少不必要的包裹层;
  • 性能更好。

🧠 小可爱口诀:

“Vue3 出餐更简洁,不用 div 来打包。”


🍓第 233 页:Teleport(传送门)

代码示例:

<template>
  <teleport to="body">
    <div class="dialog">这是弹窗</div>
  </teleport>
</template>

💬 意思是: 这个弹窗的代码虽然写在组件里,但实际 DOM 被传送到 下显示。


🧋奶茶铺类比:

你在“厨房”做奶茶,但要把奶茶“直接传送到顾客桌上”—— 不需要顾客跑过来取。🚀

比如:

  • 弹窗(Modal)
  • 提示框(Toast)
  • 右键菜单

这些东西逻辑属于当前组件,但显示要脱离当前位置。


🧠 小可爱口诀:

“Teleport 开传送门,逻辑留本地,画面上天庭。” ☁️


🍋第 234 页:createRenderer(自定义渲染器)

代码片段:

import { createRenderer } from 'vue'
const { render } = createRenderer({
  createElement(type) { /* 自定义渲染逻辑 */ },
  insert(el, parent) { /* 插入逻辑 */ }
})

💬 意思:Vue3 可以让你自己决定“渲染到哪”。 你可以让 Vue 渲染到:

  • 网页 DOM(默认)
  • Canvas(绘图)
  • 3D 游戏场景
  • 终端字符界面

🧋奶茶铺类比:

Vue2 的出餐机只能“装奶茶杯”; Vue3 你可以让它出“冰淇淋碗”或“外卖盒”!🍦🍱


🧠 小可爱口诀:

“Renderer 自定义,奶茶能上天。” 🚀


🍇第 235 页:Composition API + 生命周期钩子


💡 Composition API(组合式 API)

代码示例:

import { ref, onMounted } from 'vue'export default {
  setup() {
    const count = ref(0)
    const add = () => count.value++
    onMounted(() => console.log('组件挂载了'))
    return { count, add }
  }
}

🧋奶茶铺类比:

每道奶茶都有独立“操作台”:

  • ref 是食材桶(能存材料);
  • onMounted 是“店员上岗时”的初始化动作;
  • setup 是整个操作台的启动按钮。

💡 优点:

  • 更灵活复用;
  • 不用把逻辑分散在 data、methods、watch;
  • 方便团队协作。

🧠 小可爱口诀:

“setup 起台子,ref 存材料,onMounted 店员上岗。” 🍵


💡 生命周期钩子对比图(书上有图)

Vue2Vue3
beforeCreate / createdsetup()
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

🧋奶茶铺类比:

Vue2:店长用“开店日记”记录事件(生命周期)。 Vue3:直接用“语音提醒”—— onMounted() 就像 Siri 提醒:“嘿,奶茶台已经启动啦~” ☕


💡 Global API(全局 API 变动)

书中提到:

  • Vue2 的 Vue.use()Vue.component() 改为 app.use()app.component()
  • 必须用 createApp(App) 来启动应用。
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

🧋奶茶铺类比:

Vue2 是“全城共享一个厨房”; Vue3 是“每家分店自己开厨房”。

这样更安全、可维护,不容易串味 😆。


🧠 小可爱口诀:

“Vue3 用 createApp,厨房独立更安全。”


🍯第 231–235 页总结

特性功能奶茶铺类比
Fragment多根节点去掉多余包装盒
TeleportDOM 传送门弹窗直达顾客桌
createRenderer自定义渲染奶茶能输出到各种容器
Composition API逻辑聚合每杯奶茶独立操作台
新生命周期钩子更直观事件名自动语音提醒
Global API多实例分店厨房独立运行

🧠 小可爱记忆法:

“Fragment 裸出餐,Teleport 弹窗闪; Renderer 随地造,setup 一台搞; 生命周期改语音,createApp 各开店。” 🧋✨

🧋第 236 页:渲染函数与生命周期命名变更


🍧 29.3.9 渲染函数(Render Function)变更

🧩 1️⃣ 作用

渲染函数就是 直接用 JavaScript 来生成 DOM 的方式。 普通 Vue 写法我们用 <template>,而渲染函数则是用 render() 来“手工造结构”。

比如:

render() {
  return h('div', { class: 'cup' }, '一杯奶茶')
}

Vue3 中,对渲染函数的底层 API 做了不少改动。


🧃 改动一:scopedSlots 删除

👉 全部改成用 $slots 访问。

🧋奶茶铺类比: 以前点单时要问“老板,这一杯加料(slot)在哪个列表(scopedSlot)?” 现在直接用 $slots,系统自动识别,点单更简洁。✅


🧃 改动二:动画类名重命名

Vue2Vue3
v-enterv-enter-from
v-leavev-leave-from

🧋奶茶铺类比:

以前动画只有“开始”“结束”, 现在加了“从哪来”“到哪去”, 更细致控制,就像“从收银台走到门口再离开”~💃


🧃 改动三:watch 不再支持“点路径字符串”

Vue2 写法:$watch('user.name', fn) ❌ Vue3 写法:必须写成函数:$watch(() => user.name, fn)

🧋奶茶铺类比:

以前你告诉老板“看顾客.奶茶名”这种话模糊, 现在必须指出“看这个顾客对象的 name 属性”才行。 Vue3 更严格,更安全。


🧃 改动四:outerHTML → innerHTML

在 Vue2 中,Vue 渲染时会替换整个根节点 (outerHTML)。 Vue3 只替换容器内的内容 (innerHTML)。

🧋奶茶铺类比:

Vue2 每次装修都拆掉整个奶茶铺外壳; Vue3 只换里面的桌椅,不动外墙~🍹


🍧 29.3.10 其他变更(生命周期、API 调整)

🔄 生命周期命名变化

Vue2Vue3含义
destroyedunmounted组件卸载完毕
beforeDestroybeforeUnmount卸载前

🧋奶茶铺类比:

Vue2 说“店铺被摧毁(destroyed)”, Vue3 改口成“店铺下班收摊(unmounted)”,听着更文明 😆


🧩 其他重要变化

改动点说明奶茶铺类比
data 必须是函数保证每个组件独立数据每个店员有自己独立的账本
mixindata 可合并多配方能合成一份菜单
attribute 强制绑定属性更严格,避免乱加料
<template> 现在更自由没有特殊标签要求,可嵌套使用
自定义指令 API 统一到生命周期指令像员工,也有“入职”“离职”周期

🧠 小可爱口诀:

“destroy 改 unmount,模板更灵活, data 要函数,属性更严格。”


🍋第 237 页:移除的 API(跟 Vue2 说再见的老朋友👋)


❌ 29.3.11 移除 API

被移除项替代方式含义
keyCodev-on 修饰符监听键盘事件时统一用名称
$on$off$once❌移除不再用手动事件总线
filter❌移除推荐用计算属性或方法替代
内联模板 attribute❌移除推荐用组件模板
$destroy❌移除不允许手动销毁组件

🧋奶茶铺类比:

  • $on/$off:以前店员靠“手喊传话”(事件总线), Vue3 用“内部对讲机系统”自动处理通讯。📞
  • filter:以前加料用“筛子”过滤,现在直接放机器“加料口”。
  • $destroy:以前要手动拆机器(危险⚠️),现在工厂自动安全关闭。

🧠 小可爱口诀:

on/on/off 不要喊,filter 筛子下岗完, destroy 交系统,安全又心安。”


🍯 总结:第 236–237 页重点

分类Vue2 → Vue3 变化奶茶铺记忆法
渲染函数scopedSlots 改 $slots点单方式更智能
动画类名v-enter 改 v-enter-from从哪来到哪去更清晰
watch不再支持字符串路径必须函数形式看得准
生命周期destroy → unmount不说摧毁说下班
data必须是函数每人独立账本
mixin 合并支持更好合并多配方组合新饮品
移除 APIon/on/off/filter/destroy告别手工操作,自动化系统上线

🧋一句话总结:

Vue3 不仅升级“智能工厂”, 还全面“整顿奶茶流程”: 手工活下岗、命名更文明、系统更安全。 🍹✨