Implement commitMutations for insert/move/remove effects in tree order
Adds commitMutations function to reconcile.ts that processes fiber effects in correct order: removes (reverse), inserts+moves (left-to-right with insertBefore), updates (top-down). Integrates key-based reconciliation pipeline into render() via reconcileNode, resolveUNode, and createFiberForInsert.
This commit is contained in:
@@ -2,7 +2,8 @@ import type { UNode, UElement, URoot, ComponentFn, UComponent } from "../core/sc
|
||||
import { isURoot, isUPrimitive } from "../core/schema.js";
|
||||
import { Context } from "../core/context.js";
|
||||
import type { Fiber } from "./fiber.js";
|
||||
import { reconcileProps, commitEffects } from "./reconcile.js";
|
||||
import { reconcileProps, reconcileChildren, commitMutations } from "./reconcile.js";
|
||||
import type { CommitContext } from "./reconcile.js";
|
||||
|
||||
export interface HostConfig<TTag extends string, Instance, RootCtx> {
|
||||
name: string;
|
||||
@@ -116,6 +117,130 @@ export function createRoot<TTag extends string, Instance, RootCtx>(
|
||||
return fiber;
|
||||
}
|
||||
|
||||
function createFiberForInsert(node: UNode, parentFiber: Fiber<Instance>): Fiber<Instance> {
|
||||
if (isUPrimitive(node)) {
|
||||
const text = node === null ? "" : String(node);
|
||||
const t = host.createTextInstance(text, ctx, parentFiber.instance);
|
||||
host.emit?.("instance.create", `text_${Date.now()}`, { kind: "text", value: text });
|
||||
return {
|
||||
instance: t,
|
||||
tag: "#text",
|
||||
props: { text },
|
||||
key: undefined,
|
||||
children: [],
|
||||
parent: parentFiber,
|
||||
effect: null,
|
||||
signalDisposers: [],
|
||||
prevProps: null,
|
||||
};
|
||||
}
|
||||
|
||||
const el = node as UElement;
|
||||
|
||||
if (typeof el.type === "function") {
|
||||
const component = el.type as ComponentFn;
|
||||
const out = component({ ...el.props, children: el.children });
|
||||
host.emit?.("component.invoke", `comp_${Date.now()}`, { type: (component as unknown as UComponent).displayName ?? "anonymous" });
|
||||
return createFiberForInsert(out, parentFiber);
|
||||
}
|
||||
|
||||
const tag = el.type as TTag;
|
||||
const inst = host.createInstance(tag, el.props as Record<string, unknown>, ctx, parentFiber.instance);
|
||||
host.emit?.("instance.create", `${tag}_${Date.now()}`, { kind: "element", tag, props: el.props });
|
||||
|
||||
const fiber: Fiber<Instance> = {
|
||||
instance: inst,
|
||||
tag,
|
||||
props: el.props as Record<string, unknown>,
|
||||
key: el.key,
|
||||
children: [],
|
||||
parent: parentFiber,
|
||||
effect: null,
|
||||
signalDisposers: [],
|
||||
prevProps: null,
|
||||
};
|
||||
|
||||
for (const child of el.children) {
|
||||
const childFiber = createFiberForInsert(child, fiber);
|
||||
if (childFiber) {
|
||||
host.appendChild(inst, childFiber.instance, ctx);
|
||||
fiber.children.push(childFiber);
|
||||
}
|
||||
}
|
||||
|
||||
return fiber;
|
||||
}
|
||||
|
||||
function resolveUNode(node: UNode): UNode[] {
|
||||
if (node == null || node === false) return [];
|
||||
if (isUPrimitive(node)) return [node];
|
||||
if (isURoot(node)) {
|
||||
const result: UNode[] = [];
|
||||
for (const child of (node as URoot).children) {
|
||||
result.push(...resolveUNode(child));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const el = node as UElement;
|
||||
if (typeof el.type === "function") {
|
||||
const component = el.type as ComponentFn;
|
||||
const out = component({ ...el.props, children: el.children });
|
||||
return resolveUNode(out);
|
||||
}
|
||||
return [node];
|
||||
}
|
||||
|
||||
function reconcileNode(fiber: Fiber<Instance>, nextNode: UNode): void {
|
||||
if (isUPrimitive(nextNode)) {
|
||||
if (fiber.tag === "#text") {
|
||||
reconcileProps(fiber, nextNode, host as HostConfig<string, Instance, unknown>, ctx as unknown);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isURoot(nextNode)) {
|
||||
const rootChildren = (nextNode as URoot).children;
|
||||
const classification = reconcileChildren(
|
||||
fiber.children,
|
||||
rootChildren,
|
||||
);
|
||||
for (const m of classification.matched) {
|
||||
reconcileProps(m.oldFiber, m.newChild, host as HostConfig<string, Instance, unknown>, ctx as unknown);
|
||||
}
|
||||
const commitCtx: CommitContext<Instance> = {
|
||||
host: host as HostConfig<string, Instance, unknown>,
|
||||
ctx: ctx as unknown,
|
||||
createFiber: (node, parent) => createFiberForInsert(node, parent),
|
||||
};
|
||||
commitMutations(fiber, classification, commitCtx);
|
||||
return;
|
||||
}
|
||||
|
||||
const el = nextNode as UElement;
|
||||
|
||||
if (typeof el.type === "function") {
|
||||
const component = el.type as ComponentFn;
|
||||
const out = component({ ...el.props, children: el.children });
|
||||
reconcileNode(fiber, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fiber.tag !== el.type) return;
|
||||
|
||||
reconcileProps(fiber, el, host as HostConfig<string, Instance, unknown>, ctx as unknown);
|
||||
|
||||
const classification = reconcileChildren(fiber.children, el.children);
|
||||
for (const m of classification.matched) {
|
||||
reconcileProps(m.oldFiber, m.newChild, host as HostConfig<string, Instance, unknown>, ctx as unknown);
|
||||
}
|
||||
const commitCtx: CommitContext<Instance> = {
|
||||
host: host as HostConfig<string, Instance, unknown>,
|
||||
ctx: ctx as unknown,
|
||||
createFiber: (node, parent) => createFiberForInsert(node, parent),
|
||||
};
|
||||
commitMutations(fiber, classification, commitCtx);
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
ctx,
|
||||
@@ -125,13 +250,23 @@ export function createRoot<TTag extends string, Instance, RootCtx>(
|
||||
render(node: UNode) {
|
||||
if (this.rootFiber) {
|
||||
const payloadChildren = isURoot(node) ? (node as URoot).children : [node];
|
||||
for (let i = 0; i < payloadChildren.length; i++) {
|
||||
const childFiber = this.rootFiber.children[i];
|
||||
if (childFiber) {
|
||||
reconcileProps(childFiber, payloadChildren[i]!, host as HostConfig<string, Instance, unknown>, ctx as unknown);
|
||||
}
|
||||
const resolvedChildren: UNode[] = [];
|
||||
for (const child of payloadChildren) {
|
||||
resolvedChildren.push(...resolveUNode(child));
|
||||
}
|
||||
commitEffects(this.rootFiber, host as HostConfig<string, Instance, unknown>, ctx as unknown);
|
||||
const classification = reconcileChildren(
|
||||
this.rootFiber.children,
|
||||
resolvedChildren,
|
||||
);
|
||||
for (const m of classification.matched) {
|
||||
reconcileNode(m.oldFiber, m.newChild);
|
||||
}
|
||||
const commitCtx: CommitContext<Instance> = {
|
||||
host: host as HostConfig<string, Instance, unknown>,
|
||||
ctx: ctx as unknown,
|
||||
createFiber: (node, parent) => createFiberForInsert(node, parent),
|
||||
};
|
||||
commitMutations(this.rootFiber, classification, commitCtx);
|
||||
host.emit?.("root.render", `root_${Date.now()}`, { childCount: payloadChildren.length });
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user