React’s diff algorithm是了解React中的Diff算法必读的文章之一,以下内容是我在阅读过程中边看边翻译的,非科班渣翻请谅解。强烈建议阅读英文原文。

原文地址:
React’s diff algorithm (by Christopher Chedeau)

React是Facebook推出的一个用于构建UI的JavaScript库。React的设计从根本出发来提升性能。在本文中,我将会介绍diff算法和渲染如何在React中发挥作用的,以便使用者们能更好地优化其app。

Diff算法(Diff Algorithm)

在深入了解算法实现的细节之前,我们有必要先大致了解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。需要注意render的返回值并不是一个真正的DOM节点,而只是轻量级的JavaScript对象,我们称之为虚拟DOM。
React基于虚拟DOM这个表示方法,计算得出将前一页面渲染成后一页面时所需的最小差异动作。以下给出的例子首先挂载<MyComponent first={true} />,然后替换为<MyComponent first={false} />,之后卸载该组件。这个过程中的DOM指令如下:

无节点 -> 挂载first节点
- 创建节点:<div className="first"><span>A Span</span></div>

first节点 -> second节点
- 替换属性值:className="first"->className="second"
- 替换节点:<span>A Span</span>-><p>A Paragraph</p>

卸载已挂载的second节点
- 移除节点:<div className="second"><p>A Paragraph</p></div>

逐层比较(Level By Level)

计算任意两棵树之间的最小差异数通常是O(n^3)问题。可以预见,这种算法在实际应用中是不可取的。React使用的是一个更简单有效的启发式算法,在O(n)复杂度下找到一个较好的近似解。
React只是逐层比较两棵树,以此确定两棵二叉树之间的最小差异数。这大大降低了算法复杂度,而且对最终解的准确度并没有很大的损失。因为在Web应用中,不同层之间的组件移动是很少见的,通常只在同一层之间移动。如下图所示:

列表(List)

假设我们现在有一个组件,在一次迭代中会渲染出5个组件,接下来在5个组件的中间插入一个新的组件。在信息有限的前提下,进行两个组件列表之间的映射是非常困难的。
默认React会将两个列表中的第一个组件配对,然后配对两个列表第二个组件,以此类推。也可以由开发者为组件提供一个key属性来帮助React解决映射问题。在实际应用中,我们总是很容易在孩子节点中找出唯一的key,如下所示:

组件(Component)

一个React APP通常由许多用户定义的组件组成,这些组件最终组成一棵主要由div组成的树。由于React通常是只比对那些具有相同类的组件,React的diff算法利用了这个辅助信息。
举例如下,如果<Header>组件被<ExampleBlock>替代,React会移除<Header>然后直接创建一个<ExampleBlock>。我们不需要浪费时间去比对两个不可能有任何相似之处的组件。如下所示:

事件代理(Event Delegation)

为DOM节点设置事件监听器是非常缓慢而且消耗内存的。所以React采用了另一种流行的技术,称为“事件代理”。React甚至更进一步,重新实现了一套兼容W3C标准的事件系统,这意味着IE8中的关于事件处理的bug也随之而去,因为在不同浏览器中的事件名称是一致的。
让我解释一下它是如何实现的:首先,文档根节点绑定一个事件监听器;当触发事件时,浏览器会提供触发事件的DOM节点;为了通过DOM层级结构传播事件,React不会迭代虚拟DOM层次结构。
取而代之的是,React利用了每个React组件都有一个唯一的编码层次结构的id这一事实。我们可以通过简单的字符串操作来获取所有父节点的id。比起直接将虚拟DOM节点与事件监听器绑定,将事件监听器存储在hash map中性能更好。下面是一个事件在虚拟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会在应用启动时在内存中分配一个对象池,每当需要一个事件对象时,就会从对象池中选取一个可用的事件对象进行复用。这样大大降低了垃圾回收的复杂度。

渲染(Rendering)

批处理(Batching)

每当用户在组件上触发setState方法时,React都将其标记为dirty。在事件循环结束时,React再对所有的脏组件进行响应,并重新渲染它们。
这种批处理意味着在一次事件循环中,DOM只会被更新一次。这是构建高性能应用的关键,而在原生JS中这是难以实现的。这在React应用程序中默认设置,与生俱来。如下所示:

子树渲染(Sub-tree Rendering)

当调用setState时,组件会为其子元素重构虚拟DOM。如果在根元素上调用setState,那么整个React应用程序都需要重新渲染。所有的组件无论是否发生改变,都会调用其render方法。这看似可怕而低效,实际上,这仅仅操作了保存在内存中的虚拟DOM,而没有触及真实的DOM。
首先,我们讨论的是展示用户界面。因为屏幕代销是有限的,所以每次只能显示数百到数千个元素。JavaScript为整个界面提供了足够快的可管理的业务逻辑。
另一个重要问题是,当编写React代码时,通常不会每次发生变化都在根节点上调用setState方法。你只在那些受到事件触发的组件上调用setState方法,而很少直接在最顶端节点上调用setState方法。这意味着setState方法的调用常常局限在与用户交互的组件上,如下所示:

选择性的子树渲染(Selective Sub-tree Rendering)

最后,你也可以通过在组件上实现以下方法来防止其子树的渲染:

