【译】React 组件渲染性能探索

原文地址:Component Rendering Performance in React

React 因为性能好而被广为周知,但这并不意味着我们能够把这个当作是理所当然。让你的React应用更快的关键Tips之一就是优化你的 render 函数.

我曾经创建过一个简单的测试,来比较下面不同条件下的 render() 函数的速度:

  • 无状态(函数)组件(stateless components) vs 有状态(基于class的)组件components

  • 纯组件(Pure component)渲染 vs 无状态组件

  • React 0.14 vs React 15 在development模式和production模式下的渲染性能

关键结论(TL;DR)

对于那些不耐烦,只是想阅读结果的人来说,这是以下测试中得出的最重要的结论:

  • 无状态组件Stateless (functional) components 并不比有状态组件 stateful (class) components

  • React 15 的渲染性能大概比 0.14 快 25%

  • 纯组件 Pure components 更快,因为使用shouldComponentUpdate

  • development 模式下的渲染性能production 模式下慢 2–8x倍

  • React 15 development 模式下的渲染性能比 0.14 慢 2x 倍

很惊讶?

与每个基准一样,弄懂结果的关键是先理解方法。让我们花一些时间来解释我在这里做了什么,为什么要这么做。

怎样进行性能测试

目标是创建一个非常简单的测试来迭代 render() 函数。我创建了包含三种组件之一的父组件:

  • 一个无状态组件

  • 一个有状态组件

  • 一个纯粹的有状态组件,即 shouldComponentUpdate 返回 false

// Stateful component
class Stateful extends Component {
  render () {
    return <div>Hello Cmp1: stateful</div>;
  }
}
// Pure stateful with disabled updates
class Pure extends Component {
  shouldComponentUpdate() {
    return false;
  }
  render () {
    return <div>Hello Cmp2: stateful,pure,no updates</div>;
  }
}
// Stateless component
function Stateless() {
  return <div>Hello Cmp3: stateless</div>;
}

测试的组件很简单,并没有改变DOM。

顶层的组件将三种类型的组件每种循环渲染100,000次。同时使用浏览器自带Performance.now功能统计从初始渲染到最后渲染的时间来衡量渲染时间。遗憾的是,我们不能使用React官方提供的的Perf函数统计,因为它们不能用在生产环境中。

虽然始终传递props以确保更新,但目标组件保持渲染的数据相同。这样我们可以保证与纯组件的一致性。 DOM不会从这些组件中更新,从而保证其他API(例如布局呈现引擎)的干扰达到最小。

所有的JavaScript代码都运行在纯ES6中,没有转换步骤(没有转换为ES5)

测试运行的浏览器为Chrome 51 (包含常用的插件,如一系列的 dev tools,1Password,Pocket等),一个纯粹的Chrome Canary 54 (没有插件,没有历史记录,完全全新的) 和 Firefox 46. OS X 10.11,主机环境为一台2.6 GHz Intel Core i7 处理器的MBP.所有呈现的数据都是取所有浏览器运行结果的平均值。

TJ Holowaychuk’s 的观点中,值得一提的是不安装任何插件的浏览器能够产生一个明显更好的结果。这就是我们使用一个纯粹的Chrome Canary配置的原因。无论怎样,我们普通的用户通常都会在浏览器安装几个插件,我们并不知道这个会导致多大的性能损失。

执行这些类型的基准测试时的精确度并不容易实现。有时测试会运行速度更慢或更快,导致结果产生偏差。这就是为什么我多次运行测试,并统计了所有的结果。您单次测试的结果可能会有所不同。

所有的 源码 都在Github上,欢迎check out. 每个框架对应一个文件夹,你能够在里面运行常见的 npm i && npm start命令。这些应用运行在不同的端口,因此他们能够被同时执行。 readme 文件 提供了更详尽的说明。

我们已经有了这些前提,现在,让我们来讨论一下这些发现。

神话:无状态组件更快?

根据React 0.14和React 15,无状态(functional)组件与常规的基于类的状态组件一样快。

您会注意到,React 0.14中的测试显示无状态组件与状态组件性能有5%的差异,但我认为这是统计误差。

但是,当整个生命周期被剥离,且没有引用,没有状态管理,无状态组件为何不会更快?

根据 Dan Abramov的说法,无状态组件内部封装在一个类中,当前并没有进行任何的优化,

@ggrgur There is no “optimized” support for them yet because stateless component is wrapped in a class internally. It's same code path.

React团队已经承诺将对于无状态组件的优化,我确信在React的未来版本之一中将实现。

