看过我的《jsx如何转换成react-fiber》文章的朋友都知道,jsx先是通过babel编译和转化,变成vNode结构,这个结构很重要,react的渲染是离不开current Fiber树和workInprogress Fiber树,这两棵树都是由以下结构为模板生成的。
let vNode = {
"type": "div",
"props": {
"className": "container",
"children": [{
"type": "h1",
"props": {
"children": [{
"type": "TEXT_ELEMENT",
"props": {
"nodeValue": "Hello, World!",
"children": []
}
}]
}
}, {
"type": "p",
"props": {
"children": [{
"type": "TEXT_ELEMENT",
"props": {
"nodeValue": "This is an example component.",
"children": []
}
}]
}
}]
}
}
生成一棵fiber树是不可以用递归去生成的。原因是,递归如果太深会造成render函数长时间占用主线程,造成页面卡顿。所以,在这里必须使用这两者配合去生成fiber树:调度器、协调器。
调度器
requestIdleCallback在浏览器空闲时期被调用并执行workLoop函数,在workLoop中会检查nextUnitOfWork是否存在,存在的话,就继续执行performUnitOfWork。
let nextUnitOfWork = null
function workLoop(deadline) {
let shouldYield = false
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork( nextUnitOfWork )
shouldYield = deadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop)
协调器
那么nextUnitOfWork是什么呢?performUnitOfWork又做了什么呢?
let nextUnitOfWork = null
let currentRoot = null // 保存着当前页面对应的fiber tree,初始化时为null
function render(element, container){
nextUnitOfWork = {
dom: container,
props: { children: [element]},
alternate: currentRoot,
}
}
const container = document.getElementById("root")
const element = vNode;
render(element, container)
在这里,vNode是由最新的JSX重新编译出来的vNode树,结构在最上面可见。(不太了解可以看《JSX是如何变成react-fiber的》)
所以,当初始化或者更新的时候,就会调用render函数,给nextUnitOfWork进行赋值,当满足(nextUnitOfWork && !shouldYield)时,调用performUnitOfWork函数。
function performUnitOfWork(fiber) {
// 第一步 根据fiber节点创建真实的dom节点,并保存在fiber.dom属性中
if(!fiber.dom){
fiber.dom = createDom(fiber) // 不核心,所以就不展示代码了
}
// 第二步 给子元素创建对应的fiber节点
const children = fiber.props.children
reconcileChildren(fiber, children)
// 第三步,查找下一个工作单元
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++
}
}
以上代码做了什么事情呢?首先children是什么呢?它就是[vNode,vNode]结构。
如果您嫌代码太长,理不通顺,请跟进我的脚步!让我们一起拆开来看看。
第二步
首先,当前Fiber节点有孩子的时候,也就是以下的vNode有children的时候,就将该children作为参数传入reconcileChildren函数中,同时也传入当前的Fiber节点
let vNode = {
"type": "div",
"props": {
"className": "container",
"children": [{
"type": "h1",
"props": {
"children": [{
"type": "TEXT_ELEMENT",
"props": {
"nodeValue": "Hello,",
"children": []
}
},{
"type": "TEXT_ELEMENT",
"props": {
"nodeValue": "World!",
"children": []
}
}]
}
}
}
进入reconcileChildren函数中,我们可以发现,它循环了children,创建第一个子节点时,父节点和它使用child进行关联,其余的子节点通过sibling属性进行关联第一个Child节点
// 参数:父Fiber节点,父Fiber节点的children(vNode结构)
function reconcileChildren(wipFiber, elements) {
let index = 0
let prevSibling = null
while (index < elements.length || oldFiber != null) {
const element = elements[index]
let newFiber = null
// 这里的new Fiber是我写的伪代码,指的是根据不同状态去创建Fiber节点
newFiber = new Fiber()
if (index === 0) {
wipFiber.child = newFiber
} else if (element) {
prevSibling.sibling = newFiber
}
prevSibling = newFiber index++
}
}
第三步
在第二步中,我们发现父Fiber节点和子Fiber节点关联,那么,完成第二步后,到第三步时,下一个nextUnitOfWork就变成了Fiber的子节点,如果Fiber并没有子节点呢?没关系,它会去寻找Fiber的兄弟节点作为下一个nextUnitOfWork,并返回。
if(fiber.child){
return fiber.child
}
let nextFiber = fiber
while(nextFiber){
if(nextFiber.sibling){
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
在这里我们可以看出,创建Fiber树的过程并不是连续的,它一边一层一层地深度遍历Vnode模板,一边创建Fiber节点,并且在完成一次Fiber节点的创建后,再将下一个“毛坯”状态的Fiber给到公共变量nextUnitOfWork,而下一个Fiber的选择,首先是深度遍历,给到child,如果没有child再寻找sibling节点;当都没有的时候,就递归回到上一层,找上一层的sibling。
然后,等待下一次的浏览器空余时间片,再重新调起
function workLoop(deadline) {
let shouldYield = false
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork( nextUnitOfWork )
shouldYield = deadline.timeRemaining() < 1
}
requestIdleCallback(workLoop)
}
requestIdleCallback(workLoop)
完美闭环!
创作不易,谢谢mini-react的创作者,让我在短时间内看懂react原理,再也不怕了!