前言
随着mvvm模式的流行,现在大多数的前端框架基本都是在react和vue中选择其一,react的核心就是尽量避免开发者去操作真实的dom,可以让开发者直接操作数据,然后react来完成真实dom的更新。
react可以做到这一点,就是基于虚拟dom的reconciliation,其实就是对比两颗虚拟dom树的差异,然后渲染到浏览器上面。
但是人总是要有追求,两年前左右react就发现了reconciliation的一个潜在问题,就是在对比两颗树的时候,花费的时间太长,可能导致浏览器假死,所以就启动了一个项目来重写reconciliation,那就是react fiber.
为什么?
这里不得不提浏览器的渲染机制,现在基本上公认的是60fps,也就是说浏览器会在每秒内渲染60次,也就是基本上16.7ms渲染一次。
(为什么是60fps呢,这里和硬件的刷新频率有关系,有兴趣的可以查下)
基本渲染流程如下
1,执行js
2,样式计算
3,计算布局,执行
4,pait,绘制各层
5,合成各层的绘制结果,呈现在浏览器上。
所以基本上就是在16.7ms内执行完这些操作,就是比较完美的啦,但是事情不可能这么完美,比如如果js代码执行时间特别长的话,一直在等你的js执行完之后,才会去渲染,页面就是一直空白。
例子:
var then = Date.now()
var i = 0
var el = document.getElementById('message')
while (true) {
var now = Date.now()
if (now - then > 1000) {
if (i++ >= 10) {
break;
}
el.innerText += 'hello!\n'
console.log(i)
then = now
}
}
可以看到当js一直在运行,占用浏览器的主进程的时候,一直到所有的js运行完之后,才会把结果渲染到浏览器上面。
如果我们改下代码,让js长时间占用浏览器主进程的时候,页面就会一直刷新不出来。
比如把上面的代码里边的10,改成1000的时候,然后就会一直白屏,等到浏览器全部刷新完毕,才会显示出来。
综上来看,最好的方式就是在浏览器的每一帧内,刷新一次,尽量不要让js长时间占据浏览器主进程。
这里就是为什么react fiber为什么会出现的原因了,因为之前的reconciliation也就是现在所说的stack reconciliation 就像是在一个人在水底下游泳,如果不是你感觉受不了,绝对不会浮出水面换气一样,在react里就是如果不把所有的差异都找到,绝对不会结束reconciliation。
在 http://conf.reactjs.org/ 上,Lin Clark 通过漫画为我们介绍 Fiber
这个就是以前的reconciliation图:
当所有的事情都等待reconciliation结束的时候,可能有其他更高级别的功能需求进来,比如用户点击输入框,或者是点击按钮等操作,但是由于还在执行,就会就一直卡住,让用户认为页面在假死。
所以Lin Clark介绍的最好的办法,也是用的最多的办法,不管是在计算机系统还是哪里,那就是分片,我借了你的东西,我用一段时间,就得过来就还给你,等你用完了之后,我再过来借一次,好借好还,再借不难。
比如和下图一样
这基本就是react fiber的核心所在!
Fiber node
什么是fiber node呢,fiber node其实和原来的虚拟dom有点类似,也是用来表示dom元素的描述的,但是如果只是这样,那和原来的虚拟dom也没什么区别了,而且也无法实现上面的暂停,继续等功能了。
可以看下源码里边fiber node的定义
可以看到fiber node 也是一个普通的对象,不过它里边包含了很多以前的虚拟dom里边没有的属性,比如return, child, sibling。(fiber 也是通过增加了这几个属性,将原来的递归,改成了一个链表)
类似下图
fiber node 就是一个包含了react dom的许多信息的普通对象。
fiber tree 算法
具体流程和原来的差不多,其实也还是找出两次更新之间的差异,然后渲染到浏览器上面。
- 首次render函数执行完之后,react会保存一份react fiber树,然后会循环利用,不会重复建立,称为curret 树。
2,当有setstate或者其他的时候,就会根据现在的current树重新生成一份包含变化的树。
这里最重要的就是在对比两颗树的过程中是异步的,随时可以中断,恢复,但是当更新的时候是同步的,也就是说 diff 过程中,是异步,commit是同步的。
diff 具体过程
这里就是根据信息,来遍历workpress树然后找不不同,这里不一样的一点是因为加了很多的指针,类似加了很多直达电梯,节省了很多时间,可以直接到达。
任何一项工作都会有下面几步, 首先获取该在哪里做,然后开始做,再接着就是花时间干完这项工作,最后退出,继续寻找下一步该在哪里工作。
对应关系就是
获取该在哪里做: performUnitOfWork
开始做: beginWork
完成工作: completeUnitOfWork
寻找下一步哪里做: completeWork
所有的函数都在(packages/react-reconciler/src/ReactFiberScheduler.js)
可以看下别人做的效果图
代表是子节点,比如上面的b1就是a1的子节点
代表兄弟节点,比如b3和b2就是兄弟节点。可以看到上面的处理方式在兄弟节点和父子节点之间的处理方式是不同的,不过比较简单,可以去看下。
唯一要记住的一点就是这里的过程是异步的,随时可能会暂停,或者停止,或者需要恢复过来重新执行。
commit
这里就是同步的了,不过速度也会很快的,因为这里把哪些改变了的fiber node形成了一个链表,如果中间没有更新的话,会快速的跳到下面去。
类似于下图的链表