Vue 3 响应式全流程演示

{{ count }} 模板、依赖收集、微任务队列、mount/patch 更新真实 DOM

// ─── 日志工具 ───
var logEl = document.getElementById("log");
function log(msg) {
  console.log(msg);
  logEl.textContent = logEl.textContent + msg + "\n";
  logEl.scrollTop = logEl.scrollHeight;
}

log("=== {{ count }} 响应式 + 真实 DOM ===\n");

// ─── 1. 依赖存储 ───
var targetMap = new WeakMap();
var activeEffect = null;

// ─── 2. 更新队列 ───
var queue = new Set();
var isFlushing = false;

// ─── 3. reactive ───
function reactive(obj) {
  return new Proxy(obj, {
    get: function (target, key) {
      track(target, key);
      return target[key];
    },
    set: function (target, key, value) {
      var old = target[key];
      target[key] = value;
      if (old !== value) {
        trigger(target, key);
      }
      return true;
    }
  });
}

function track(target, key) {
  if (!activeEffect) {
    return;
  }
  var depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  var dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }
  dep.add(activeEffect);
  log("  [track] " + String(key) + " ← effect#" + activeEffect.id);
}

function trigger(target, key) {
  var depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  var dep = depsMap.get(key);
  if (!dep) {
    return;
  }
  log("  [trigger] " + String(key) + " 变了,通知 " + dep.size + " 个 effect");
  dep.forEach(function (effectFn) {
    if (effectFn.scheduler) {
      effectFn.scheduler(effectFn);
    } else {
      effectFn.run();
    }
  });
}

function queueJob(job) {
  if (queue.has(job)) {
    log("       [queueJob] effect#" + job.id + " 已在队列,跳过");
    return;
  }
  queue.add(job);
  log("       [queueJob] effect#" + job.id + " 入队");
  if (!isFlushing) {
    isFlushing = true;
    Promise.resolve().then(flushJobs);
  }
}

function flushJobs() {
  log("\n── 微任务:flushJobs ──");
  queue.forEach(function (job) {
    log("       [run] effect#" + job.id);
    job.run();
  });
  queue.clear();
  isFlushing = false;
  log("── 微任务结束 ──\n");
}

// ─── 4. effect ───
var uid = 0;
function effect(fn, options) {
  if (!options) {
    options = {};
  }
  var effectFn = function () {
    activeEffect = effectFn;
    fn();
    activeEffect = null;
  };
  effectFn.id = ++uid;
  effectFn.run = effectFn;
  effectFn.scheduler = options.scheduler;
  effectFn();
  return effectFn;
}

// ─── 5. 真实 DOM:mount / patch ───
var container = document.getElementById("app");
var state = reactive({ count: 0 });
var lastVNode = null;

// 模板 <div>{{ count }}</div> 编译后的 render
function render() {
  var count = state.count; // 读 count → track
  return { type: "div", text: String(count), el: null };
}

// 首次:创建真实 DOM 并插入页面
function mount(vnode, parent) {
  var el = document.createElement(vnode.type);
  el.textContent = vnode.text; // 真实 DOM API
  vnode.el = el;               // VNode 记住对应的真实节点
  parent.appendChild(el);
  log("  [patch] 挂载真实 DOM → createElement + textContent = " + vnode.text);
}

// 更新:只改 textContent,不重建整个 div
function patch(oldVNode, newVNode) {
  if (oldVNode.text !== newVNode.text) {
    newVNode.el = oldVNode.el; // 复用同一个 DOM 节点
    newVNode.el.textContent = newVNode.text; // ★ 真实 DOM 在这里被更新
    log('  [patch] 更新真实 DOM → textContent: "' + oldVNode.text + '" → "' + newVNode.text + '"');
  }
}

function componentUpdateFn() {
  log("  [render] 执行 render,读取 count...");
  var newVNode = render();

  if (!lastVNode) {
    mount(newVNode, container);
  } else {
    patch(lastVNode, newVNode);
  }
  lastVNode = newVNode;
}

// ─── 6. 启动 ───
log("── 初始化 ──");
effect(componentUpdateFn, {
  scheduler: function (effectFn) {
    queueJob(effectFn);
  }
});

// ─── 7. 按钮交互 ───
document.getElementById("btnAdd").onclick = function () {
  log("\n── 点击 count++ ──");
  state.count = state.count + 1;
};

