@vanilla-dom/core
Version:
轻量级 DOM 渲染引擎,VNode 到 DOM 转换
136 lines (134 loc) • 4.98 kB
JavaScript
import { appendChild, clearChildren, createElement, createTextNode, removeNode, removeProperty, setEvents, setProperty, updateEvents, updateProperty } from "./dom-utils.js";
import { isRegisteredComponent, renderRegisteredComponent } from "./pattern-registry.js";
//#region src/renderer.ts
/**
* 核心渲染引擎
* 负责将 VNode 树转换为真实 DOM
*/
const vNodeToDOMMap = /* @__PURE__ */ new WeakMap();
const domToVNodeMap = /* @__PURE__ */ new WeakMap();
/**
* 将 VNode 转换为 DOM 元素
*/
function createDOMFromTree(vnode) {
if (typeof vnode.type === "function") return createDOMFromComponent(vnode);
const element = createElement(vnode.type);
if (vnode.props) for (const [key, value] of Object.entries(vnode.props)) setProperty(element, key, value);
if (vnode.events) setEvents(element, vnode.events);
const children = Array.isArray(vnode.children) ? vnode.children : [];
for (const child of children) {
const childNode = createDOMFromChild(child);
if (childNode) appendChild(element, childNode);
}
vNodeToDOMMap.set(vnode, element);
domToVNodeMap.set(element, vnode);
if (vnode.ref) vnode.ref(element);
return element;
}
/**
* 处理组件 VNode
*/
function createDOMFromComponent(vnode) {
const component = vnode.type;
if (isRegisteredComponent(component)) {
const props = vnode.props || {};
const children = vnode.children || [];
const renderedVNode = renderRegisteredComponent(component, props, children);
if (renderedVNode) return createDOMFromTree(renderedVNode);
}
try {
const props = vnode.props || {};
const children = vnode.children || [];
const result = component({
...props,
children
});
if (result && typeof result === "object" && "type" in result) return createDOMFromTree(result);
if (typeof result === "string" || typeof result === "number") return createTextNode(String(result));
return createTextNode("");
} catch (error) {
console.error(`[@vanilla-dom/core] Error rendering component ${component.name || "Anonymous"}:`, error);
return createTextNode(`[Component Error: ${component.name || "Anonymous"}]`);
}
}
/**
* 处理子节点
*/
function createDOMFromChild(child) {
if (child == null || typeof child === "boolean") return null;
if (typeof child === "string" || typeof child === "number") return createTextNode(String(child));
return createDOMFromTree(child);
}
/**
* 渲染 VNode 到容器
*/
function render(vnode, options) {
const { container, replace = false } = options;
if (replace) clearChildren(container);
const domNode = createDOMFromTree(vnode);
appendChild(container, domNode);
}
/**
* 更新 DOM - 基于新旧 VNode 树的差异
*/
function updateDOM(oldVNode, newVNode, domNode) {
if (oldVNode.type !== newVNode.type) {
const newDomNode = createDOMFromTree(newVNode);
const parent = domNode.parentNode;
if (parent) parent.replaceChild(newDomNode, domNode);
return;
}
updateProperties(domNode, oldVNode.props, newVNode.props);
updateEvents(domNode, newVNode.events, oldVNode.events);
const oldChildren = Array.isArray(oldVNode.children) ? oldVNode.children : [];
const newChildren = Array.isArray(newVNode.children) ? newVNode.children : [];
updateChildren(domNode, oldChildren, newChildren);
if (oldVNode.ref !== newVNode.ref) {
if (oldVNode.ref) oldVNode.ref(null);
if (newVNode.ref) newVNode.ref(domNode);
}
domToVNodeMap.set(domNode, newVNode);
vNodeToDOMMap.set(newVNode, domNode);
}
/**
* 更新元素属性
*/
function updateProperties(element, oldProps, newProps) {
const oldP = oldProps || {};
const newP = newProps || {};
for (const key in oldP) if (!(key in newP)) removeProperty(element, key);
for (const key in newP) updateProperty(element, key, newP[key], oldP[key]);
}
/**
* 更新子节点 - 简单实现,保持 Core 层职责单一
*/
function updateChildren(parent, oldChildren, newChildren) {
const maxLength = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < maxLength; i++) {
const oldChild = oldChildren[i];
const newChild = newChildren[i];
const childNode = parent.childNodes[i];
if (!newChild) {
if (childNode) removeNode(childNode);
} else if (!oldChild) {
const newChildNode = createDOMFromChild(newChild);
if (newChildNode) appendChild(parent, newChildNode);
} else if (typeof oldChild !== typeof newChild) {
const newChildNode = createDOMFromChild(newChild);
if (newChildNode && childNode) parent.replaceChild(newChildNode, childNode);
} else if (typeof oldChild === "object" && typeof newChild === "object") updateDOM(oldChild, newChild, childNode);
else if (oldChild !== newChild) {
if (childNode) childNode.textContent = String(newChild);
}
}
}
/**
* 水合现有 DOM(用于 SSR 场景,当前简单实现)
*/
function hydrate(vnode, existingDOM) {
vNodeToDOMMap.set(vnode, existingDOM);
domToVNodeMap.set(existingDOM, vnode);
}
//#endregion
export { createDOMFromTree, hydrate, render, updateDOM };
//# sourceMappingURL=renderer.js.map