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);