React是由facebook开发,用于构建用户界面的js类库,以提高性能为设计理念。在本文中,我将为你们介绍在React中的diff算法,以及它的渲染机制,以便于你可以更好的优化你的程序。react
在深刻了解实现细节以前,了解React如何工做是很重要的。算法
var MyComponent = 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描述你想要的样子。你必须明白它渲染的结果不是一个真实的DOM。渲染结果只是轻量级的JavaScript对象。咱们称之为虚拟DOM。
React将使用此表示法来尝试找到从上一个渲染到下一个渲染的最小步数,好比,若是咱们要用 <MyComponent first={false} />
去替代<MyComponent first={true} />
,插入真实DOM, 而后移除它,下面是DOM指令的结果:浏览器
从无到有
建立DOM节点:<div className="first"><span>A Span</span></div>
bash
从一到二
用className="second"
去替换属性 className="first"
用 <p>A Paragraph</p>
去替换节点<span>A Span</span
>数据结构
从二到无
移除节点:<div className="second"><p>A Paragraph</p></div>
函数
寻找两个任意树之间最小的修改数须要执行n3次,你能够想象,这对于咱们来讲是难以接受的。而React用了一个简单并且目前来讲都很是强大的计算方法去找到他们的变化,而执行的次数仅仅为n次。性能
React经过逐级的去比较两颗节点树的差别,这大大下降了复杂性,并且精准度上损失也不大,由于在Web应用程序中将组件树不一样级的移动比较是很是罕见的。 组件一般只能在子组件横向移动。优化
假设咱们有一个组件,它在一个迭代中渲染了5个组件,而下一次渲染的时候在组件列表的中间插入一个新的组件。 只是经过这个信息真的很难知道如何在两个组件列表之间进行映射。ui
默认状况下,React将先前列表的第一个组件与下一个列表的第一个组件相关联,等等。您能够提供一个Key属性,以帮助React去找到他们的映射关系。 在实际中,这一般很容易把刚刚插入的组件从他们当中找出来。
this
一个React应用程序一般由许多用户定义的组件组成,最终会变成一个主要由div组成的树。 经过diff算法考虑了这些附加信息,由于React只匹配具备相同类的组件。
例如,若是<Header>
被<ExampleBlock>
替换,则React将删除<Header>
并建立一个<ExampleBlock>
。 咱们不须要花费宝贵的时间尝试匹配不太可能有类似之处的两个组件。
将事件监听器附加到DOM节点是痛苦的缓慢并且消耗内存的事情。可是,React实现了一种称为“事件委托”的流行技术。 React更进一步,并从新实现了符合W3C标准的事件系统。这意味着Internet Explorer 8事件处理兼容问题将成为过去的事情,全部的事件名称在浏览器之间是一致的。
让我解释一下它的实现。单个事件监听器附加到文档的根节点上。当事件被触发时,浏览器告诉咱们触发事件的DOM节点。为了经过DOM节点传播事件,React并无在虚拟DOM层次结构上进行迭代。
相反,每一个React组件都有惟一id用来表示他的层级。 咱们可使用简单的字符串操做来获取全部父节点的id。 经过将事件侦听器存储在哈希映射中,咱们发现它比将它们附加到虚拟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);复制代码
浏览器为每一个事件和监听器建立一个新的事件对象。 这个对象有一个很是好的的属性,你能够保留事件对象的引用甚至修改它。 可是,这意味着要作大量的内存分配。React在一开始会为这些对象分配内存池。 每当须要事件对象时,它将从内存池中被从新使用,这大大减小了内存垃圾回收。
每当您在组件上调用setState时,React会将其标记为脏。 在事件循环结束时,React会查看全部脏组件并从新渲染它们。若是是批处理也就意味着在事件循环期间,正在更新渲染DOM的时间正好是一次。 这个属性是构建一个应用程序的性能好坏的关键,可是写经常使用的JavaScript很是难以得到。 在React应用程序中,默认状况下能够得到。
调用setState时,组件将重建其子项的虚拟DOM。若是您在根元素上调用setState,则会从新渲染整个React应用程序。全部的组件,即便它们没有改变,也会使用它们的渲染方法。这可能听起来很可怕,效率低下,但在实践中,这样能够正常工做,由于咱们没有碰到实际的DOM。
首先,咱们讨论一下显示用户界面。因为屏幕空间有限,您一般一次按顺序的显示数百到数千个元素。 JavaScript已经能够足够快的速度处理业务逻辑和整个接口管理。
另外一个重要的一点,当编写React代码时,每次发生变化一般不会都在根节点上调用setState。你能够在接收到事件变化的组件或父组件上调用它。咱们不多走到顶端,一般用户的交互都是发生在对应的组件上变化。
最后,您能够防止一些子树从新渲染。 若是在组件上使用如下方法:boolean shouldComponentUpdate(object nextProps, object nextState)
基于组件的上一个和下一个props/state,您能够告诉React该组件没有更改,而且不须要从新渲染。 正确使用该方法能够大大提升性能。为了可以使用它,您须要比较JavaScript对象。这里面仍是有不少问题的,好比js对象的比较是深点仍是浅点,若是深度比较,我是用不可变数据结构仍是作深拷贝。并且你还要记住,这个函数将一直被调用,因此你想要确保计算时间要比渲染组件所用的时间要少,即便渲染是多余的。
到底什么状况下使用shouldComponentUpdate?
按照React团队的说法,shouldComponentUpdate是保证性能的紧急出口
http://jamesknelson.com/should-i-use-shouldcomponentupdate
http://www.infoq.com/cn/news/2016/07/react-shouldComponentUpdate复制代码
让React变得如此快的技术已经不是什么新鲜的事情,并且咱们好久以前就知道,操做DOM是昂贵的,因此你应该对DOM进行批量的读写、使用事件委托,这些都能使你的程序变得更快。
你们还一直在讨论React, 由于事实上,使用常规的Javascript代码很难去实现这些优化的方法,而React默认就能实现,这也是为何React为何能脱颖而出的缘由。
React的性能成本模型也很容易理解:每次setState都会从新呈现整个子树。 若是要提升性能,请尽量少调用setState,并使用shouldComponentUpdate来防止从新全部子树。