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