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