前言:最新在学习React16 内部原理的事项,为了更好的记录学习,梳理学习内容,把内容更新如下啦~
React16 Fiber的前生React15使用的递归有何弊端?
思路:在React15 的版本中采用的是Virtual Dom对比的方案,通过对比VirtualDom找出差异的部分,从而只将差异的部分更新到页面中去,避免更新整体Dom以提高性能。
弊端:但是在Virtual Dom 对比的过程中,React15 使用了递归,而递归的调用过程是不能终止的。所以当Virtual Dom 的层级比较深,递归的过程会长期占据主线程,而JavaScript 又是单线程的,不能同时执行多个任务。其他任务就需要等待递归的执行结束,才能开始。加上javaScript 的执行和UI的渲染是互斥的,此时的用户要么看到空白的界面,要么就是有界面但不能响应用户的操作。所以用户体验非常差。
简而言之,react15在查找对比的过程中,会大量长时间占据主线程,而且不能中断,容易造成页面的卡顿!
上代码:
// 通过balbel 转换jsx 代码,其实是一个就是js语法糖,
const jsx = (
<div id = "a1">
<div id = "b1">
<div id = "c1"></div>
<div id = "c2"></div>
</div>
<div id = "b2"></div>
</div>
)
// 转化为一个js 对象
{
"tyep":"div",
"prop":{
"id":"a1",
"children":[
{
"type":"div",
"props":{
"id":"b1",
"children":[
{
"type":"div",
"props":"c1"
},
{
"type":"div",
"props":"c2"
}
]
}
},
{
"type":"div",
"props":{
"id":"b2"
}
}
]
}
}
render(jsx对象,页面根元素 )
//!!重点!!使用render 函数添加到页面
function render (vdom,container){
// 创建元素
const newElement = document.createElement(vdom.type)
// 为元素添加属性
Object.keys(vdom.props)
.filter(propsName => propsName !== "children")
.forEach(propsName => newElement[propsName] = vdom.props[propsName])
// 关键代码!!!递归添加子元素 =》 在此处会消耗大量的性能
if(Array.isArray(vdom.props.children)){
vdom.props.children.forEach(child => render( child, container))
}
// 将元素添加到页面中
container.appendChild(element)
}
Fiber为啥能解决React15递归产生的弊端?
-
遍历方式:Fiber 放弃了原先递归的方式,而是采取循环,而循环是可以随时终止,后面再继续执行。这个特点为执行更加优先级的任务提供了可能
-
数据处理:React15 是将整个树作为一个大对象进行处理,故处理的比较耗时。而React16将分割成多个不同的小单元,然后通过链式表的结构去告知每个单元的父级,子级,兄弟级的元素。这个特点减少的耗时的时间,小而美~
通俗来说就是把一个大的树对象变成多个小对象,里面包含更多的属性去指向自己的父级,子级,和兄弟级。主要的属性如下
- 执行方式:Fiber 采用利用浏览器的空余时间去执行的,在浏览器有空余的时间的时候才会去进行dom 的对比更新,一旦发现浏览器没有更多的空余时间,或者有更高级的任务,就会退出来执行更加优先级的任务。 这个特点会避免dom 对比的过程中 大量占据主线程,而是把主线程的控制权交给浏览器。
以下是执行的流程示意图
Fiber的实现方式,利用循环模拟递归
Fiber的工作分为两个阶段:render 阶段和 commit 阶段。 render 阶段:构建render对象,构建链表,在链表中标记要执行的dom操作,可中断。 commit 阶段:根据构建好的链表进行DOM操作,不可中断。
循环的循序:从上往下走,构建节点对应的Fiber对象。从A1找到子级B1,B2;再找到B1的子级C1,C2;再找C1的子集,如果C1没有子集,那么开始找兄弟C2,再找兄弟C2是否有子集,没有子集,再找是否有兄弟集,如果都没有,则开始网上寻找B1 ,寻找B1是否有兄弟集-B2,找到B2,再次查看是否有子集和兄弟集。
似乎有点绕~~简单来说,从根节点出发,自上而下先找子集,有子集,子集优先构建,如果没有子集,查找兄弟节点。有兄弟节点再次判断该兄弟节点是否是有子集和兄弟集,若两样都没有,则开始循环往上寻找。最后,由下往上回到根节点。
实现思路代码
// 利用Fiber代码实现
const container = document.getElementById("root")
// 主动构建根节点的Fiber对象
const workInProgressRoot = {
stateNode : container,
props:{
children:[jsx]
}
}
// 下一个要执行的任务,是否还有其他的对象
let nextUnitOfWork = workInProgressRoot
function workLoop(deadline){
// 1.是否有空余时间
// 2.是否有执行的任务
while(nextUnitOfWork && deadline.timeRemaining() > 0){
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
// 如果没有执行的任务,代表任务已经执行完成
if( !nextUnitOfWork){
//进行更新Dom的操作
commitRoot()
}
}
function performUnitOfWork(workInProgressFiber){
// 向下开始构建每个fiber对象
beginWork(workInProgressFiber)
//如果当前有子集,继续返回对象,接着去构建子集的子集
if(workInProgressFiber.child){
return workInProgressFiber.child
}
while(workInProgressFiber){
// 开始向上构建链表
completeUnitOfWork(workInProgressFiber)
// 如果有同级
if(workInProgressFiber.sibling){
// 返回 同级,构建同级的子集
return workInProgressFiber.sibling
}
workInProgressFiber = workInProgressFiber.return
}
}
function completeUnitOfWork(){
// 获取当前Fiber 的父级
const returnFiber = workInProgressFiber.return
// 判断父级是否存在
if(returnFiber){
//要执行的Dom操作
if(workInProgressFiber.effectTag){
// 由下往上开始构建链表,比较复杂
if(!returnFiber.lastEffect){
returnFiber.lastEffect = workInProgressFiber.lastEffect
}
if(returnFiber.firstEffect){
returnFiber.lastEffect.nextEffect = workInProgressFiber
}
if(returnFiber.lastEffect){
returnFiber.lastEffect.nextEffect = workInProgressFiber
}else{
returnFiber.lastEffect = workInProgressFiber
}
returnFiber.lastEffect = workInProgressFiber
}
}
}
function commitRoot(){
let currentFiber = workInProgressFiber.firstEffect
while(currentFiber){
currentFiber.return.stateNode.appendChild(currentFiber.stateNode)
currentFiber = currentFiber.nextEffect
}
}
function beginWork(workInProgressFiber){
// 1.创建Dom元素,并添加到stateNOde 属性
if( !workInProgressFiber.stateNode){
workInProgressFiber.stateNode = document.createElement(workInProgressFiber.type)
}
// 为Dom添加属性
for(let attr in workInProgressFiber.props){
if(attr !== "children"){
workInProgressFiber.stateNode[attr] = workInProgressFiber.props[attr]
}
}
// 2;构建当前Fiber的子集Fiber
if(Array.isArray(workInProgressFiber.props.child)){
let preChildFiber = null
workInProgressFiber.props.child.forEach((child,index)=>{
let childFiber = {
type: child.type,
props: child.props,
effectTag:'PLACEMENT',
return: workInProgressFiber
}
if(index === 0){
workInProgressFiber.child = childFiber
}else{
preChildFiber.sibling = childFiber
}
preChildFiber = childFiber
})
}
}
// 在浏览器的空余时间去执行构建剩余Fiber对象的代码
requesetIdleCallback (workLoop)
完毕~