build-mini-react
createElement
原码:
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
react创建元素的方法如下:
const element = React.createElement(
"div",
{ id: "foo" },
React.createElement("a", null, "bar"),
React.createElement("b")
)
实际上就是:
React.createElement(
type,
[props],
[...children]
)
我们可以看作是创建了一个包含type和props的对象:
React.createElement(
type,
props: {
...props,
children
}
)
举例:
createElement("div", null, a, b) 相当于
{
"type": "div",
"props": { "children": [a, b] }
}
render
render函数最初源码如下:
function render(element, container) {
const dom = document.createElement(element.type)
container.appendChild(dom)
}
为了渲染子元素,需要递归调用render
function render(element, container) {
const dom = document.createElement(element.type)
element.props.children.forEach(child =>
render(child, dom)
)
container.appendChild(dom)
}
给元素添加属性:
function render(element, container) {
const dom = .....
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)
}
在上述render的实现函数中,在数据量大的情况下,dom的渲染会严重阻塞主流程,如下代码:
element.props.children.forEach(child =>
render(child, dom)
)
所以需要化整为零,将dom的渲染拆分成一个个小的工作单元, 在workloop函数中进行递归。当任何一个小的工作单元完成时,浏览器都可以中断渲染去进行更高优先级的工作。
使用requestIdleCallback去优化代码。在主线程空闲时,浏览器会调用requestIdleCallback的回调函数.
function workloop(deadline) {
.....
requestIdleCallback(workloop)
}
requestIdleCallback(workloop)
在workloop中需要创建一个performUnitOfWork去执行每一个小的work unit.
function workloop(deadline) {
...
while(nextUnitOfWork) {
...
performUnitOfWork(nextUnitOfWork)
...
}
requestIdleCallback(workloop)
}
requestIdleCallback(workloop)
function performUnitOfWork(nextUnitOfWork) {
// TODO
}
Fibers
fiber tree 是一个数据结构,用于处理好每一个工作单元(work unit)之间的联系。
每一个元素都是一个fiber,每一个fiber对应一个工作单元。
比如,我们渲染如下DOM:
React.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
container
)
在render函数中,创建了一个root fiber,并把它设置成nextUnitOfWork。剩下的事情,交给performUnitOfWork函数处理。
在performUnitOfWork中,将为每一个fiber做三件事:
- 将元素添加到dom中
- 为子元素创建fiber
- 选择下一个工作单元
当我们完成了对一个fiber的操作,如果它有子元素,那么这个子元素的fiber将是下一个工作单元。在本例中,如果我们完成的是div元素的fiber操作,那么下一个工作单元将是h1的fiber。
如果这个fiber没有child,将查找sibling fiber作为下一个工作单元。例如p元素没有child,那么a元素的fiber便成了next unit work.
如果这个fiber既没有child,也没有sibling,则将查找他的parent的sibling。比如a元素fiber的下一个工作单元就是h2的fiber。
如果parent也没有sibling,那我们就一直向上查找,一直到root fiber。