React的diff算法(译文)
前言
此篇文章主要是因为在看Virtual DOM(虚拟DOM)的时候看到的主要讲的是实现Virtual
Dom 的diff算法,原文地址:https://calendar.perfplanet.com/2013/diff/
译文
React是一个Facebok开发的用于构建用户界面的JavaScript库。它的设计从根本上考虑到性能。在这篇文章中,作者将演示diff算法和渲染在React如何工作,以便您可以优化您自己的应用程序。
Diff 算法
在我们了解实现细节之前,了解React的工作原理非常重要。
varMyComponent=React.createClass({
render:function(){
if(this.props.first){
return<div className ="first">< span > A Span</span></div >;
}else{
return<div className ="second">< p > A Paragraph</p></div >;
}
}
});
在任何时间点,你描述你想要你想要的UI。重要的是要理解render的结果不是一个实际的DOM节点。这些只是轻量级的JavaScript对象。我们称之为虚拟DOM。
React将使用这个表示(译者注:虚拟DOM)来尝试找到从前一个渲染到下一个渲染的最少步骤数。例如,如果我们加载<MyComponent first={true} />
,替换它<MyComponent first={false} />
,然后卸载它,这里是DOM指令结果:
没有第一
- 创建节点:
<div className="first"><span>A Span</span></div>
第一到第二
- 替换属性:
className="first"
到
className="second"
- 替换节点:
<span>A Span</span>
到
<p>A Paragraph</p>
最好的
- 删除节点:
<div className="second"><p>A Paragraph</p></div>
逐级
找到两个任意树之间的diff是一个O(n ^ 3)问题。你可以想象,这是不适合我们的实际使用情况。React使用简单而强大的启发式方法在O(n)中找到非常好的近似。
React只尝试逐级协调树。这大大降低了复杂性,并不是一个大损失,因为在Web应用程序中将组件移动到树中的不同级别是非常罕见的。他们通常只在同级的节点间移动。
列表
假设我们有一个组件,在一次迭代渲染5个组件,然后下一次操作是插入一个新的组件在列表的中间。
默认情况下,React将上一个列表的第一个组件与下一个列表的第一个组件相关联,以此类推。您可以提供一个key
属性,以帮助React找出映射。在实践中,找出每一个孩子节点的唯一key值是很容易的。
组件
React应用程序通常由许多用户定义的组件组成,最终变成主要由div
s 组成的树。这个附加信息被diff算法考虑,因为React将仅匹配具有相同类的组件。
例如,如果一个 <Header>
被替换<ExampleBlock>
,React将删除<Header>并创建一个<ExampleBlock>。我们不需要花费宝贵的时间来尝试匹配不可能具有任何相似性的两个组件。
事件委托
将事件侦听器附加到DOM节点是非常缓慢和内存消耗。相反,React实现了一种名为“事件委托”的流行技术。React更进一步,重新实现一个符合W3C标准的事件系统。这意味着Internet Explorer 8事件处理错误是过去的事情,所有的事件名称在不同的浏览器中是一致的。
让我解释一下它是如何实现的。单个事件侦听器附加到文档的根目录。当事件触发时,浏览器会向我们提供目标DOM节点。为了通过DOM层次结构传播事件,React不在虚拟DOM层次结构上进行迭代。
相反,我们利用每一个React组件都有一个层次编码的独一无二的id值得事实。我们可以使用简单的字符串操作来获取所有父级的id。通过将事件侦听器存储在一个hash map中,我们发现它比将它们附加到虚拟DOM表现更好。下面的实例展示了 事件在虚拟DOM分发的过程
dispatchEvent('click','a.b.c', event);
clickCaptureListeners['a'](event);
clickCaptureListeners['a.b'](event);
clickCaptureListeners['a.b.c'](event);
clickBubbleListeners['a.b.c'](event);
clickBubbleListeners['a.b'](event);
clickBubbleListeners['a'](event);
浏览器为每个事件和每个侦听器创建一个新的事件对象。这有一个nice的特性 ,您可以保留对事件对象的引用或甚至修改它。然而,这意味着进行大量的内存分配。React在启动时分配这些对象的分发池。每当需要一个事件对象时,它都会从该分发池中重用。这显着减少了垃圾收集。
渲染(render)
批处理
每当你调用setState
一个组件时,React会将其标记为脏。在事件循环结束时,React会查看所有脏组件并重新呈现它们。
此批处理意味着在事件循环期间,正好有一次DOM正在更新。这个特性是构建高性能应用程序的关键,但是使用常用的JavaScript极难获得。在React应用程序中,默认情况下会得到它。
子树渲染
当setState
调用时,组件为其子代重建虚拟DOM。如果你调用setState
根元素,那么整个React应用程序将被重新渲染。所有的组件,即使他们没有改变,将render
调用他们的方法。这听起来可怕和低效,但在实践中,这工作正常,因为我们不触摸实际的DOM。
首先,我们正在谈论显示用户界面。由于屏幕空间有限,您通常一次显示数百到数千个元素的顺序。JavaScript已经得到足够快的业务逻辑为整个接口是可管理的。
另一个重要的点是,当编写React代码时,通常不会在每次更改时在根节点上调用setState。您在接收到更改事件或上面的几个组件的组件上调用它。你很少去一路到顶部。这意味着更改会本地化到用户交互的位置。
选择性子树渲染
最后,你有可能防止一些子树重新渲染。如果在组件上实现以下方法:
boolean shouldComponentUpdate (object nextProps ,object nextState )
基于组件的上一个和下一个属性/状态,你可以告诉React这个组件没有改变,没有必要重新渲染它。当正确实施时,这可以提供巨大的性能改进。
为了能够使用它,你必须能够比较JavaScript对象。有很多关于这个的issues,比如应该浅层比较还是深层比较;如果它是深层的,我们应该使用不可变的数据结构或做深拷贝。
并且你想记住这个函数将一直被调用,所以你想确保计算所需的时间比启发式的渲染组件所需的时间要少,不是严格需要渲染。
结论
使React快速的技术不是新的。我们已经知道很长时间,触摸DOM是昂贵的,你应该批量写和读操作,事件委派更快...
人们仍然谈论他们,因为在实践中,他们很难在常规JavaScript代码中实现。使React脱颖而出的是所有这些优化默认发生。这使得很难射击自己在脚,使你的应用程序缓慢。
React的性能成本模型也很容易理解:每个setState重新呈现整个子树。如果你想挤出性能,调用setState尽可能低,并使用shouldComponentUpdate来防止重新渲染一个大的子树。
React的diff算法(译文)的更多相关文章
- 深入理解React:diff 算法
目录 序言 React 的核心思想 传统 diff 算法 React diff 两个假设 三个策略 diff 具体优化 tree diff component diff element diff 小结 ...
- React中diff算法的理解
React中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DO ...
- react的diff算法与antd中switch组件不更新问题
问题描述: 现在有个需求,现有一个列表table,里面的数据有启用的也有关闭的,switch组件会根据数据状态展示,同时进行排序,启用数据在前面,未启用的在后面.如图 然后现在需要操作,假如我将第四条 ...
- React的Diff算法
使用React或者RN开发APP如果不知道Diff算法的话简直是说不过去啊.毕竟"知其然,知其所以然"这句老话从远古喊到现代了. 以下内容基本是官网文章的一个总结.压缩.这次要谦虚 ...
- 个人对于React的Diff算法的一点疑问(待更新)
本人对于Diff算法也并未做深入研究,只是大概的看过一些博文了解了些原理,但依然有了如下疑问 : 对于vdom所表示的对象中,若在该oldObj和newObj之间,发现一个元素节点所表示的子对象不见了 ...
- React——diff算法
react的diff算法基于两个假设: 1.不同类型的元素会产生不同的树 2.通过设置key,开发者能够提示那些子组件是稳定的 diff算法 当比较两个树时,react首先会比较两个根节点,接下来具体 ...
- react中虚拟dom的diff算法
.state 数据 .jsx模板 .生成虚拟dom(虚拟DOM就是一个js对象,用它来描述真实DOM) ['div', {id:'abc'}, ['span', {}, 'hello world']] ...
- react性能调谐与diff算法
一个页面其实就相当于是一颗dom树,里面有很多它的子节点,然后你每次去操作一个事件,它都会生成一个虚拟dom,它会跟上一个虚拟dom进行比对,这里运用的算法叫做diff算法,当它找到需要改变的组件的时 ...
- diff算法
diff算法的作用计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面. 传统diff算法 通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ...
随机推荐
- gym/102059 E
gym/102059 待通过:A.D.G.J.M 已补过:E E:电路题,判断一个图是不是简单电路.不需要特殊的技巧,利用set存图,把度数为2的点都删掉,融入到一条边上即可. #include &l ...
- codeforces 284 D. Cow Program(记忆化搜索)
题目链接:http://codeforces.com/contest/284/problem/D 题意:给出n个数,奇数次操作x,y都加上a[x],偶数次操作y加上a[x],x减去a[x],走出了范围 ...
- HTML5基本介绍
HTML5简介 HTML是互联网上应用最广泛的标记语言.HTML文件就是普通文本+HTML标记,而不同的HTML标记能表示不同的效果.(简单的说HTML是超文本标记语言) HTML5草案的前身名为 W ...
- 原来JS是这样的 - 对象属性
引子 在上一篇(原来JS是这样的 (2))刚发布的时候就阅读了那篇文章的人可能会注意到那篇曾用过"JavaScript 中万物皆对象"的说法,而在随后我发现错误后立即更新改掉了这个 ...
- Android Activity启动耗时统计方案
作者:林基宗 Activity的启动速度是很多开发者关心的问题,当页面跳转耗时过长时,App就会给人一种非常笨重的感觉.在遇到某个页面启动过慢的时候,开发的第一直觉一般是onCreate执行速度太慢了 ...
- 设置普通用户输入sudo,免密进入root账户
满足给开发用户开权限,赋予sudo权限.又不让其输入密码的方式: 方式一: 开始系统内部的wheel用户组, 在/etc/suoers 中编辑配置文件如下: %wheel ALL=(ALL) NOPA ...
- 初步认识JWT
前言: 现在越来越多的项目或多或少会用到JWT,为什么会出现使用JWT这样的场景的呢? 假设现在有一个APP,后台是分布式系统.APP的首页模块部署在上海机房的服务器上,子页面模块部署在深圳机房的服务 ...
- 自荐RedisViewer有情怀的跨平台Redis可视化客户端工具
# **自荐一个有情怀的跨平台Redis可视化客户端工具——RedisViewer**[转载自 最美分享Coder 2019-09-17 06:31:00](https://www.toutiao.c ...
- java基本数据类型与引用类型
基本数据类型: byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0 short:短整型,在内存中占16位,即2个字节,取值范围-32768~3 ...
- [大数据学习研究] 4. Zookeeper-分布式服务的协同管理神器
本来这一节想写Hadoop的分布式高可用环境的搭建,写到一半,发现还是有必要先介绍一下ZooKeeper这个东西. ZooKeeper理念介绍 ZooKeeper是为分布式应用来提供协同服务的,而且Z ...