Personal Code Notes
/* @createElement */
/* @ render function */
1. Once we start rendering, we won't stop until we have rendered the entire element tree. If the element tree is large, it may block the main thread for too long. Also, if the browser needs to perform high-priority operations (such as processing user input or keeping the animation flowing), it must wait until the rendering is complete. 2. Therefore, we will break the work into several small units, and after each unit is done, we will ask the browser to break the rendering if any other operations need to be performed. 3. We use requestIdleCallback to do a loop. You can think of its requestIdleCallback as setTimeout, but the browser will run the callback when the main thread is idle, rather than telling us when to run. 4. RequestIdleCallback also provides us with deadline parameters. We can use it to check how much time the browser has before it needs to control again. 5. To start using loops, we need to set up the first unit of work and then write a function called performUnitOfWork that not only performs the work but also returns the next unit of work. * /
/* Fiber tree does three things for each Fiber: 1. Select the next unit of work. Each Fiber is connected to the parent node, the first child node, and the next sibling node. If there is a child, create Fiber for the child. Create Fiber */ for Sibling
/* Render the Fiber tree as real DOM */
/* @ Step 6 Reconciliation updates and removes nodes compare the elements received in the render function with the last fiber tree we submitted to the DOM */
/* @ step 7 function component in the function component Fiber has no DOM node children from running this function, not directly from props */
/* @hooks */
/** Next unit of work */
let nextUnitOfWork = null;
/** The last fiber tree submitted to DOM */
let currentRoot = null;
/** The root of the fiber tree */
let wipRoot = null;
/** Delete list */
let deletions = null;
/** hooks */
let wipFiber = null;
/** hook number */
let hookIndex = null;
/** Is the event */
const isEvent = key= > key.startsWith("on");
/** Is an attribute */
const isProperty = key= >key ! = ="children" && !isEvent(key);
/** Is a new attribute */
const isNew = (prev, next) = > key= >prev[key] ! == next[key];/** is an old attribute and not in a new attribute */
const isGone = (prev, next) = > key= >! (keyin next);
/** Create VDOM */
function createElement (element, props, ... children) {
return {
type: element,
props: {
...props,
children: children.map(child= >
typeof child === "object" ? child : createTextElement(child)
)
}
}
}
/** Create text VDOM */
function createTextElement (text) {
return {
type: "TEXT_ELEMENT".props: {
nodeValue: text,
children: [],}}}/* Create the real DOM and add the attribute */
function createDom (fiber) {
const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);
Object.keys(fiber.props)
.filter(isProperty)
.forEach(name= > {
dom[name] = fiber.props[name];
})
return dom
}
/** work loop */
function workLoop (deadLine) {
let shouldYield = false;
while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); shouldYield = deadLine.timeRemaining() <1;
}
if(! nextUnitOfWork && wipRoot) { commitRoot(); } requestIdleCallback(workLoop); } requestIdleCallback(workLoop);/** Executes the unit of work and returns the next unit of work */
function performUnitOfWork (fiber) {
// Add a DOM node
const isFunctionComponent = fiber.type instanceof Function;
if (isFunctionComponent) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
// Returns the next unit of work
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
returnnextFiber.sibling; } nextFiber = nextFiber.parent; }}/** Update the function component */
function updateFunctionComponent(fiber) {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
/** useState hook */
function useState (initial) {
const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex];
const hook = {
state: oldHook ? oldHook.state : initial,
queue: [],}const actions = oldHook ? oldHook.queue : [];
actions.forEach(action= > {
hook.state = action(hook.state);
})
const setState = action= > {
hook.queue.push(action);
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot,
}
nextUnitOfWork = wipRoot;
deletions = [];
}
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState]
}
/** Update the component */
function updateHostComponent(fiber) {
if(! fiber.dom) { fiber.dom = createDom(fiber); }// Create a new fiber
reconcileChildren(fiber, fiber.props.children);
}
/ * * * / adjustment
function reconcileChildren (wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while(index < elements.length || oldFiber ! =null) {
const element = elements[index];
let newFiber = null;
// Compare old Fiber with Element
const sameType = oldFiber && element && oldFiber.type === element.type;
if (sameType) {
// Update the node
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE",}}if(element && ! sameType) {// Add a node
newFiber = {
type: element.type,
props: element.props,
dom: null.parent: wipFiber,
alternate: null.effectTag: "PLACEMENT",}}if(oldFiber && ! sameType) {// Delete a node
oldFiber.effectTag = "DELETION";
deletions.push(oldFiber);
}
if (oldFiber) {
oldFiber = oldFiber.sibling;
}
if (index === 0) {
wipFiber.child = newFiber;
} else if(element) { prevSibling.sibling = newFiber; } prevSibling = newFiber; index++; }}/ * * * / node
function commitRoot () {
deletions.forEach(commitWork);
// Add a node to dom
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
function commitWork (fiber) {
if(! fiber)return;
let domParentFiber = fiber.parent;
while(! domParentFiber.dom) { domParentFiber = domParentFiber.parent; }const domParent = domParentFiber.dom;
if (fiber.effectTag === "PLACEMENT"&& fiber.dom ! =null) {
// console.log(" add ");
placementDom(fiber.dom, domParent, fiber.props);
} else if (fiber.effectTag === "UPDATE"&& fiber.dom ! =null) {
// console.log(" update ");
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectTag === "DELETION") {
// console.log(" delete ");
commitDeletion(fiber, domParent);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
// Add a node
function placementDom(dom, domParent, props) {
Object.keys(props)
.filter(isEvent)
.forEach(name= > {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, props[name]);
})
domParent.appendChild(dom);
}
// Delete a node
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
Object.keys(fiber.props)
.filter(isEvent)
.forEach(name= > {
const eventType = name.toLowerCase().substring(2);
fiber.dom.removeEventListener(eventType, fiber.props[name]);
})
domParent.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child, domParent)
}
}
// Update the node
function updateDom(dom, prevProps, nextProps) {
// Remove old or changed event listeners
Object.keys(prevProps)
.filter(isEvent)
.filter(key= >! (keyin nextProps) || isNew(prevProps, nextProps)(key))
.forEach(name= > {
const eventType = name.toLowerCase().substring(2);
dom.removeEventListener(eventType, prevProps[name]);
})
// Remove the old attributes
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach(name= > {
dom[name] = "";
})
// Set new or changed properties
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach(name= > {
dom[name] = nextProps[name];
})
// Add new event listeners
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach(name= > {
const eventType = name.toLowerCase().substring(2); dom.addEventListener(eventType, nextProps[name]); })}/** render */
function render (element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
alternate: currentRoot,
};
deletions = [];
nextUnitOfWork = wipRoot;
}
/ * * * /
const MyReactDOM = {
createElement,
render,
useState,
}
Copy the code