React中最简单的性能技巧

抛开复杂构建优化问题,React应用程序性能调优的关键在于知道什么时候渲染,什么时候不渲染。

这里有一个例子:假设您正在创建一个下拉菜单,并希望通过设置状态来展开它expanded:true。这种情况下,你必须要渲染整个块,包括下拉列表中的所有列表项才能更改css值吗?千万不要这么干!

这种情况下,shouldComponentUpdate 是你最好的朋友, 赶紧用上它。如果你能够使用shouldComponentUpdate进行优化,就不要使用无状态组件。您可以使用Shallow Compare函数来简化流程,但如果这成为React组件核心功能的一部分,我也不会感到惊讶。

更新:从React 15.3开始,React.PureComponent是一个新的基类,可以替代使用Shallow Compare插件或Pure Render mixin的场景。

最初的基准测试是在没有添加任何逻辑到render函数的情况下,进行了渲染性能的比较。而一旦添加了计算,Pure Components的优势就会更加明显。我们可以将其视为一种缓存的方法
我这么直接原因之一是看到开发人员不关心改善渲染,因为虚拟DOM将在这方面做了一些处理。但是显然,在涉及到虚拟DOM的diff算法之前,应用的速度还有大量的提升空间。

我不想说你应该在所有的地方使用shouldComponentUpdate。添加大量的逻辑或在组件很少输出相同的HTML代码的地方使用它,就会增加不必要的复杂度。你应该知道这种生命周期方法的力量,并且合理的使用它。

阻止不必要的渲染只是其中一方面。上述结果告诉我们如何提高渲染性能

优雅的Rendering

下图显示了render()函数对应用程序性能的影响。渲染是复杂操作的开始,最终导致优化的DOM更新。

render越简单,更新越快:

  • 执行的JavaScript代码越少越好  — 特别是在移动网站/应用程序

  • 返回更少的更改将有助于加快 virtual DOM 的计算

  • 父组件 (container) 的render很可能会出发所有子孙组件的render(), 这意味着更多的计算。

以下是优化render阶段的几个提示

  • 如果可能,跳过render

  • 在render函数外面,使用变量缓存开销大的计算

  • … 或者将逻辑拆分进多个组件,然后有选择地管理render方法

  • 尽可能的返回简单一点

  • 保持render方法的纯粹 (使用函数式编程的思想)

  • 保证state的扁平化,使用浅比较

正如您的应用程序可能在render阶段包含业务逻辑计算一样,React还添加了helpers来增强您的开发体验。让我们看看它们是如何影响性能的。

使用Production模式进行构建

React代码认为development模式。令人惊讶的是,development环境下的渲染显着较慢。

在构建应用程序时将其指定为production模式的方法是定义环境变量NODE_ENV = production。当然,你只会在生产环境下用到这个,因为development 模式将提供更好的调试体验。

以下是您在Webpack配置中自动执行此变量的方法

module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      ‘process.env’: {NODE_ENV: ‘”production”’}
    })
  ],}

这样做不仅能够优化你的render性能,还能够提供更小的体积。

别忘了使用UglifyJS plugin剔除不必要的代码,下面是一个使用例子:

plugins: [
  new webpack.optimize.UglifyJsPlugin({
    sourceMap: false,warnings: false,}),],

我们看到React 15在development模式下与之前的版本相比要慢得多。他们在生产中的比较结果又如何呢?

React 15 更快

React 15 重要更新 是框架与DOM交互的核心变化。 innerHTML 被替换成 document.createElement,现代浏览器中更快捷的选择。
不再支持Internet Explorer 8可能意味着一些内部流程更加优化。

React 15在渲染过程中真的更快,但只有在构建模式为production时才能生效。development模式实际上相当慢,主要是因为包含了太多的帮助调试和优化代码函数
值得注意的是,这些发现是基于React 15与React-DOM 15的。React Native的development模式下结果可能会有显着差异。也许你可以运行类似的测试,并与社区分享结果。

写在最后

性能开始于在React应用程序中优化render block。该基准测试比较了创建和优化组件的三种方法。你得对他们的使用场景和优缺点了然于心。

可能还有许多其他的基准测试方法,因此绝对不要把你在这里学到的一切当作理所当然。如果您有其他想法欢迎贡献。

Grgur是Modus Create的一名软件架构师,专门从事JavaScript性能,React JS和Sencha框架。如果这篇文章有帮助,也许他16年的从财富100强到政府和初创公司的经验可以帮助你的项目。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom