From 35b4b220933fbfcba253cd22e732b33e37a8b6e7 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Sat, 2 May 2026 13:50:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(minivue):=20=E6=B7=BB=E5=8A=A0stage06=20h?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=88=9B=E5=BB=BAVNode=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E5=AE=8C=E5=96=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在README.md中添加了所有阶段的说明,包括stage00到stage09的内容 - 移除了stage05中render函数的返回值和注释,简化函数实现 - 新增stage06/index.html基础页面结构 - 实现stage06 h函数自动创建VNode功能,支持: - 类型定义VNode和VNodeChild - h函数创建虚拟节点,支持嵌套子节点 - mount函数递归挂载VNode到真实DOM - 属性设置和文本节点处理 - 提供了完整的使用示例,展示用户卡片组件的渲染 --- README.md | 11 ++++++++ stage05/index.js | 2 -- stage06/index.html | 12 ++++++++ stage06/index.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 stage06/index.html create mode 100644 stage06/index.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..79a1099 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ + +- stage00 纯html +- stage01 命令式 DOM +- stage02 声明式渲染 +- stage03 列表渲染 +- stage04 模拟异步接口 +- stage05 虚拟 DOM 初体验 +- stage06 用 h 函数自动创建 VNode +- stage07 重新渲染暴露全量更新问题 +- stage08 简单 patch(无 key 对比) +- stage09 带 key 的 diff 算法 diff --git a/stage05/index.js b/stage05/index.js index 008bab3..9d10b5d 100644 --- a/stage05/index.js +++ b/stage05/index.js @@ -15,7 +15,6 @@ * 渲染 VNode 树为真实 DOM 并挂载到容器 * @param {VNode} vnode * @param {HTMLElement} container - * @returns {HTMLElement | Text} 挂载的真实 DOM */ function render(vnode, container) { let el; @@ -43,7 +42,6 @@ function render(vnode, container) { } } container.appendChild(el); - return el; } /** diff --git a/stage06/index.html b/stage06/index.html new file mode 100644 index 0000000..8d1ff45 --- /dev/null +++ b/stage06/index.html @@ -0,0 +1,12 @@ + + + + + + Stage 06: 用 h 函数自动创建 VNode + + +
+ + + diff --git a/stage06/index.js b/stage06/index.js new file mode 100644 index 0000000..d5abd89 --- /dev/null +++ b/stage06/index.js @@ -0,0 +1,70 @@ +/** + * Stage 06:用 h 函数自动创建 VNode + * 从“手动拼对象”到“用函数生成”,与 Vue 的 createElement 原理一致 + */ + +/** + * @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); +} + +const root = document.getElementById("minivue"); +const vnode = h( + "div", + { class: "user-card" }, + h("h2", null, "姓名: 小明"), + h("p", null, "年龄: 18"), + h("span", null, "学生"), +); + +mount(vnode, root);