mini-react-dom
Version:
A lightweight React-like DOM renderer with JSX, hooks, context, SSR and lazy support
102 lines (80 loc) • 2.7 kB
JavaScript
import { resetHooks, setRerender } from "./hooks.js";
import { Fragment } from "./createElement.js";
import { reconcile } from "./reconcile.js";
let rootElement = null;
let rootContainer = null;
let prevVNode = null;
export function render(element, container) {
rootElement = element;
rootContainer = container;
resetHooks();
setRerender(() => render(rootElement, rootContainer));
if (prevVNode) {
reconcile(container, prevVNode, element);
} else {
const dom = createDom(element);
container.appendChild(dom);
}
prevVNode = element;
}
export function createDom(element) {
if (typeof element.type === "function") {
return createDom(element.type(element.props));
}
if (element.type === Fragment) {
const fragment = document.createDocumentFragment();
(element.props.children || []).forEach((child) => {
fragment.appendChild(createDom(child));
});
return fragment;
}
const dom =
element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
const isListener = (name) => name.startsWith("on");
const isAttribute = (name) => !isListener(name) && name !== "children";
Object.keys(element.props || {}).forEach((name) => {
if (isListener(name)) {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, element.props[name]);
} else if (isAttribute(name)) {
dom[name] = element.props[name];
}
});
(element.props?.children || []).forEach((child) => {
dom.appendChild(createDom(child));
});
return dom;
}
export function hydrateDom(dom, element) {
if (typeof element.type === "function") {
return hydrateDom(dom, element.type(element.props));
}
if (element.type === "TEXT_ELEMENT") {
return;
}
const isListener = (name) => name.startsWith("on");
const isAttribute = (name) => !isListener(name) && name !== "children";
Object.keys(element.props || {}).forEach((name) => {
if (isListener(name)) {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, element.props[name]);
} else if (isAttribute(name)) {
// optional: check against existing attributes if needed
dom[name] = element.props[name];
}
});
const children = element.props?.children || [];
const domChildren = Array.from(dom.childNodes);
for (let i = 0; i < children.length; i++) {
hydrateDom(domChildren[i], children[i]);
}
}
export function hydrate(element, container) {
rootElement = element;
rootContainer = container;
resetHooks();
setRerender(() => render(rootElement, rootContainer));
hydrateDom(container, element);
}