手写React的Fiber架构,深入理解其原理 (2)

上面我们简单的实现了虚拟DOM渲染到页面上的代码,这部分工作被React官方称为renderer,renderer是第三方可以自己实现的一个模块,还有个核心模块叫做reconsiler,reconsiler的一大功能就是大家熟知的diff,他会计算出应该更新哪些页面节点,然后将需要更新的节点虚拟DOM传递给renderer,renderer负责将这些节点渲染到页面上。但是这个流程有个问题,虽然React的diff算法是经过优化的,但是他却是同步的,renderer负责操作DOM的appendChild等API也是同步的,也就是说如果有大量节点需要更新,JS线程的运行时间可能会比较长,在这段时间浏览器是不会响应其他事件的,因为JS线程和GUI线程是互斥的,JS运行时页面就不会响应,这个时间太长了,用户就可能看到卡顿,特别是动画的卡顿会很明显。在React的官方演讲中有个例子,可以很明显的看到这种同步计算造成的卡顿:

1625d95bc100c7fe

而Fiber就是用来解决这个问题的,Fiber可以将长时间的同步任务拆分成多个小任务,从而让浏览器能够抽身去响应其他事件,等他空了再回来继续计算,这样整个计算流程就显得平滑很多。下面是使用Fiber后的效果:

1625d95bc2baf0e1

怎么来拆分

上面我们自己实现的render方法直接递归遍历了整个vDom树,如果我们在中途某一步停下来,下次再调用时其实并不知道上次在哪里停下来的,不知道从哪里开始,即使你将上次的结束节点记下来了,你也不知道下一个该执行哪个,所以vDom的树形结构并不满足中途暂停,下次继续的需求,需要改造数据结构。另一个需要解决的问题是,拆分下来的小任务什么时候执行?我们的目的是让用户有更流畅的体验,所以我们最好不要阻塞高优先级的任务,比如用户输入,动画之类,等他们执行完了我们再计算。那我怎么知道现在有没有高优先级任务,浏览器是不是空闲呢?总结下来,Fiber要想达到目的,需要解决两个问题:

新的任务调度,有高优先级任务的时候将浏览器让出来,等浏览器空了再继续执行

新的数据结构,可以随时中断,下次进来可以接着执行

requestIdleCallback

requestIdleCallback是一个实验中的新API,这个API调用方式如下:

// 开启调用 var handle = window.requestIdleCallback(callback[, options]) // 结束调用 Window.cancelIdleCallback(handle)

requestIdleCallback接收一个回调,这个回调会在浏览器空闲时调用,每次调用会传入一个IdleDeadline,可以拿到当前还空余多久,options可以传入参数最多等多久,等到了时间浏览器还不空就强制执行了。使用这个API可以解决任务调度的问题,让浏览器在空闲时才计算diff并渲染。更多关于requestIdleCallback的使用可以查看MDN的文档。但是这个API还在实验中,兼容性不好,所以React官方自己实现了一套。本文会继续使用requestIdleCallback来进行任务调度,我们进行任务调度的思想是将任务拆分成多个小任务,requestIdleCallback里面不断的把小任务拿出来执行,当所有任务都执行完或者超时了就结束本次执行,同时要注册下次执行,代码架子就是这样:

function workLoop(deadline) { while(nextUnitOfWork && deadline.timeRemaining() > 1) { // 这个while循环会在任务执行完或者时间到了的时候结束 nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } // 如果任务还没完,但是时间到了,我们需要继续注册requestIdleCallback requestIdleCallback(workLoop); } // performUnitOfWork用来执行任务,参数是我们的当前fiber任务,返回值是下一个任务 function performUnitOfWork(fiber) { } requestIdleCallback(workLoop);

Fiber可中断数据结构

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfxpf.html