Files
minivue/stage05/index.js
zzy 35b4b22093 feat(minivue): 添加stage06 h函数创建VNode功能并完善文档
- 在README.md中添加了所有阶段的说明,包括stage00到stage09的内容
- 移除了stage05中render函数的返回值和注释,简化函数实现
- 新增stage06/index.html基础页面结构
- 实现stage06 h函数自动创建VNode功能,支持:
  - 类型定义VNode和VNodeChild
  - h函数创建虚拟节点,支持嵌套子节点
  - mount函数递归挂载VNode到真实DOM
  - 属性设置和文本节点处理
- 提供了完整的使用示例,展示用户卡片组件的渲染
2026-05-02 13:50:16 +08:00

72 lines
1.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Stage 05虚拟 DOM 初体验
* 用纯 JavaScript 对象描述 DOM 树,并编写递归渲染函数
*/
/**
* @typedef {Object} VNode
* @property {string} type - 元素标签或 'text'
* @property {Object<string, string>} [props] - 属性
* @property {(VNode|string)[]} [children] - 子节点
* @property {string} [value] - 文本内容(仅 type='text' 时使用)
*/
/**
* 渲染 VNode 树为真实 DOM 并挂载到容器
* @param {VNode} vnode
* @param {HTMLElement} container
*/
function render(vnode, container) {
let el;
if (vnode.type === "text") {
el = document.createTextNode(vnode.value);
} else {
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) => {
// 子节点可能是字符串 (自动转为文本 VNode)
if (typeof child === "string") {
render({ type: "text", value: child }, el);
} else {
render(child, el);
}
});
}
}
container.appendChild(el);
}
/**
* 手动构建一个卡片 VNode
* @type {VNode}
*/
const userNode = {
type: "div",
props: { class: "user-card" },
children: [
{
type: "h2",
children: [{ type: "text", value: "姓名: 小明" }],
},
{
type: "p",
children: [{ type: "text", value: "年龄: 18" }],
},
{
type: "span",
children: [{ type: "text", value: "学生" }],
},
],
};
const root = document.getElementById("minivue");
render(userNode, root);