added basic updates
This commit is contained in:
parent
b9fb2d5a45
commit
04e4278bab
|
|
@ -1,5 +1,6 @@
|
||||||
import { domFiber, ElementSymbol, FiberNodeSymbol } from "./symbols";
|
import { domFiber, domProps, ElementSymbol, FiberNodeSymbol } from "./symbols";
|
||||||
import {
|
import {
|
||||||
|
debugEffectTag,
|
||||||
EffectTag,
|
EffectTag,
|
||||||
isFunctionFiber,
|
isFunctionFiber,
|
||||||
isHostFiber,
|
isHostFiber,
|
||||||
|
|
@ -11,7 +12,7 @@ import {
|
||||||
type RootFiber,
|
type RootFiber,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
let unitOfWork: FiberNode | null = null;
|
let unitOfWork: FiberNode | RootFiber | null = null;
|
||||||
let wipFiber: FiberNode;
|
let wipFiber: FiberNode;
|
||||||
let hookIndex: number;
|
let hookIndex: number;
|
||||||
let effectFiber: FiberNode | null = null;
|
let effectFiber: FiberNode | null = null;
|
||||||
|
|
@ -27,11 +28,24 @@ const createTextElement = (text: string): JSXElement => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function performUnitOfWork(fiber: FiberNode) {
|
const getRootFiber = (fiber: FiberNode): RootFiber => {
|
||||||
|
let root = fiber.parent;
|
||||||
|
while (root) {
|
||||||
|
if (root.isRoot) {
|
||||||
|
return root as RootFiber;
|
||||||
|
}
|
||||||
|
root = root.parent;
|
||||||
|
}
|
||||||
|
throw "Can't find root";
|
||||||
|
};
|
||||||
|
|
||||||
|
function performUnitOfWork(fiber: FiberNode | RootFiber) {
|
||||||
if (isFunctionFiber(fiber)) {
|
if (isFunctionFiber(fiber)) {
|
||||||
updateFunctionComponent(fiber);
|
updateFunctionComponent(fiber);
|
||||||
} else if (isHostFiber(fiber)) {
|
} else if (isHostFiber(fiber)) {
|
||||||
updateHostComponent(fiber);
|
updateHostComponent(fiber);
|
||||||
|
} else if (fiber.isRoot) {
|
||||||
|
reconcileChildren(fiber, fiber.pendingProps.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fiber.child) {
|
if (fiber.child) {
|
||||||
|
|
@ -90,7 +104,9 @@ function updateFunctionComponent(fiber: FunctionFiber) {
|
||||||
|
|
||||||
function updateHostComponent(fiber: HostFiber) {
|
function updateHostComponent(fiber: HostFiber) {
|
||||||
wipFiber = fiber;
|
wipFiber = fiber;
|
||||||
reconcileChildren(fiber, fiber.pendingProps.children);
|
if (fiber.type !== textElementType) {
|
||||||
|
reconcileChildren(fiber, fiber.pendingProps.children);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushEffect(fiber: FiberNode) {
|
function pushEffect(fiber: FiberNode) {
|
||||||
|
|
@ -112,7 +128,10 @@ function pushEffect(fiber: FiberNode) {
|
||||||
wipRoot.lastEffect = fiber;
|
wipRoot.lastEffect = fiber;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reconcileChildren(wipFiber: FiberNode, children: Child[] = []) {
|
function reconcileChildren(
|
||||||
|
wipFiber: FiberNode | RootFiber,
|
||||||
|
children: Child[] = [],
|
||||||
|
) {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let oldFiber = wipFiber.alternate?.child;
|
let oldFiber = wipFiber.alternate?.child;
|
||||||
let prevSibling: FiberNode | null = null;
|
let prevSibling: FiberNode | null = null;
|
||||||
|
|
@ -202,32 +221,105 @@ function findDomParent(fiber: FiberNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateProps(fiber: HostFiber) {
|
||||||
|
const dom = fiber.dom;
|
||||||
|
if (dom instanceof Text) {
|
||||||
|
dom.nodeValue = fiber.pendingProps.nodeValue;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!dom || !(dom instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prevProps = fiber.alternate?.pendingProps ?? {};
|
||||||
|
const nextProps = fiber.pendingProps ?? {};
|
||||||
|
let propNames = new Set([
|
||||||
|
...Object.keys(prevProps),
|
||||||
|
...Object.keys(nextProps),
|
||||||
|
]);
|
||||||
|
for (const propName of propNames) {
|
||||||
|
if (
|
||||||
|
propName === "children" ||
|
||||||
|
propName === "ref" ||
|
||||||
|
propName.startsWith("on")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const prevProp = prevProps[propName];
|
||||||
|
const nextProp = nextProps[propName];
|
||||||
|
if (nextProp === undefined) {
|
||||||
|
dom.removeAttribute(propName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (prevProp !== nextProp) {
|
||||||
|
// Handle special cases
|
||||||
|
if (propName === "className") {
|
||||||
|
dom.className = nextProp;
|
||||||
|
} else if (propName === "htmlFor") {
|
||||||
|
(dom as HTMLLabelElement).htmlFor = nextProp;
|
||||||
|
} else if (propName === "style" && typeof nextProp === "object") {
|
||||||
|
Object.assign(dom.style, nextProp);
|
||||||
|
} else {
|
||||||
|
dom.setAttribute(propName, nextProp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scheduleRerender(fiber: FiberNode) {
|
||||||
|
const root = getRootFiber(fiber);
|
||||||
|
wipRoot = root.alternate;
|
||||||
|
unitOfWork = root.alternate;
|
||||||
|
requestIdleCallback(workLoop);
|
||||||
|
}
|
||||||
|
|
||||||
function commitRoot() {
|
function commitRoot() {
|
||||||
if (!wipRoot) return;
|
if (!wipRoot) return;
|
||||||
effectFiber = wipRoot.firstEffect;
|
effectFiber = wipRoot.firstEffect;
|
||||||
while (effectFiber) {
|
while (effectFiber) {
|
||||||
|
console.log(
|
||||||
|
"commit",
|
||||||
|
debugEffectTag(effectFiber.effectTag),
|
||||||
|
typeof effectFiber.type !== "function"
|
||||||
|
? effectFiber.type
|
||||||
|
: effectFiber.type.name,
|
||||||
|
);
|
||||||
// Do effect
|
// Do effect
|
||||||
if (effectFiber.effectTag & EffectTag.Placement) {
|
if (
|
||||||
let dom: Node;
|
effectFiber.effectTag & EffectTag.Placement &&
|
||||||
|
isHostFiber(effectFiber)
|
||||||
|
) {
|
||||||
if (effectFiber.type === textElementType) {
|
if (effectFiber.type === textElementType) {
|
||||||
dom = document.createTextNode(effectFiber.pendingProps.nodeValue);
|
effectFiber.dom = document.createTextNode(
|
||||||
|
effectFiber.pendingProps.nodeValue,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
dom = document.createElement(effectFiber.type as string);
|
effectFiber.dom = document.createElement(effectFiber.type as string);
|
||||||
// TODO: Set props
|
updateProps(effectFiber);
|
||||||
// TODO: Maybe add event section
|
(effectFiber.dom as HTMLElement)[domFiber] = effectFiber;
|
||||||
|
(effectFiber.dom as HTMLElement)[domProps] = effectFiber.pendingProps;
|
||||||
// TODO: Maybe add ref in far future might consider a general on mount and on unmount event
|
// TODO: Maybe add ref in far future might consider a general on mount and on unmount event
|
||||||
}
|
}
|
||||||
effectFiber.dom = dom;
|
|
||||||
const parent = findDomParent(effectFiber);
|
const parent = findDomParent(effectFiber);
|
||||||
if (parent?.dom) {
|
if (parent?.dom) {
|
||||||
console.log("Appending", dom, "into", parent.dom);
|
parent.dom.appendChild(effectFiber.dom);
|
||||||
parent.dom.appendChild(dom);
|
|
||||||
} else {
|
} else {
|
||||||
throw "Can't commit without parent";
|
throw "Can't commit without parent";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (effectFiber.effectTag & EffectTag.Deletion) {
|
||||||
|
if (effectFiber.dom && effectFiber.dom instanceof HTMLElement) {
|
||||||
|
effectFiber.dom.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (effectFiber.effectTag & EffectTag.Update && isHostFiber(effectFiber)) {
|
||||||
|
updateProps(effectFiber);
|
||||||
|
}
|
||||||
effectFiber = effectFiber.nextEffect;
|
effectFiber = effectFiber.nextEffect;
|
||||||
|
wipRoot.lastEffect = effectFiber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wipRoot.firstEffect = null;
|
||||||
|
wipRoot.lastEffect = null;
|
||||||
wipRoot = null;
|
wipRoot = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -264,6 +356,7 @@ const getEventProp = (eventType: string) => {
|
||||||
};
|
};
|
||||||
function handleEvent(event: Event, root: RootFiber) {
|
function handleEvent(event: Event, root: RootFiber) {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
|
if (target === root.dom) return;
|
||||||
const type = event.type;
|
const type = event.type;
|
||||||
let node: FiberNode | RootFiber | null = target[domFiber] ?? null;
|
let node: FiberNode | RootFiber | null = target[domFiber] ?? null;
|
||||||
let propagationStopped = false;
|
let propagationStopped = false;
|
||||||
|
|
@ -284,7 +377,7 @@ function handleEvent(event: Event, root: RootFiber) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRootFiber(root: HTMLElement): RootFiber {
|
export function createRootFiber(root: HTMLElement): RootFiber {
|
||||||
const rootFiber: RootFiber = {
|
const alternate: RootFiber = {
|
||||||
$$typeof: FiberNodeSymbol,
|
$$typeof: FiberNodeSymbol,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
key: null,
|
key: null,
|
||||||
|
|
@ -302,6 +395,25 @@ export function createRootFiber(root: HTMLElement): RootFiber {
|
||||||
firstEffect: null,
|
firstEffect: null,
|
||||||
lastEffect: null,
|
lastEffect: null,
|
||||||
};
|
};
|
||||||
|
const rootFiber: RootFiber = {
|
||||||
|
$$typeof: FiberNodeSymbol,
|
||||||
|
type: undefined,
|
||||||
|
key: null,
|
||||||
|
child: null,
|
||||||
|
sibling: null,
|
||||||
|
parent: null,
|
||||||
|
index: 0,
|
||||||
|
dom: root,
|
||||||
|
hooks: [],
|
||||||
|
pendingProps: null,
|
||||||
|
effectTag: EffectTag.NoEffect,
|
||||||
|
alternate,
|
||||||
|
nextEffect: null,
|
||||||
|
isRoot: true,
|
||||||
|
firstEffect: null,
|
||||||
|
lastEffect: null,
|
||||||
|
};
|
||||||
|
alternate.alternate = rootFiber;
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
rootFiber.dom.addEventListener(event, (event) =>
|
rootFiber.dom.addEventListener(event, (event) =>
|
||||||
handleEvent(event, rootFiber),
|
handleEvent(event, rootFiber),
|
||||||
|
|
@ -312,10 +424,15 @@ export function createRootFiber(root: HTMLElement): RootFiber {
|
||||||
|
|
||||||
export function render(rootFiber: RootFiber, element: JSXElement) {
|
export function render(rootFiber: RootFiber, element: JSXElement) {
|
||||||
wipRoot = rootFiber;
|
wipRoot = rootFiber;
|
||||||
const fiber = createFiberFromElement(element);
|
rootFiber.pendingProps = {
|
||||||
fiber.parent = rootFiber;
|
children: [element],
|
||||||
rootFiber.child = fiber;
|
};
|
||||||
wipRoot.child = fiber;
|
rootFiber.alternate!.pendingProps = {
|
||||||
unitOfWork = fiber;
|
children: [element],
|
||||||
console.log(fiber);
|
};
|
||||||
|
// const fiber = createFiberFromElement(element);
|
||||||
|
// fiber.parent = rootFiber;
|
||||||
|
// rootFiber.child = fiber;
|
||||||
|
// wipRoot.child = fiber;
|
||||||
|
unitOfWork = wipRoot;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
import { createRootFiber, render } from "./cr";
|
import { createRootFiber, render, scheduleRerender } from "./cr";
|
||||||
|
import { domFiber } from "./symbols";
|
||||||
|
|
||||||
|
let renderCount = 0;
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
console.log("render");
|
||||||
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<div
|
||||||
<div>Hello world</div>
|
id="foo"
|
||||||
<ul>
|
onClick={(e) => {
|
||||||
{items.map((item) => (
|
const fiber = (e.target as HTMLElement)[domFiber];
|
||||||
<li key={item}>{String(item)}</li>
|
scheduleRerender(fiber!);
|
||||||
))}
|
}}
|
||||||
</ul>
|
>
|
||||||
</main>
|
Hello world {String(++renderCount)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("foo");
|
|
||||||
const root = createRootFiber(document.getElementById("app")!);
|
const root = createRootFiber(document.getElementById("app")!);
|
||||||
console.log(<div>Hello World</div>);
|
|
||||||
render(root, <App />);
|
render(root, <App />);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,18 @@ export enum EffectTag {
|
||||||
Layout = 0x20,
|
Layout = 0x20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const debugEffectTag = (tag: EffectTag): string => {
|
||||||
|
const effects = [];
|
||||||
|
if (tag & EffectTag.Placement) effects.push("Placement");
|
||||||
|
if (tag & EffectTag.Update) effects.push("Update");
|
||||||
|
if (tag & EffectTag.Deletion) effects.push("Deletion");
|
||||||
|
if (tag & EffectTag.Hydration) effects.push("Hydration");
|
||||||
|
if (tag & EffectTag.Passive) effects.push("Passive");
|
||||||
|
if (tag & EffectTag.Layout) effects.push("Layout");
|
||||||
|
if (effects.length === 0) effects.push("NoEffect");
|
||||||
|
return effects.join(" | ");
|
||||||
|
};
|
||||||
|
|
||||||
export const StateHook = Symbol.for("cr.state-hook");
|
export const StateHook = Symbol.for("cr.state-hook");
|
||||||
export const EffectHook = Symbol.for("cr.effect-hook");
|
export const EffectHook = Symbol.for("cr.effect-hook");
|
||||||
type Hook =
|
type Hook =
|
||||||
|
|
@ -63,16 +75,18 @@ export type FunctionFiber = FiberNode & {
|
||||||
type: FC;
|
type: FC;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HostFiber = FiberNode & {
|
export type HostFiber = Omit<FiberNode, "dom"> & {
|
||||||
type: string;
|
type: string;
|
||||||
|
dom: Node | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RootFiber = Omit<FiberNode, "type"> & {
|
export type RootFiber = Omit<FiberNode, "type" | "dom" | "alternate"> & {
|
||||||
type: undefined;
|
type: undefined;
|
||||||
dom: HTMLElement;
|
dom: Node;
|
||||||
isRoot: true;
|
isRoot: true;
|
||||||
firstEffect: FiberNode | null;
|
firstEffect: FiberNode | null;
|
||||||
lastEffect: FiberNode | null;
|
lastEffect: FiberNode | null;
|
||||||
|
alternate: RootFiber | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isFiberNode = (fiber: any): fiber is FiberNode =>
|
export const isFiberNode = (fiber: any): fiber is FiberNode =>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue