UNPKG

@vanilla-dom/core

Version:

轻量级 DOM 渲染引擎,VNode 到 DOM 转换

136 lines (134 loc) 4.98 kB
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