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 = "加载用户"; + } +});