大家都知道jsx是一种js方言,在react中,它能更方便我们的在使用js去创建dom。例如:
let element = (
<div id="A1">
A1
<div id="B1">
B1
<div id="C1">C1</div>
<div id="C2">C2</div>
</div>
<div id="B2">B2</div>
</div>
)
经过了babel编译后会生成这样子一个js对象,它能描述出当前jsx的属性(可以复制去https://www.json.cn/)去格式化下。
{"type":"div","props":{"id":"A1","children":[{"props":{"text":"A1","children":[]}},
{"type":"div","props":{"id":"B1","children":[{"props":{"text":"B1","children":[]}},
{"type":"div","props":{"id":"C1","children":[{"props":{"text":"C1","children":[]}}]}},
{"type":"div","props":{"id":"C2","children":[{"props":{"text":"C2","children":[]}}]}}]}},
{"type":"div","props":{"id":"B2","children":[{"props":{"text":"B2","children":[]}}]}}]}}
那我们需要怎么把它转化成fiber数据类型呢?let's go!
在react的js入口文件,都会有把虚拟dom通过ReactDom.render方法渲染到原生html上
ReactDom.render(Element, document.getElementById('root'));
在正式
创建文件ReactDom.js
function render(element, container) {
let rootFiber = { // 根fiber
stateNode: container,
props: { children: [element] }
}
schduleRoot(rootFiber)
}
let nextUnitOfWork = null
function schduleRoot(rootFiber) {
nextUnitOfWork = rootFiber
}
function workLoop(deadline) { // 实现可中断、恢复
let shouleYield = false
while(nextUnitOfWork && !shouleYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1 // 如果剩余时间小于1,则继续往下走
}
requestIdleCallback(workLoop, { timeout: 500 })
}
function performUnitOfWork(currentFiber) {
let newChildren = currentFiber.props.children
reconcileChildren(currentFiber, newChildren) // 把子节点转成fiber结构
if(currentFiber.child) {
return currentFiber.child
}
while(currentFiber) {
if(currentFiber.sibling) {
return currentFiber.sibling
}
currentFiber = currentFiber.return
}
}
function reconcileChildren(currentFiber, newChildren) {
let newChildrenIndex = 0;
let prevSibling // 上一个兄弟节点
while(newChildrenIndex < newChildren.length) {
let newChild = newChildren[newChildrenIndex] // 第x项
let newFiber = {
type: newChild.type,
props: newChild.props,
return: currentFiber,
}
if(newChildrenIndex === 0) { // 如果是大儿子的话,就让它成为child
currentFiber.child = newFiber
} else { // 如果不是大儿子,那就让它成为它哥哥的sibling
prevSibling.sibling = newFiber
}
prevSibling = newFiber
newChildrenIndex++
}
}
requestIdleCallback(workLoop, { timeout: 500 })
const ReactDom = {
render
}
这样一通操作下来虚拟dom对象,就转成了fiber对象,并且给它转成单链表的。实际上react的fiber对象是远远多与上面写的。像这个样子:
{
// 实例属性
tag: tag, // 标记不同组件类型,如函数组件、类组件、文本、原生组件...
key: key, // react 元素上的 key 就是 jsx 上写的那个 key ,也就是最终 ReactElement 上的
elementType: null, // createElement的第一个参数,ReactElement 上的 type
type: null, // 表示fiber的真实类型 ,elementType 基本一样,在使用了懒加载之类的功能时可能会不一样
stateNode: null, // 实例对象,比如 class 组件 new 完后就挂载在这个属性上面,如果是RootFiber,那么它上面挂的是 FiberRoot,如果是原生节点就是 dom 对象
// fiber
return: null, // 父节点,指向上一个 fiber
child: null, // 子节点,指向自身下面的第一个 fiber
sibling: null, // 兄弟组件, 指向一个兄弟节点
index: 0, // 一般如果没有兄弟节点的话是0 当某个父节点下的子节点是数组类型的时候会给每个子节点一个 index,index 和 key 要一起做 diff
ref: null, // reactElement 上的 ref 属性
pendingProps: pendingProps, // 新的 props
memoizedProps: null, // 旧的 props
updateQueue: null, // fiber 上的更新队列执行一次 setState 就会往这个属性上挂一个新的更新, 每条更新最终会形成一个链表结构,最后做批量更新
memoizedState: null, // 对应 memoizedProps,上次渲染的 state,相当于当前的 state,理解成 prev 和 next 的关系
mode: mode, // 表示当前组件下的子组件的渲染方式
// effects
effectTag: NoEffect, // 表示当前 fiber 要进行何种更新(更新、删除等)
nextEffect: null, // 指向下个需要更新的fiber
firstEffect: null, // 指向所有子节点里,需要更新的 fiber 里的第一个
lastEffect: null, // 指向所有子节点中需要更新的 fiber 的最后一个
expirationTime: NoWork, // 过期时间,代表任务在未来的哪个时间点应该被完成
childExpirationTime: NoWork, // child 过期时间
alternate: null,// current 树和 workInprogress 树之间的相互引用
}
网友评论