react.createElement
创建出一个对象
React中:
const element = <h1 title="foo">Hello</h1>
const container = document.getElementById('root')
ReactDOM.render(element, container)
自己实现:
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)
createElement
实现自己的 createEelment 而不是 React.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: [],
},
}
}
eg
createElement('div')会返回:
{
type: 'div'
props: {
children: []
}
}
挂载到 Ha0ran 上
const Ha0ran = {
createElement,
}
就可以像用 React.createElement 一样用 Ha0ran.createElement
render
我们从用 element type 创建 DOM 节点开始,并且把新节点追加到 container 上
function rener(element, container) {
const dom = document.createElement(element.type)
container.appendChild(dom)
}
递归处理子元素
function render(element, container) {
const dom = document.createElement(element.type)
element.props.children.forEach(child => {
render(child, dom);
})
container.appendChild(dom);
}
我们需要处理文本元素,当 element.type 是 TEXT_ElEMENT 的时候需要创建文本节点而不是常规的节点,所以 dom 出代码需要改为
const dom =
element.type === 'TEXT_ELEMENT'
? document.createTextNode("")
: document.createElement(element.type)
最后把元素的属性挂载到节点上(除了children),这里指的属性是 ‘id’ ‘title’ ‘class’ 这些属性
const isProperty = key => key !== 'children')
Obejct.keys(element.props)
.filter(isProperty)
.forEach(name => {
dom[name] = element.props[name]
})
render代码汇总
function render(element, container) {
// 创建dom节点 对文本进行了处理
const dom =
element.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(element.type);
// 找出 element 的属性部分
const isProperty = key => key !== 'children';
// 将属性挂载到 dom 上
Object.keys(element.props)
.filter(isProperty)
.forEach(name => {
dom[name] = element.props[name];
});
// 递归渲染 子元素
element.props.children.forEach(child => {
render(child, dom);
})
// 挂载到 container 容器上展示
container.appendChild(dom);
}
Concurrent Mode
对于上面的递归函数有问题。
一旦我们开始渲染就不会停止直到整个渲染完成。如果这颗元素树很大,就会造成阻塞主线程。
所以就需要把工作分解成小的单元,每完成一个小的单元就打断渲染如果有其他的事情需要做。
// 下一个单元的工作
let nextUnitOfWork = null;
// 工作循环,循环执行单元,渲染任务
function workLoop(deadline) {
// 打断的标志
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = preformUnitOfWork(nextUnitOfWork);
// 打断标志取决于时间
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(nextUnitOfWork) {
// ToDo
}
这里用 requestIdleCallback 做了一个循环,你可以把它看作是 setTimeout,但是requestIdleCallback 会在浏览器空闲的时候浏览器自动调用。
React 官方已经没有用 requestIdleCallback 了,但是实现的效果是差不多的。
Fiber
为了组织这些工作单元,我们需要一个数据结构:a fiber tree
我们将为每个元素提供一个 fiber 并且每个 fiber 都是一个工作单元。
eg. 我们想要渲染这么一颗树:
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>
那么就会创建一颗这样的 fiber tree:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFEbGtgl-1629298565181)(https://i.loli.net/2021/08/18/nBlyXvpxq1YQTcI.png)]
在 render 中我们创建了根节点 root fiber,并将其设置为 nextUnitOfWork,剩下的工作将在 performUnitOfWork 中进行,在 performUnitOfWork 这里我们要做三件事:
- 将元素添加到 DOM 上
- 给元素的每一个子元素都创建 fiber
- 选择下一个工作单元
function performUnitOfWork(fiber) {
// ToDo add dom node
// Todo create new fiber
// Todo return next unit of work
}
这种数据结构的目标之一是轻松的找到下一个工作单元,所以每一个 fiber 都有一个一个连接到他的子节点,兄弟节点,和父节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYYbxDhB-1629298565183)(https://i.loli.net/2021/08/18/nPl4qxJfyvMkeL1.png)]
如果该 fiber 的工作完成,就会去找下一个 工作单元,而这个工作单元就是他下一个子元素的 fiber。如果没有子元素那就去找 兄弟元素 sibling,如果兄弟元素也没有了就去找他的父元素,这里可以是兄弟元素的父元素,比如上面的 a 指向了 h1。最后直到回到 root 就算渲染完成。
创建一个 createDom 函数:
function createDom(fiber) {
const dom =
fiber.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(fiber.type)
const isProperty = key => key !== 'children';
Object.keys(fiber.type)
.filter(isProperty)
.forEach(name => {
dom[name] = fiber.props[name]
})
return dom;
}
改变 render 函数:
function render(element, container) {
nextUnitOfWork = {
dom: container,
props: {
children: [element]
}
}
}
在 render 函数里我设置了 nextUnitOfWork 到 fiber 树的 root节点。
performUnitOfWork:
function performUnitOfWork(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
// Todo create new fiber
// Todo return next unit of work
}
然后对于每个子元素都创建一个新的 fiber
function performUnitOfWork(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
const elements = fiber.props.children;
const index = 0;
let preSibling = null;
while (index < elements.length) {
const element = elements[index];
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,
}
}
// Todo return next unit of work
}
然后我们把 newFiber 添加到 fiber tree 中将其设为孩子或者兄弟,取决于它是否是第一个孩子。
function performUnitOfWork(fiber) {
// ToDo add dom node
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
// Todo create new fiber
const elements = fiber.props.children;
const index = 0;
let preSibling = 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 {
preSibling.sibling = newFiber
}
preSibling = newFiber
index++;
}
// Todo return next unit of work
}
最后我们找下一个工作单元:
function performUnitOfWork(fiber) {
// ToDo add dom node
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
// Todo create new fiber
const elements = fiber.props.children;
const index = 0;
let preSibling = 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 {
preSibling.sibling = newFiber
}
preSibling = newFiber
index++;
}
// Todo return next unit of work
if (fiber.child) {
return fiber.child;
}
let nextFiber = fiber;
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.parent;
}
}
Render and Commit phases
为了让用户看到完整的 UI(因为浏览器会打断我们的渲染工作),我们需要删除 performUnitOfWork 中的这段代码
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
并在 render 函数中这样跟踪 fiber 树中的根节点
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
}
nextUnitOfWork = wipRoot;
}
let nextUnitOfWork = null
let wipRoot = null
一旦完成(没有下一个工作单元了)我们就提交整个 fiber tree 给DOM
function commitRoot() {
// ToDo add nodes to dom
commitWork(wipRoot.child);
wipRoot = null;
}
// 递归 将所有节点追加到 dom 上
function commitWork(fiber) {
if (!fiber) {
return
}
const domParent = fiber.parent.dom;
domParent.appendChild(fiber.dom)
commitWork(fiber.child)
commitWork(fiber.sibling)
}
function workLoop(deadline) {
...
...
if (!nextUnitOfWork && wipRoot) {
commitRoot();
}
}
到目前为止我们只做了添加功能,下面还要做更新和删除功能
Reconciliation
我们需要保存一个保存一个引用指向"上一个 fiber tree 我们提交到DOM上的"当我们完成提交。取名为 currentRoot。
还需要给每个 fiber 添加一个属性叫 alternate,这个属性指向上一个 fiber(在上一次提交阶段我们提交到DOM上的)。
function commitRoot(){
commitWork(wipRoot.child)
// newly added snippet
currentRoot = wipRoot
wipRoot = null
}
function render(element, container) {
wipRoot = {
dom: container,
props: {
children: [element],
},
// newly added snippet
alternate: currentRoot,
}
nextUnitOfWork = wipRoot;
}
let nextUnitOfWork = null
// Newly added snippet
let currentRoot = null
let wipRoot = null
现在的 perfromUnitOfWork
function performUnitOfWork(fiber) {
// ToDo add node to dom
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
// ToDo create new fiber
const elements = fiber.props.children;
const index = 0;
const prevSibling = null;
while (index < elements.length) {
const element = elements[index];
const newFiber = {
dom: null,
type: element.type,
props: element.props,
parent: fiber
}
if (index === 0) {
fiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}
prevSibling = newFiber;
index++;
}
// ToDo return next unit of work
if (fiber.child) {
return fiber.child;
}
let newFiber = fiber;
while(newFiber) {
if (newFiber.sibling) {
return newFiber.sibling;
}
newFiber = newFiber.parent;
}
}
添加一个新的函数:reconcileChildren
同时迭代之前的fiber的子元素(wipFiber.alternate)和我们想要调和的元素的数组
这段时间,我们只留下两个最重要的东西:oldFiber 和 elements
element 是我们想要渲染到 dom 上的东西
oldFiber 是我们之前渲染的东西
function performUnitOfWork(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
const elements = fiber.props.children;
reconcileChildren(fiber, element);
if (fiber.child) {
...
}
}
// 这里我们将调和之前的fiber和新的元素
function reconcileChildren (wipFiber, elements) {
let index = 0;
let oldFiber =
wipFiber.alternate && wipFiber.alternate.child;
while (
index < element.length ||
oldFiber != null // 注意。这里不能用 !== ,否则不行,具体原因还未知
) {
const element = element[index];
// ToDo compare oldFiber to element
}
}
比较 oldFiber 和 element 的方法:
这里类型说的是节点类型 比如 div h1 这种类型
- 如果之前的 fiber 和 新的元素类型相同,我们保留这个节点,仅仅只是更新新的属性。[1]
- 如果类型不一样并且这里有新的元素进来,我们就要创建节点。[2]
- 如果类型不同并且还有之前的 fiber(节点与新的节点不一样),就要移除之前的节点 [3]
const sameType =
oldFiber &&
element &&
element.type === oldFiber.type;
if (sameType) {
// ToDo update the node
}
if (element && !sameType) {
// ToDo add this node
}
if (oldFiber && !sameType) {
// ToDo delete the oldFiber's node
}
当为 [1] 的时候:
if (sameType) {
newFiber = {
type: oldFiber.type,
props: element.props,
dom: oldFiber.dom,
parent: wipFiber,
alternate: oldFiber,
effectTag: 'UPDATE',
}
}
这里我们添加了一个新的属性: effectTag 后面提交阶段会用到。
当为 [2] 的时候:
if (element && !sameType) {
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: 'PLACEMENT',
}
}
当为 [3] 时:
if (oldFiber && !sameType) {
oldFiber.effectTag = 'DELETION';
deletions.push(oldFiber);
}
但是当我们从正在进行的根节点把 fiber tree 提交到 DOM 时,是没有之前的 fiber的,所以 render 函数中需要加个数组跟踪我们想要移除的节点。
funciton render(element, container) {
...
deletions = [];
...
}
let deletions = null;
function commitRoot() {
deletions.forEach(commitWork);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
}
修改 commitWork 的代码,处理不同的 eefectTags
function updateDom(dom, prevProps, nextProps) {
// ToDo
}
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 === 'DELETION') {
domParent.removeChild(fiber,dom);
} else if (
fiber.effectTag === 'UPDATE' &&
fiber.dom !== null
) {
updateDom(
fiber.dom,
fiber.alternate.props,
fiber.props
)
}
}
我们比较之前 fiber 的 props 和新的 fiber 的 props,移除已经消失的 props,设置新的或者改变的 props。
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 properties
Object.keys(prevProps)
.filter(isProperty)
.filter(isGone(prevProps, nextProps))
.forEach(name => {
dom[name] = ""
});
// Set new or changed properties
Obejct.keys(nextProps)
.filter(isProperty)
.filter(isNew(prevProps, nextProps))
.forEach(name => {
dom[name] = nextProps[name];
});
}
我们还需要处理这样一个特殊的属性:事件监听(以 on 开头的)
所以代码应该改为
const isEvent = key => key.startsWith('on');
const isProperty = key =>
ket !== 'children' && !isEvent(key);
updateDom 中添加对事件的处理:移除和添加
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,
preProps[name]
)
})
// Add event listener
Object.keys(nextProps)
.filter(isEvent)
.filter(isNew(prevProps, nextProps))
.forEach(name => {
const eventType = name
.toLowerCase()
.substring(2);
dom.addEventListener(
eventType,
nextProps[name]
)
})
}
对目前实现的代码做个汇总:
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,
}
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) {
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) {
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++
}
}
Function Component
函数组件与之前不同的地方在于
- fiber 来自于函数组件没有 DOM 节点
- 子元素来自于于函数的执行而不是直接从 props 中获取
改变 performUnitOfWork 的代码
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) {
// ToDo
}
function updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
reconcileChildren(fiber, fiber.props.children);
}
在 updateFunctionComponent 中我们执行函数然后拿到 children
function updateFunctionComponent(fiber) {
const children = [fiber.type(fiber.props)]
reconcileChildren(fiber, children);
}
将 commitWork 中
const domParent = fiber.parent.dom
改为
let domParentFiber = fiber.parent
// 一直往上找直到找到 DOM节点
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.dom
删除的时候也要改
if (fiber.effectTag === 'DELETION') {
domParent.removeChild(fiber.dom)
}
改为
if (fiber.effectTag === 'DELETION') {
commitDeletion(fiber, domParent);
}
function commitDeletion(fiber, domParent) {
if (fiber.dom) {
domParent.removeChild(fiber.dom)
} else {
commitDeletion(fiber.child, domParent)
}
}
Hooks
let wipFiber = null
let hookIndex = null
function updateFunctionComponent(fiber) {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
}
function useState(initial) {
// ToDo
}
当调用 useState 的时候,检查是否有 old hook,通过用 hook index 检查 fiber 中的 alternate。
如果有 old hook,就把 state 从 old hook 中复制到 new hook 中,如果没有就初始化一个。
当我们给 fiber 添加了一个 hook,就将索引值 index 加一,并返回 state。
function useState(initial) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex]
const hook = {
state: oldHook ? oldHook.state : initial,
}
wipFiber.hooks.push(hook);
hookIndex++;
return [hook, state];
}
我们还需要添加一个方法更新 state,就是 setState。
然后我们做一些类似于我们在渲染函数中所做的事情,将一个新的正在进行的工作根设置为下一个工作单元,这样工作循环就可以开始一个新的渲染阶段。
我们在下一次渲染组件时这样做,我们从旧的钩子队列中获取所有的动作,然后将它们一一应用到新的钩子状态,所以当我们返回状态时,它会被更新。
function useState(initial) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex]
const hook = {
state: oldHook ? oldHook.state : initial,
// Newly added snippet
queue: [],
}
// Newly added snippet
const actions = oldHook ? oldHook.queue: []
actions.forEach(action => {
hook.state = action(hook.state);
})
// Newly added snippet
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];
}
至此,我们完成了自己的 react。
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;
}
}
let wipFiber = null;
let hookIndex = null;
function updateFunctionComponent(fiber) {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
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++;
}
}
原文连接: build your own react