version1: jsx
const element = <h1 title="foo">Hello</h1>;
const container = document.getElementById('root');
ReactDOM.render(element, container);
version2: React.createElement
const element = React.createElement(
"h1",
{ title: "foo"},
"Hello"
);
const container = document.getElementById('root');
ReactDOM.render(element, container);
version3: convert jsx to js
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello"
}
}
const container = document.getElementById('root');
ReactDOM.render(element, container);
version4:
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello"
}
}
const container = document.getElementById('root');
const node = document.createElement(element.type);
node["title"] = element.props.title;
const text = document.createTextNode("");
text["nodeValue"] = element.props.children;
node.appendChild(text);
container.appendChild(node);
version5:
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
const element = React.createElement(
"div",
{ id: "foo" },
React.createElement("a", null, "bar"),
React.createElement("b")
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
version6:
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children,
},
}
}
demo:
createElement("div");
//Output: {"type":"div","props":{"children":[]}}
createElement("div", null, "a")
//Output: {"type":"div","props":{"children":["a"]}}
createElement("div", null, "a", "b")
//Output: {"type":"div","props":{"children":["a","b"]}}
Step1: 实现createElement
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => typeof child === "object" ? child : createTextElement(child)),
},
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
}
}
实现MyReact
const MyReact = {
createElement,
}
const element = MyReact.createElement(
"div",
{ id: "foo" },
MyReact.createElement("a", null, "bar"),
MyReact.createElement("b")
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
Step2: 实现render
function render(element, container) {
const dom = element.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
const isProperty = key => key !== "children";
Object.keys(element.props)
.filter(isProperty)
.forEach(name => {
dom[name] = element.props[name]
});
element.props.children.forEach(child =>
render(child, dom)
);
container.appendChild(dom);
}
Step3: Concurrent Mode
DOM树太大时,会阻塞浏览器主进程。
let nextUnitOfWork = null;
function workLoop(deadline) {
let shouldYield = false;
while(nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(nextUnitOfWork) {
//TODO
}
Step4: Fibers
function createDom(fiber) {
const dom = fiber.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(fiber.type);
const isProperty = key => key !== "children";
Object.keys(fiber.props)
.filter(isProperty)
.forEach(name => {
dom[name] = fiber.props[name]
});
return dom;
}
function render(element, container) {
nextUnitOfWork = {
dom: container,
props: {
children: [element],
}
}
}
let nextUnitOfWork = null
function workLoop(deadline) {
let shouldYield = false;
while(nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(fiber) {
if(!fiber.dom) {
fiber.dom = createDom(fiber);
}
if(fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom);
}
const elements = fiber.props.children;
let index = 0;
let prevSibling = null;
while(index < elements.length){
const element = elements[index];
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null
}
if(index === 0){
fiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
if(fiber.child){
return fiber.child;
}
let nextFiber = fiber;
while(nextFiber){
if(nextFiber.sibling){
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
Step5: Render and Commit Phases
function commitRoot() {
// TODO add nodes to dom
}
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
}
}
nextUnitOfWork = wipRoot;
}
let nextUnitOfWork = null
let wipRoot = null
function workLoop(deadline) {
let shouldYield = false;
while(nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1;
}
if(!nextUnitOfWork && wipRoot){
commitRoot()
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(fiber) {
if(!fiber.dom) {
fiber.dom = createDom(fiber);
}
// if(fiber.parent) {
// fiber.parent.dom.appendChild(fiber.dom);
// }
const elements = fiber.props.children;
let index = 0;
let prevSibling = null;
while(index < elements.length){
const element = elements[index];
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null
}
if(index === 0){
fiber.child = newFiber;
} else {
prevSibling.sibling = newFiber;
}
prevSibling = newFiber;
index++;
}
if(fiber.child){
return fiber.child;
}
let nextFiber = fiber;
while(nextFiber){
if(nextFiber.sibling){
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
Step6: Reconciliation
const isEvent = key => key.startsWith("on")
const isProperty = key =>
key !== "children" && !isEvent(key)
// const isProperty = key => key !== "children"
const isNew = (prev, next) => key =>
prev[key] !== next[key]
const isGone = (prev, next) => key => !(key in next)
function updateDom(dom, prevProps, nextProps) {
//Remove old or changed event listeners
Object.keys(prevProps)
.filter(isEvent)
.filter(
key =>
!(key in nextProps) ||
isNew(prevProps, nextProps)(key)
)
.forEach(name => {
const eventType = name
.toLowerCase()
.substring(2)
dom.removeEventListener(
eventType,
prevProps[name]
)
})
// Remove old properties
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 event listeners
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach(name => {
const eventType = name
.toLowerCase()
.substring(2)
dom.addEventListener(
eventType,
nextProps[name]
)
})
}
function commitRoot(){
deletions.forEach(commitWork);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
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);
}
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
alternate: currentRoot,
}
deletions = []
nextUnitOfWork = wipRoot;
}
let nextUnitOfWork = null
let currentRoot = null
let wipRoot = null
let deletions = null
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){
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
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;
const sameType = oldFiber && element && element.type == oldFiber.type;
if(sameType) {
// TODO update the node
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE",
}
}
if(element && !sameType) {
// TODO add this node
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: "PLACEMENT",
}
}
if(oldFiber && !sameType) {
// TODO delete the oldFiber's node
oldFiber.effectTag = "DELETION";
deletions.push(oldFiber);
}
// const newFiber = {
// type: element.type,
// props: element.props,
// parent: wipFiber,
// dom: null
// }
// if(index === 0){
// wipFiber.child = newFiber;
// } else {
// prevSibling.sibling = newFiber;
// }
// prevSibling = newFiber;
index++;
}
}
More
build-your-own-react
https://pomb.us/build-your-own-react/