Files
minivue/stage07/index.js
zzy 5760d63c3a feat(runtime-core): 添加 Vue 渲染器和 h 函数实现
- 实现了 render 函数用于 Vue 组件渲染
- 实现了 h 函数用于创建虚拟节点(VNode)
- 添加类型定义和相关工具函数
2026-05-02 16:34:48 +08:00

141 lines
3.3 KiB
JavaScript
Raw 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 07全量重新渲染的问题
* 体验每次都 destroy/create 带来的性能与状态损失
*/
/**
* @typedef {string|number|VNode} VNodeChild
*/
/**
* @typedef {Object} VNode
* @property {string} type
* @property {Object<string, any>} [props]
* @property {VNodeChild[]} [children]
* @property {HTMLElement | null} [el]
*/
/**
* 创建虚拟节点(VNode)
* @param {string} type - 标签名,如 'div'
* @param {Object<string, any> | 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 = "加载用户";
}
});