【插播一条】虚拟DOM Diff算法
我们都知道React通过虚拟DOM机制可以有效解决复杂的DOM操作带来的性能瓶颈,并且每当数据变化时,React都会重新构建整个DOM树,然后将当前DOM树和上次DOM树进行对比,然后将需要变化的部分进行实际更新。那么,需要变化的部分细化到什么地步,虚拟DOM是又如何运作的?不懂这些对我们实际使用react并不会有什么困扰,但是理解这些对我们实现自定义组件时如何进一步优化性能具有指导意义。
Diff算法复杂度为O(n),这基于下面两个假设:
- 两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构;
- 对于同一层次的一组子节点,他们可以通过唯一ID进行区分。
逐层进行节点比较
React按树结构从上到下依次遍历,逐层比较。它只会对相同颜色框内的DOM节点比较,也就是同一个父节点下的所有子节点。
Diff算法逐层进行比较当该位置前后节点类型不同时
当树中的同一位置前后输出了不同类型的节点:直接删除前面的节点(不管该节点是否有子节点),然后创建并插入新的节点。同样的,同一位置前后输出不同组件时也是直接销毁第一个组件,然后创建新组件并插入。
对于上图,Diff算法的操作是
A.destroy();
A=new A();
A.append(new B());
A.append(new C());
D.append(new D());
假设这些节点都是组件,那么从生命周期来理解的话就是:
A will unmount.
A is created.
B is created.
C is created.
B did mount.
C did mount.
A did mount.
D is updated.
Root is updated.
由上面简单的例子可见,Diff算法在同一位置不论是对组件还是组件内的节点只要类型不同直接删除加重建,这样从上到下遍历一次搞定。那么对于我们平日的使用:能用display:none之类的class解决的事,就不要去替换修改DOM结构,(这点我们在jquery实践中也基本都做到了)。另外对于节点列表的操作(一般涉及到增、删、查、改、排序),要给每个节点去设置唯一的标识(key),�这可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高性能。
Flux应用程序架构
一个小的组件写起来好像并不太不烦,譬如先前提到的一个实现评论功能的组件。但是内容提交后如何在上面内容区域显示呢,也就是组件之间如何通信?
Flux的解决方案是让数据流变成单向,引入Store、Action、Action Creators和Dispatcher等概念来管理信息流,完全面向View,并且始终是整体刷新的思路。如下图所示:
单项数据流这显然是一个链式反应,Action触发Store的更新,Store的更新又出发view的重新渲染。得益于React的View每次更新都是整体刷新的思路,我们可以完全不必关心Store的变化细节,只需要监听Store的onChange事件,每次变化都触发View的re-render。
回到提交评论的使用场景(使用Flux):
1、两个组件:评论列表和评论框
2、所有组件绑定Store(存储了评论数据)
3、Action Creator向服务器发送请求
4、Store中监听action,更新自身数据,然后触发view的更新。
组件之间需要共享的状态放到Store中进行维护,Store中的状态改变时,就会发布onChange事件,订阅了这个事件的View就会执行重新渲染,通过这样一种发布——订阅模式就实现了从Store到View的数据绑定。
那么当View接收接收用户交互后(譬如对评论点赞),又如何将新状态存入Store?
View接收用户的输入之后,产生一个特定的action,然后通过Dispatcher分发处理去更新Store,重新渲染view,这样也就形成了一个闭环的单向流动。Dispatcher是全局唯一的,也就是所有action的集中处理中心,而每个action处理函数都能接收到所有的action,即所有的action能力都相同,只是当前在执行的任务可能不同。另外为了使View更加纯粹,将Action的创建逻辑也单拿出去放到了Action Creators中。
Flux的标准实现非常简单,因此还衍生出了很多第三方实现,而如今最为火热的应该属于Redux,它采用了函数式编程的思想来维护整个应用程序的状态。
网友评论