document.getElementById("btnDemo").onclick = function () {
  log("\n── 演示:同步连续 count=1,2,3 ──");
  state.count = 1;
  state.count = 2;
  state.count = 3;
  log("── 同步结束,DOM 可能还是旧值,等微任务... ──");
  Promise.resolve().then(function () {
    log("── 演示结束,DOM 应显示 3 ──\n");
  });
};

document.getElementById("btnClear").onclick = function () {
  logEl.textContent = "";
};

componentUpdateFn 执行流程

render → diff → patch 的关键步骤与实例状态更新

console.log("=== componentUpdateFn 详细执行流程 ===");

// 模拟 Vue 组件实例
const instance = {
  data: { count: 0, message: "Hello" },
  isMounted: false,
  subTree: null,
  container: document.createElement("div"),

  // 🎯 组件的 render 函数(就是 componentUpdateFn 的核心)
  render() {
    console.log("   📝 render() 执行: 读取响应式数据");
    // 这里会访问响应式数据,建立依赖关系
    const count = this.data.count;
    const message = this.data.message;

    // 生成 Virtual DOM
    return {
      type: "div",
      props: { class: "container" },
      children: [
        { type: "h1", children: `Count: ${count}` },
        { type: "p", children: message },
        { type: "button", props: { onClick: () => this.data.count++ }, children: "Add" }
      ],
    };
  },
};

// 模拟 Diff 算法
function diff(oldVNode, newVNode) {
  console.log("   🔍 Diff 算法对比:");
  const patches = [];

  if (!oldVNode) {
    patches.push({ type: "CREATE", vnode: newVNode });
    console.log("     - 创建新节点");
    return patches;
  }

  // 简单的属性对比
  if (oldVNode.children !== newVNode.children) {
    patches.push({ type: "UPDATE_TEXT", oldText: oldVNode.children, newText: newVNode.children });
    console.log(`     - 文本更新: "${oldVNode.children}" → "${newVNode.children}"`);
  }

  // 属性对比
  const propPatches = diffProps(oldVNode.props, newVNode.props);
  if (propPatches.length > 0) {
    patches.push({ type: "UPDATE_PROPS", patches: propPatches });
  }

  return patches;
}

function diffProps(oldProps = {}, newProps = {}) {
  const patches = [];
  // 省略:属性级别的详细对比
  return patches;
}

// 模拟 patch 函数
function patch(oldVNode, newVNode, container) {
  console.log("   🏗️  patch() 执行 DOM 操作");

  if (!oldVNode) {
    // 挂载阶段
    console.log("     - 挂载新节点到 DOM");
    mount(newVNode, container);
  } else {
    // 更新阶段
    console.log("     - 更新现有 DOM 节点");
    update(oldVNode, newVNode);
  }
}

function mount(vnode, container) {
  // 创建真实 DOM 的逻辑...
  console.log("     - 创建元素:", vnode.type);
}

function update(oldVNode, newVNode) {
  // 更新 DOM 的逻辑...
  console.log("     - 最小化更新 DOM");
}

// 🎯 完整的 componentUpdateFn
const componentUpdateFn = () => {
  console.group("🚀 componentUpdateFn 开始执行");

  try {
    // 1. 执行 render 函数生成新 VNode
    console.log("1. 📝 调用 instance.render()");
    const newVNode = instance.render.call(instance);
    console.log("   新 VNode:", JSON.stringify(newVNode, null, 2));

    // 2. 执行 Diff 和 Patch
    console.log("2. 🔄 执行 Diff & Patch");
    const patches = diff(instance.subTree, newVNode);

    console.log("3. 🏗️  执行 DOM 更新");
    patch(instance.subTree, newVNode, instance.container);

    // 3. 更新实例状态
    console.log("4. 📊 更新实例状态");
    instance.subTree = newVNode;
    if (!instance.isMounted) {
      instance.isMounted = true;
      console.log("   - 标记组件已挂载");
    }

    console.log("✅ componentUpdateFn 执行完成");
    return newVNode;
  } catch (error) {
    console.error("❌ componentUpdateFn 执行出错:", error);
    throw error;
  } finally {
    console.groupEnd();
  }
};

// 测试
console.log("\n--- 第一次执行(挂载)---");
componentUpdateFn();

console.log("\n--- 数据变化后执行(更新)---");
instance.data.count = 5;
instance.data.message = "Updated!";
componentUpdateFn();