feat(runtime-core): 添加 Vue 渲染器和 h 函数实现
- 实现了 render 函数用于 Vue 组件渲染 - 实现了 h 函数用于创建虚拟节点(VNode) - 添加类型定义和相关工具函数
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
|
||||
render in vue ./packages/runtime-core/src/renderer.ts
|
||||
|
||||
h in vue ./packages/runtime-core/src/h.ts
|
||||
|
||||
- stage00 纯html
|
||||
- stage01 命令式 DOM
|
||||
- stage02 声明式渲染
|
||||
|
||||
14
stage07/index.html
Normal file
14
stage07/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='zh-cn'>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<title>Stage 07: 全量重新渲染的问题</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="number" id="userId" placeholder="用户 ID" />
|
||||
<button id="loadBtn">加载用户</button>
|
||||
<div id="minivue"></div>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
140
stage07/index.js
Normal file
140
stage07/index.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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 = "加载用户";
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user