# 手写 React
原文:build your own react (opens new window)
本文是对 build your own react (opens new window) 的学习注解。
相信也有很多同学拜读过这篇文章,虽然只有短短的几百行代码,还是需要花些精力去理清其中的逻辑关系。
读完整篇文章可以对 react 基本原理有一个整体上的认识和宏观上的把握,对于进一步学习react 源码非常有帮助。
本篇跟随原文作者的思路,把每一步的代码整理出来,然后结合自己的理解添加一些理解说明文字。
让我们跟随作者一步一步实现一个简版的 react。
# Step 0
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);
# Step I: The createElement Function
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
};
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
const Didact = {
createElement,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
ReactDom.render(element, container);
# Step II: The render Function
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
function render(element, container) {
const dom =
element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
const isProperty = (key) => key !== "children"; // 注意这里是判断props里面不是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);
}
const Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
Didact.render(element, container);
可在 codesandbox (opens new window) 执行
# Step III: Concurrent Mode
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
/** 一:
* 通过递归调用render函数完成渲染存在一个问题。
* 再整个 element tree 被完全渲染出来之前我们没法停止这个过程。
* 如果 element tree 很大,那么就会阻塞住线程。
* 如果这个时候浏览器需要处理高优先级的任务,例如响应用户输入或者渲染流畅的动画等等,这个时候就不得不等待整个渲染完成。
* 显然这是不能接受的。
*/
function render(element, container) {
const dom =
element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
const isProperty = (key) => key !== "children"; // 注意这里是判断props里面不是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);
}
/**
* 既然不能一次性完成,那就想办法把整个过程拆分成小的执行单元,
* 如果有高优先级的任务需要处理,那么每完成一个执行单元就让浏览器暂停执行渲染,转而去执行高优先级的任务。
*/
let nextUnitWork = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitWork && !shouldYield) {
nextUnitWork = performUnitOfWork(nextUnitWork);
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop); // 在主线程空闲的时候执行回调函数,这里只是为了演示方便,react 不用 requestIdleCallback 而是自己实现了 scheduler package https://github.com/facebook/react/tree/master/packages/scheduler
function performUnitOfWork(nextUnitWork) {
// TODO
}
const Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
Didact.render(element, container);
# Step IV: Fibers
/**
* 一:
* 为了将这些执行单元组织起来,我们需要一个数据结构就是我们常说的 fiber tree,每一个 element 都对应一个fiber,每一个fiber都是一个执行单元
* 具体什么是fiber tree,fiber tree 是一个数据结构,由单个fiber之间的关系连接而成。
* 这样做的目的是为了方便找到下一个需要处理的执行单元,
* 基于这样的目的,每个 fiber 都会保存对它第一个 child、下一个兄弟、parent 的引用。
*
*
* 执行规则:当处理完一个fiber
* 1. 如果它有child则,child会成为下一个被处理的fiber
* 2. 如果没有child,则下一个兄弟成为下一个被处理的fiber
* 3. 如果既没有child又没有下一个兄弟则处理parent的下一个兄弟。
* 4. 如果parent也没有下一个兄弟,则继续往上找,直到找到或者到达根 fiber。如果达到根 fiber 说明我们完成了本次渲染的所有任务。
*/
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
// 创建dom节点添加属性
function createDom(fiber) {
const dom =
fiber.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(fiber.type);
const isProperty = (key) => key !== "children"; // 注意这里是判断props里面不是children属性的其他属性
Object.keys(fiber.props)
.filter(isProperty)
.forEach((name) => {
dom[name] = fiber.props[name];
});
return dom;
}
/**
* render 创建 root Fiber
*/
function render(element, container) {
nextUnitWork = {
dom: container,
props: {
children: [element],
},
};
}
let nextUnitWork = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitWork && !shouldYield) {
nextUnitWork = performUnitOfWork(nextUnitWork);
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
/**
* performUnitOfWork 做三件事
* 1. add the element to the DOM
* 2. create the fibers for the element’s children
* 3. select the next unit of work
*/
function performUnitOfWork(fiber) {
// 创建dom
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom);
}
// 创建新的 fibers
const elements = fiber.props.children;
let index = 0;
let preSibling = null;
while (index < elements.length) {
const element = element[index];
const newFiber = {
// fiber 的结构
type: element.type,
props: elements.props,
parent: fiber,
dom: null,
};
if (index === 0) {
fiber.child = newFiber;
} else {
preSibling.sibling = newFiber;
}
preSibling = newFiber;
index++;
}
// 返回下一个要被处理的fiber
if (fiber.child) {
return fiber.child;
}
let nexFiber = fiber;
while (nextFiber) {
if (nexFiber.sibling) {
return nexFiber.sibling;
}
nexFiber = nexFiber.parent;
}
}
const Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
Didact.render(element, container);
# Step V: Render and Commit Phases
/**
* 一:
* 当前存在的问题:我们每处理一个fiber就要往DOM添加一个新的节点。
* 但是浏览器有可能会暂停整个渲染过程。这就会导致看到不完整的UI。
*/
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
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 commitRoot() {
commitWork(wipRoot, child);
wipRoot = null;
}
function commitWork(fiber) {
if (!fiber) {
return;
}
const domParent = fiber.parent.dom;
domParent.appendChild(fiber.dom);
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
};
nextUnitOfWork = wipRoot;
}
let nextUnitWork = null;
/**
* 二:
* 为了解决这个问题我们引入一个新的变量 wipRoot (work in progress root),用来保存正在处理中的 fiber tree
*
*/
let wipRoot = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitWork && !shouldYield) {
nextUnitWork = performUnitOfWork(nextUnitWork);
shouldYield = deadline.timeRemaining() < 1;
}
/**
* 三:
* 当所有的 fiber 都被处理完成后,通过 nextUnitWork 是否为 null 判断,
* 调用 commitRoot 函数一次性处理 dom 的插入
*/
if (!nextUnitWork && wipRoot) {
commitRoot();
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
const elements = fiber.props.children;
let index = 0;
let preSibling = null;
while (index < elements.length) {
const element = element[index];
const newFiber = {
type: element.type,
props: elements.props,
parent: fiber,
dom: null,
};
if (index === 0) {
fiber.child = newFiber;
} else {
preSibling.sibling = newFiber;
}
preSibling = newFiber;
index++;
}
if (fiber.child) {
return fiber.child;
}
let nexFiber = fiber;
while (nextFiber) {
if (nexFiber.sibling) {
return nexFiber.sibling;
}
nexFiber = nexFiber.parent;
}
// 最后没有找到说明到达根 fiber
return null;
}
const Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
Didact.render(element, container);
# Step VI: Reconciliation
/**
* 目前为止的实现只是往DOM中添加节点,还没有更新和删除操作。
*/
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: [],
},
};
}
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) => !(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, // alternate 属性指向老的fiber
};
deletions = [];
nextUnitOfWork = wipRoot;
}
let nextUnitOfWork = null;
let currentRoot = null; // 保存当前 Fiber Tree
let wipRoot = null;
let deletions = null; // 因为在 commit 阶段我们是在 wipRoot 上进行的,没法判断哪些节点是需要被删除的。所以使用 deletions 来保存需要被删除的节点。
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);
}
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;
}
}
/**
* 在这里对新旧 fibers 进行比较处理,也就是我们常说的 diff 过程。
* 这个过程又要调和
*/
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;
/**
* 比较规则:
* 1. 如果老 fiber 和新的 element 有相同的 type,只需要使用 props 更新 DOM 节点
* 2. 如果 type 不同并且是一个新加进来的 element,则需要创建一个新的 DOM 节点
* 3. 如果 type 不同,并且又有老的 fiber,则需要移除对应的 DOM 节点
*/
if (sameType) {
// 更新DOM节点
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: "UPDATE", // effectTag 标记用于commit阶段
};
}
if (element && !sameType) {
// 添加DOM节点
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: "PLACEMENT",
};
}
if (oldFiber && !sameType) {
// 删除旧的 fiber 节点
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 Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
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>
);
Didact.render(element, container);
};
rerender("World");
# Step VII: Function Components
/**
* 一:
* Function 组件有两处不同的地方
* 1. the fiber from a function component doesn’t have a DOM node
* 2. and the children come from running the function instead of getting them directly from the props
*/
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: [],
},
};
}
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) => !(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;
}
/**
* 四:
* 这里需要单独处理没有 dom 属性的 fiber
* 需要不断地往上寻找直到找到一个 Dom 节点
*/
let domParentFiber = fiber.parent
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent
}
const domParent = domParentFiber.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") {
commitDeletion(fiber, domParent)
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom)
} else {
commitDeletion(fiber.child, domParent)
}
}
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 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) {
const isFunctionComponent =
fiber.type instanceof Function
/**
* 三:所以在处理 fiber 的时候需要判断 fiber 的类型,如果是函数类型则需要单独处理
*/
if (isFunctionComponent) {
updateFunctionComponent(fiber)
} else {
updateHostComponent(fiber)
}
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
/**
* 如果是 Function 组件调用function获得children
*/
function updateFunctionComponent(fiber) {
// 这里 fiber.type 是定义的函数组件函数,接收 props 返回 element 结构
// 又因为 type 是 Function 所以这里没法直接创建 dom
// 所以在最后的 commit 阶段需要单独处理这样的 fiber
const children = [fiber.type(fiber.props)]
reconcileChildren(fiber, children)
}
/**
* 如果不是function组件则按之前的逻辑处理
*/
function updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
reconcileChildren(fiber, fiber.props.children)
}
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) {
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 Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
function App(props) {
return (
<div>
<h1>Hi {props.name}</h1>
<Container />
</div>
);
}
function Child() {
return <h3>Child hello world</h3>;
}
function Container() {
return (
<div>
<span>Container hello world</span>
<Child />
</div>
);
}
const element = <App name="foo" />;
/**
* 二:
* 这里 APP 是函数组件
* 最后返回的 element 的格式为 {type: f App(){},props: object}
*/
const container = document.getElementById("root");
Didact.render(element, container)
# Step VIII: Hooks
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: [],
},
};
}
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) => !(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;
}
let domParentFiber = fiber.parent;
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.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") {
commitDeletion(fiber, domParent);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom);
} else {
commitDeletion(fiber.child, domParent);
}
}
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 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) {
const isFunctionComponent = fiber.type instanceof Function;
if (isFunctionComponent) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
function updateFunctionComponent(fiber) {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
let wipFiber = null;
let hookIndex = null;
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];
}
function updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
reconcileChildren(fiber, fiber.props.children);
}
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) {
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 Didact = {
createElement,
render,
useState,
};
/** @jsx Didact.createElement */
function Counter() {
const [state, setState] = Didact.useState(1);
return <h1 onClick={() => setState((c) => c + 1)}>Count: {state}</h1>;
}
const element = <Counter />;
const container = document.getElementById("root");
Didact.render(element, container);
← JS引擎工作原理详解 TODO →