From 5760d63c3a2778a4d88e00a529418a199fc397d4 Mon Sep 17 00:00:00 2001
From: zzy <2450266535@qq.com>
Date: Sat, 2 May 2026 16:34:48 +0800
Subject: [PATCH] =?UTF-8?q?feat(runtime-core):=20=E6=B7=BB=E5=8A=A0=20Vue?=
=?UTF-8?q?=20=E6=B8=B2=E6=9F=93=E5=99=A8=E5=92=8C=20h=20=E5=87=BD?=
=?UTF-8?q?=E6=95=B0=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 实现了 render 函数用于 Vue 组件渲染
- 实现了 h 函数用于创建虚拟节点(VNode)
- 添加类型定义和相关工具函数
---
README.md | 4 ++
stage07/index.html | 14 +++++
stage07/index.js | 140 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 158 insertions(+)
create mode 100644 stage07/index.html
create mode 100644 stage07/index.js
diff --git a/README.md b/README.md
index 79a1099..d1b5d02 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,8 @@
+render in vue ./packages/runtime-core/src/renderer.ts
+
+h in vue ./packages/runtime-core/src/h.ts
+
- stage00 纯html
- stage01 命令式 DOM
- stage02 声明式渲染
diff --git a/stage07/index.html b/stage07/index.html
new file mode 100644
index 0000000..a4aa209
--- /dev/null
+++ b/stage07/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Stage 07: 全量重新渲染的问题
+
+
+
+
+
+
+
+
diff --git a/stage07/index.js b/stage07/index.js
new file mode 100644
index 0000000..8efa9d1
--- /dev/null
+++ b/stage07/index.js
@@ -0,0 +1,140 @@
+/**
+ * Stage 07:全量重新渲染的问题
+ * 体验每次都 destroy/create 带来的性能与状态损失
+ */
+
+/**
+ * @typedef {string|number|VNode} VNodeChild
+ */
+
+/**
+ * @typedef {Object} VNode
+ * @property {string} type
+ * @property {Object} [props]
+ * @property {VNodeChild[]} [children]
+ * @property {HTMLElement | null} [el]
+ */
+
+/**
+ * 创建虚拟节点(VNode)
+ * @param {string} type - 标签名,如 'div'
+ * @param {Object | null} props - 属性或 null
+ * @param {...(string|VNode)} children - 子节点,字符串会自动转为文本节点
+ * @returns {VNode}
+ */
+function h(type, props, ...children) {
+ const normalizedChildren = children.flat(Infinity);
+ return {
+ type,
+ props: props || {},
+ children: normalizedChildren,
+ };
+}
+
+/**
+ * 将 VNode 挂载到真实 DOM(递归)
+ * @param {VNode} vnode
+ * @param {HTMLElement} container
+ */
+function mount(vnode, container) {
+ const el = document.createElement(vnode.type);
+
+ if (vnode.props) {
+ for (const key in vnode.props) {
+ el.setAttribute(key, vnode.props[key]);
+ }
+ }
+
+ if (vnode.children) {
+ vnode.children.forEach((child) => {
+ if (typeof child === "string" || typeof child === "number") {
+ el.appendChild(document.createTextNode(String(child)));
+ } else {
+ mount(child, el);
+ }
+ });
+ }
+
+ container.appendChild(el);
+}
+
+/**
+ * 使用数据创建VNode
+ * @param {{ name: string, age: number, tag: string }} user
+ * @returns
+ */
+function constructUserVNode(user) {
+ return h(
+ "div",
+ { class: "user-card" },
+ h("h2", null, "姓名: ", user.name),
+ h("p", null, "年龄: ", String(user.age)),
+ h("span", null, user.tag),
+ );
+}
+/**
+ * 使用数据创建VNode
+ * @param {{ name: string, age: number, tag: string }[]} users
+ * @returns
+ */
+function constructUserListVNode(users) {
+ return h(
+ "div",
+ { id: "user-list" },
+ ...users.map((u) => constructUserVNode(u)),
+ );
+}
+
+// 模拟数据库
+const multiUser = [
+ { name: "小明", age: 18, tag: "学生" },
+ { name: "小王", age: 19, tag: "学生" },
+ { name: "小李", age: 20, tag: "学生" },
+ { name: "小孙", age: 56, tag: "校长" },
+ { name: "小赵", age: 43, tag: "老师" },
+];
+
+/**
+ * 模拟异步获取用户数据
+ * @param {number} id
+ * @returns {Promise<{ name: string, age: number, tag: string }[]>}
+ */
+async function getUser(id) {
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ const count = Math.min(Math.max(1, id), multiUser.length);
+ return multiUser.slice(0, count);
+}
+
+const root = document.getElementById("minivue");
+const loadBtn = /** @type {HTMLButtonElement} */ (
+ document.getElementById("loadBtn")
+);
+const userIdInput = /** @type {HTMLInputElement} */ (
+ document.getElementById("userId")
+);
+
+userIdInput.value = "1";
+
+loadBtn.addEventListener("click", async () => {
+ const rawValue = userIdInput.value.trim();
+ const id = parseInt(rawValue, 10);
+
+ if (isNaN(id) || id < 1) {
+ alert("请输入一个大于 0 的数字 ID");
+ return;
+ }
+
+ loadBtn.disabled = true;
+ loadBtn.textContent = "加载中...";
+
+ try {
+ const users = await getUser(id);
+ mount(constructUserListVNode(users), root);
+ } catch (err) {
+ console.error("加载失败", err);
+ alert("加载失败,请重试");
+ } finally {
+ loadBtn.disabled = false;
+ loadBtn.textContent = "加载用户";
+ }
+});