boolean shouldComponentUpdate(object nextProps, object nextState)

基于组件前后的状态(props/state),你可以告诉React这个组件没有变化,所以也就没有必要重新渲染它。恰当的使用这个功能可以给应用带来巨大的性能提升。
为了能够使用该功能,你必须能够比较JS对象,但是这会带来许多问题,比如应当进行哪种程度的比较(深/浅),如果是深层次的比较,我们应当使用不可变的数据结构或者做深拷贝。
同时还要时刻警惕,这个函数会一直被不停地调用。所以无论组件是否需要重新渲染,你都要确保使用该函数所需要的时间要少于未使用此项功能而直接渲染组件所需的时间。

总结(Conclusion)

这个使React变快的技术并不是新技术。我们很早就知道直接操作DOM是昂贵的,应当进行批量读写操作,使用事件委托更快……
但是人们仍然在讨论这些,因为在实践中,很难再原生JavaScript代码中应用。真正使得React变得出色的是这些优化都是默认发生的。你很难把自己陷入困境,让自己的app变慢。
React的性能成本模型也很易于理解:每次setState都会重新渲染整个子树。如果你想要提升性能,应当尽可能少的调用setState,并且使用shouldComponentUpdate来防止重新渲染一个大的子树。

React中的Diff算法——Christopher Chedeau(原文翻译)相关推荐

  1. JS每日一题:Vue中的diff算法?

    20190125 Vue中的diff算法? 概念: diff算法是一种优化手段,将前后两个模块进行差异对比,修补(更新)差异的过程叫做patch(打补丁) 为什么vue,react这些框架中都会有di ...

  2. React虚拟DOM Diff算法解析

    React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法.这让我们可以无需担心性能问题而"毫无顾忌"的随时"刷新"整个页面,由虚拟DOM来确保只对界面 ...

  3. Vue中的Diff算法 patch函数-简单Diff算法-双端Diff算法-快速Diff算法-当数据发生改变,视图如何更新?

    文章目录 Vue中的Diff算法 概述 前置知识 patch方法 简单Diff算法 总结 双端Diff算法 --vue2 快速Diff算法 --vue3 vue2和vue3 Diff算法的区别 当数据 ...

  4. [Vue][面试]你怎么理解vue中的diff算法?

    你怎么理解vue中的diff算法? #####源码分析1:必要性,lifecycle.js–mountComponent() vue中一个组件一个watcher实例,而组件中可能存在很多个data中的 ...

  5. vue中的diff算法

    一.是什么diff算法 先来一句概念: diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom. 换句人话 diff的过程就 ...

  6. 【面试】以面试者的角度回答Vue中的diff算法(附图示diff运算过程)

    文章目录 面试者角度回答 图示diff运算过程 掘金同人账号:

  7. 关于react diff 算法(译文)

    React是由facebook开发,用于构建用户界面的js类库,以提升性能为设计理念.在本文中,我将为大家介绍在React中的diff算法,以及它的渲染机制,以便于你能够更好的优化你的程序. Diff ...

  8. React中diff算法的理解

    React中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DO ...

  9. diff算法_详解 React 16 的 Diff 策略

    这是我 Deep In React 系列的第二篇文章,如果还没有读过的强烈建议你先读前一篇:详谈 React Fiber 架构(1). 前言 我相信在看这篇文章的读者一般都已经了解过 React 16 ...

最新文章

  1. Android 布局中 如何使控件居中
  2. JSON中的安全问题
  3. tomcat参数java_opts调整
  4. mapbox 导航_狂甩不掉,骑行最稳手机支架!一体式安装太方便,秒变单车导航仪...
  5. Springboot 2.0选择HikariCP作为默认数据库连接池的五大理由
  6. Docker安装MySQL教程
  7. Redis五大数据类型
  8. Ajax学习笔记-基础概述-1
  9. java 编译单个文件_单独Java文件的通用快速编译方法
  10. hcia是什么等级的证书_华为hcia是什么等级的证书
  11. 算法学习:最小公倍数求法()
  12. 融云红包上线 要让每一款App都能“抢红包”
  13. Android显示系统详解
  14. 汉语与asc码互转最快的办法
  15. 【论文汇总】人群计数中Transformer的应用,持续更新
  16. windows下安装字体到linux服务器
  17. 【思维与逻辑】有1000瓶药水,但其中有一瓶毒药水,需要多少只小白鼠?
  18. SwitchyOmega插件安装
  19. Java 开发最容易写的 10 个bug
  20. python安装arcpy_为arcpy安装pip

热门文章

  1. 关闭Win10休眠和快速启动
  2. 鱼骨图分析法实际案例_技术前沿 | 基于鱼骨图分析标准实施偏差成因的应用研究...
  3. “共享”日本旅游车票 销售电商已消失
  4. echarts 自定义legend,线性渐变
  5. Windows 7 小工具
  6. 基于html的网页设计-音乐主题
  7. [bat]bat命令——findstr
  8. 至强服务器哪个系列好,至强cpu那么好,为什么日常装机的时候一般都选酷睿系列的呢?...
  9. 通过Feign实现Spring Cloud微服务调用
  10. alex机器人 ser_机器人Alex