19. Write a mini-react
Video courses (efficient Learning) :Into the curriculum
Course Contents:
1. Introduction and questions
2. React design philosophy
React source code architecture
4. Source directory structure and debugging
5. JSX & core API
Legacy and Concurrent mode entry functions
7. Fiber architecture
8. Render phase
9. The diff algorithm
10. com MIT stage
11. Life cycle
12. Status update process
13. Hooks the source code
14. Handwritten hooks
15.scheduler&Lane
16. Concurrent mode
17.context
18 Event System
19. Write the React mini
20. Summary & Answers to interview questions in Chapter 1
21.demo
What’s the difference between mini React and real source code
- In the Render phase we traversed the entire Fiber tree, in the source code if nothing changes the node will hit the optimized logic, and then skip the traversal of the node
- * * * * * * * * * * * * * * * * * * * * * * *
- Each time we iterate we are creating a new node, some conditions in the source code will reuse the node
- Priority is not used
Step 1: Renderer and entry function
const React = {
createElement,
render,
};
const container = document.getElementById("root");
const updateValue = (e) = > {
rerender(e.target.value);
};
const rerender = (value) = > {
const element = (
<div>
<input onInput={updateValue} value={value} />
<h2>Hello {value}</h2>
</div>
);
React.render(element, container);
};
rerender("World");
Copy the code
Step 2: Create the DOM node function
/ create elementfunction createElement(type, props, ... children) {
return {
type,
props: {
...props,
children: children.map((child) = > (typeof child === "object" ? child : createTextElement(child))),
},
};
}
// Create a text type
function createTextElement(text) {
return {
type: "TEXT_ELEMENT".props: {
nodeValue: text,
children: [].}}; }/ / create the dom
function createDom(fiber) {
const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);
updateDom(dom, {}, fiber.props);
return dom;
}
Copy the code
Step 3: Update the node function
const isEvent = (key) = > key.startsWith("on");
const isProperty = (key) = >key ! = ="children" && !isEvent(key);
const isNew = (prev, next) = > (key) = >prev[key] ! == next[key];const isGone = (prev, next) = > (key) = >! (keyin next);
// Update node properties
function updateDom(dom, prevProps, nextProps) {
// Delete old events
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]);
});
// Delete the old attributes
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach((name) = > {
dom[name] = "";
});
// Set new properties
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach((name) = > {
dom[name] = nextProps[name];
});
// Add new events
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach((name) = > {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, nextProps[name]);
});
}
Copy the code
Step 4: Render stage
/ / render phase
function performUnitOfWork(fiber) {
if(! fiber.dom) { fiber.dom = createDom(fiber); }const elements = fiber.props.children;
reconcileChildren(fiber, elements);
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
returnnextFiber.sibling; } nextFiber = nextFiber.parent; }}// Mediate the node
function reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while(index < elements.length || (oldFiber ! = =null&& oldFiber ! = =undefined)) {
const element = elements[index];
let newFiber = null;
const sameType = oldFiber && element && element.type === oldFiber.type;
if (sameType) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE"}; }if(element && ! sameType) { newFiber = {type: element.type,
props: element.props,
dom: null.parent: wipFiber,
alternate: null.effectTag: "PLACEMENT"}; }if(oldFiber && ! sameType) { 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++; }}Copy the code
Step 5: Commit phase
/ / the commit phase
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
// Manipulate the real DOM
function commitWork(fiber) {
if(! fiber) {return;
}
const domParent = fiber.parent.dom;
if (fiber.effectTag === "PLACEMENT"&& fiber.dom ! = =null) {
domParent.appendChild(fiber.dom);
} else if (fiber.effectTag === "UPDATE"&& fiber.dom ! = =null) {
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectTag === "DELETION") {
domParent.removeChild(fiber.dom);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
Copy the code
Step 6: Start scheduling
// Start rendering entry
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
alternate: currentRoot,
};
deletions = [];
nextUnitOfWork = wipRoot;
}
// Scheduling function
function workLoop(deadline) {
let shouldYield = false;
while(nextUnitOfWork && ! shouldYield) {/ / render phase
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if(! nextUnitOfWork && wipRoot) { commitRoot();/ / the commit phase
}
requestIdleCallback(workLoop); // Idle scheduling
}
requestIdleCallback(workLoop);
Copy the code
The complete code
/ / create the element
function createElement(type, props, ... children) {
return {
type,
props: {
...props,
children: children.map((child) = > (typeof child === "object" ? child : createTextElement(child))),
},
};
}
// Create a text type
function createTextElement(text) {
return {
type: "TEXT_ELEMENT".props: {
nodeValue: text,
children: [].}}; }/ / create the dom
function createDom(fiber) {
const dom = fiber.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);
updateDom(dom, {}, fiber.props);
return dom;
}
const isEvent = (key) = > key.startsWith("on");
const isProperty = (key) = >key ! = ="children" && !isEvent(key);
const isNew = (prev, next) = > (key) = >prev[key] ! == next[key];const isGone = (prev, next) = > (key) = >! (keyin next);
// Update node properties
function updateDom(dom, prevProps, nextProps) {
// Delete old events
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]);
});
// Delete the old attributes
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach((name) = > {
dom[name] = "";
});
// Set new properties
Object.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach((name) = > {
dom[name] = nextProps[name];
});
// Add new events
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach((name) = > {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, nextProps[name]);
});
}
/ / the commit phase
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
// Manipulate the real DOM
function commitWork(fiber) {
if(! fiber) {return;
}
const domParent = fiber.parent.dom;
if (fiber.effectTag === "PLACEMENT"&& fiber.dom ! = =null) {
domParent.appendChild(fiber.dom);
} else if (fiber.effectTag === "UPDATE"&& fiber.dom ! = =null) {
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectTag === "DELETION") {
domParent.removeChild(fiber.dom);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
let nextUnitOfWork = null;
let currentRoot = null;
let wipRoot = null;
let deletions = null;
// Start rendering entry
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
alternate: currentRoot,
};
deletions = [];
nextUnitOfWork = wipRoot;
}
// Scheduling function
function workLoop(deadline) {
let shouldYield = false;
while(nextUnitOfWork && ! shouldYield) {/ / render phase
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if(! nextUnitOfWork && wipRoot) { commitRoot();/ / the commit phase
}
requestIdleCallback(workLoop); // Idle scheduling
}
requestIdleCallback(workLoop);
/ / render phase
function performUnitOfWork(fiber) {
if(! fiber.dom) { fiber.dom = createDom(fiber); }const elements = fiber.props.children;
reconcileChildren(fiber, elements);
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
returnnextFiber.sibling; } nextFiber = nextFiber.parent; }}// Mediate the node
function reconcileChildren(wipFiber, elements) {
let index = 0;
let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
let prevSibling = null;
while(index < elements.length || (oldFiber ! = =null&& oldFiber ! = =undefined)) {
const element = elements[index];
let newFiber = null;
const sameType = oldFiber && element && element.type === oldFiber.type;
if (sameType) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE"}; }if(element && ! sameType) { newFiber = {type: element.type,
props: element.props,
dom: null.parent: wipFiber,
alternate: null.effectTag: "PLACEMENT"}; }if(oldFiber && ! sameType) { 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++; }}const React = {
createElement,
render,
};
const container = document.getElementById("root");
const updateValue = (e) = > {
rerender(e.target.value);
};
const rerender = (value) = > {
const element = (
<div>
<input onInput={updateValue} value={value} />
<h2>Hello {value}</h2>
</div>
);
React.render(element, container);
};
rerender("World");
Copy the code