2025 从 0 开始设计一个 mini-vue

59 阅读3分钟

最优学习方法:快速掌握代码设计的步骤

  1. 抓住核心目标(Why)

    • 先问:这段代码要解决什么问题?(答案:实现数据响应式,支持数据变化时自动更新依赖。)
    • 明确目标后,理解代码的每一部分如何服务于这个目标。
  2. 分层拆解(What)

    • 把代码分成小块,逐层理解功能(从全局变量到具体函数)。
    • 关注输入输出和数据流,而不是一开始钻研细节。
  3. 动手调试(How)

    • 在代码中加 console.log,运行并观察数据流和执行顺序。
    • 修改参数或逻辑,验证自己的理解。
  4. 可视化梳理(Connect)

    • 用图表或伪代码把逻辑串起来,快速建立心理模型。
  5. 总结复述(Master)

    • 用自己的话解释代码,假装教别人,找出理解盲点。

快速理解代码:结构化拆解与分析

1. 抓住核心目标

这段代码的核心是实现 Vue 3 的响应式系统

  • 数据变化 → 自动触发副作用(如渲染)。
  • 用 Proxy 监听数据,用 bucket 收集和触发依赖。

2. 分层拆解代码

我将代码分成 5 个关键部分,逐一解释:

(1)全局变量:依赖存储和状态管理
const bucket = new WeakMap(); // 全局容器,存对象的依赖关系
let activeEffect = null;      // 当前正在执行的副作用函数
  • bucket: 像一个“依赖数据库”,用 WeakMap 存每个对象及其属性的依赖。
    • 为什么用 WeakMap?对象不用时自动清理,避免内存泄漏。
  • activeEffect: 标记当前运行的副作用(如渲染函数),用于收集依赖。

快速理解:bucket 是“仓库”,activeEffect 是“工人”,工人干活时把依赖存进仓库。

(2)track 函数:收集依赖
function track(target, key) {
  if (!activeEffect) return; // 没工人,不干活

  let depsMap = bucket.get(target); // 获取对象依赖图
  if (!depsMap) bucket.set(target, (depsMap = new Map())); // 没有就新建

  let deps = depsMap.get(key); // 获取属性依赖集合
  if (!deps) depsMap.set(key, (deps = new Set())); // 没有就新建

  deps.add(activeEffect); // 把工人加进依赖集合
}
  • 作用:当读取数据(如 data.count)时,把当前副作用存进 bucket。
  • 数据结构
    • WeakMap: target → Map
    • Map: key → Set
    • Set: 存 activeEffect(避免重复)。
  • 流程:检查 → 初始化 → 添加。
(3)trigger 函数:触发更新
function trigger(target, key) {
  const depsMap = bucket.get(target); // 找到对象依赖图
  if (!depsMap) return;

  const deps = depsMap.get(key); // 找到属性依赖集合
  if (deps) deps.forEach(effect => effect()); // 执行所有依赖
}
  • 作用:数据变化时,找到相关依赖并执行。
  • 流程:查找 → 执行。
(4)reactive 函数:数据监听

[[Reflect 的使用]]

function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key); // 读取时收集依赖
      return Reflect.get(target, key, receiver); // 返回值
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver); // 设置新值
      trigger(target, key); // 触发更新
      return true;
    }
  });
}
  • 作用:用 Proxy 包裹对象,拦截 get(读取)和 set(修改)。
  • 核心
    • get: 收集依赖。
    • set: 触发更新。
(5)effect 函数:副作用管理
function effect(fn) {
  activeEffect = fn; // 设置工人
  fn(); // 立即执行,触发 get,收集依赖
  activeEffect = null; // 清空
}
  • 作用:运行副作用(如打印或渲染),并在执行时收集依赖。
  • 流程:标记 → 执行 → 清理。

3. 数据流与执行顺序

用测试代码理解:

const data = reactive({ count: 0 });
effect(() => console.log("count is:", data.count));
data.count = 1;
  1. 初始化
    • reactive 创建 Proxy 对象。
  2. effect 执行
    • activeEffect = fn。
    • fn() 触发 get → track 把 fn 存进 bucket。
  3. 修改数据
    • data.count = 1 触发 set → trigger 执行 fn。

测试代码

// 测试代码

const data = reactive({ count: 0 });

effect(() => {

  console.log("count is:", data.count);

});

  

data.count = 1; // 输出: "count is: 1"

data.count = 2; // 输出: "count is: 2"

使用断点进行流程分析, reactive 定义之后使用 Proxy 进行代理, get 就 track, set 就trigger。 接下来遇到 effect effect 会知己先执行一遍并且进行 track 操作, 所以 count 的依赖就有了 effect 中的 fn, 到修改 count 的时候就会触发 trigger 重新执行这个 function。

项目代码: github.com/Arabeseque/…