【译】 React 性能工程(上)

04 February 2016 on Reacthtml

本文是 React 性能工程系列文章的第一篇(共两篇) 第二篇 深刻探讨React性能调试 译文 如今已经推出!react

这篇文章适用于复杂的React应用。若是只是构建一些简单的、小型的应用,你还不用考虑性能问题。没必要过早地优化,去构建吧!git

然而,若是你是在构建一个DNA设计工具、一个胶体图片分析器、一个富文本编辑器,或者一个全能的电子数据表,你就会触碰到性能的瓶颈了。这时候,就有必要来解决这个问题了。在构建Benchling这个项目的过程当中,咱们遇到了不少问题。因此,本文的目的是给那些网络开发者和关注Benchling的粉丝分享咱们学到的一些方法。(固然,若是你喜欢这类问题,咱们正在招聘!)github

在这篇文章中,我将会讲述使用React性能工具的一些基础知识、一些会致使React渲染瓶颈的常见问题,以及一些须要谨记的调试方法。web

基准

浏览器性能能够用三句话来概述:理想中你指望浏览器每秒渲染60帧,每帧16.7毫秒。当你的app运行缓慢的时候,常常须要很长时间才能响应用户事件、处理数据或者从新渲染新的数据。大多数状况下,你并无时刻在处理复杂的数据,只是浪费时间在重绘而已。chrome

使用React, 不须要作额外的工做,就能够取得性能上的优点:npm

由于React会处理全部的DOM操做,很大程度上免去了DOM解析和布局所带来的问题。在后台,React会在JavaScript中维持虚拟DOM, 这样便于快速地把文档更新到指望状态。segmentfault

咱们要避免直接操做DOM,由于React组件的状态是储存在JS中的。一个传统的性能问题就是在不恰当的时刻操做DOM,这样会致使像被迫同步布局这样的问题(例如:为了获取某节点的样式 someNode.style.left, 使得浏览器被迫渲染画面)。为了避免用如下这种作法:数组

`someNode.style.left = parseInt(someNode.style.left) + 10 + "px";`

咱们能够声明式地调用 `` 来触发组件,不须要从DOM元素读取数据,就能够简单地更新状态了。浏览器

`this.setState({left: this.state.left + 10}).`

说明一点,这些优化不用React也是能够实现的,我只是简单地指出React趋向于提早解决这些问题。

对于简单的应用,React 所带来的这些性能优化就足够了。我认为这些是使框架变得可行的最小工做量了。然而,当你开发的页面愈来愈多、越复杂时,维护和对比虚拟DOM就会变成一项昂贵的操做了。幸运的是,React提供了一些工具,能够检测哪里有性能问题,便于你及时地避开这些问题。

调试带来的性能问题

请注意 -- 调试自己也会带来一些问题,致使混淆调试部分,觉得这部分不会留在生产中。

元素窗口

元素窗口是观察DOM元素是否被从新渲染的一个简单好用的途径,当一个属性改变或者一个DOM节点更新、插入、替换时,它都会闪现一个颜色。然而,元素面板的闪现,或者说是从新渲染也将影响到性能!常常我会从元素窗口切换到控制台,来更准确地感知每秒的帧数。

PropTypes

在用进行React开发时,当一个组件被渲染时,常常要进行PropType 校验。组件所接收到的 prop 先被检测来帮助调试和开发。使用 Chrome 提供的 JS Profiler ,你能够发现React组件在这个校验的方法上花费了很长一段时间。

尽管开发环境的警告提示有助于调试,但它们是会有一些性能方面的代价的,这些代价则不会反映在生产环境。有时我会使用切换到生产构建环境来忽略这种迟缓的错觉。(只要把NODE_ENV 改成 production,就能够启动生产环境构建模式了:https://facebook.github.io/react/downloads.html#npm.)

经过React.addons.Perf来识别性能问题

在深刻讲解常见问题的修复前,重点强调一下,你必须只花时间来修复你所能把控的那些问题。若是你毫无约束地乱优化是很容易走进死胡同的。啰嗦一下,应该专一于构建,而且只把时间花在修复主要的性能瓶颈上。

使用标准的调试工具来识别性能瓶颈仍然是可行的,可是常常很难来解释数据,由于实际应用的代码会比在React-land中的代码花费更多的时间(例如:你写的一个复杂的渲染方式运行得很快,可是其带来的虚拟DOM计算倒是至关昂贵的)。这使咱们很难在React-land中识别哪些应用代码致使了明显的瓶颈问题。

幸运的是,React自带一些性能检测工具,能够在React的非生产构建环境中使用(文档)。经过react/addons,你能够找到对应的React.addons.Perf

咱们能够这样写:

<IntermediateBinder
  deleteItem={this.deleteItem}
  boundArg={item.id}>
  {(boundProps) => <TodoItem deleteItem={boundProps.deleteItem} />}
</IntermediateBinder>

(咱们探索的另外一个可能的作法是,使用一个自定义的绑定函数,这个函数自己储存了元数据, 它和一个更高端的检测函数结合使用,就能够检测到功能的结合实际上尚未改变。这彷佛不能知足咱们的需求。)

构造数组、对象字面量

这很简单,只是常常被忽略了。数组字面量会破坏 PureRenderMixin:

> ['important', 'starred'] === ['important', 'starred']
false

若是你不但愿这个对象被改变,你就能够把它放到一个模块常量或者组件静态变量中:

`const TAGS = ['important', 'starred'];`

子组件

在一个组件和它的子组件之间定义内容界限有利于性能优化----接口封装性良好的组件能够天然地促进性能更新。重构中间的组件能够帮助提升性能,你也可使用 PureRenderMixin 来保存更新。

<div>
  <ComplexForm props={this.props.complexFormProps} />
  <ul>
    <li prop={this.props.items[0]}>item A</li>
    ...1000 items...
  </ul>
</div>

在上面这个例子中,若是 complexFormPropsitems 来自同一个 store 的话,那么在 complexFormProps 里面输入,就会引起 store 的更新,而每一个 store 的更新又会致使上面这整个实例的从新渲染。虚拟 DOM 的差别是很棒的,但仍然须要每次都检测。 然而,重构它的子组件,采用 this.props.items,这样就只有当 this.props.items 变化时才会更新状态。

<div>
  <CustomList items={this.props.items} />
  <ComplexForm props={this.props.complexFormProps} />
</div>

缓存昂贵的计算

这个跟 状态来源单一性 原则有些相悖,可是若是 prop 中的计算是昂贵的,你就能够把它缓存在组件中。咱们没必要在渲染的方法中,直接地调用 doExpensiveComputation(this.prop.someProp) ,能够把这个函数进行封装,在prop 状态没改变的时候,把它缓存起来。

getCachedExpensiveComputation() {
  if (this._cachedSomeProp !== this.prop.someProp) {
    this._cachedSomeProp = this.prop.someProp;
    this._cachedComputation = doExpensiveComputation(this.prop.someProp);
  }
  return this._cachedComputation;
}

后续的优化人员使用JS分析器,将能够很好地发现这个问题。

状态连接

React 的双向数据绑定对于简单的控制反转(IoC)很是有用,它容许子组件向父组件传递新的状态。若是对React表单组件只是使用 valueLink 的话是没那么糟糕的,由于 React 的表单输入是很简单的。但若是你像咱们同样,在多个组件之间串联,那就会遇到问题了。状态连接实施以下:

linkState(key) {
  return new ReactLink(
    this.state[key],
    ReactStateSetters.createStateKeySetter(this, key)
  );
}

尽管状态没有改变,每调用一次 linkState 都会返回一个新的对象!这意味着 shallowCompare 永远不会起做用。不幸的是,咱们的变通方案就是干脆不使用 linkState。 若是不是要把一个 linkState 变成一个 getter prop 和一个 setter prop 的话,咱们要避免建立一个新的对象。例如:nameLink={this.linkState(‘name')} 能够被替换成 name={this.state.name} setName={this.setName}。(咱们已经考虑写一个能够对自身进行缓存的 linkState了)

编译程序的优化

新版的 Bebel 和 React 支持内联React元素而且自动提高常量。不幸的是,咱们尚未用过这方面的技术,但它们将有助于减小 React.createElement 的调用, 以及加速DOM的更新和解。

总结

刚刚咱们看了不少 (你应该看过原列表的!), 可是关键的两点就是你要习惯 profilingshouldComponentUpdate。 我但愿这些都可以帮到你!

任何建议、评论等,若是咱们错过了,欢迎经过 benchling.com 让咱们知悉。

请继续关注本系列文章的第二篇,咱们将讨论 React 的调试工做流,深刻存在性能问题的代码实例,进而示范如何修复。

Hacker News的讨论

更新: 第二篇已经出来啦! Check it out - A Deep Dive into React Perf Debugging.

咱们一如既往地欢迎喜欢咱们产品的朋友来加入这个团队. :)

相关文章
相关标签/搜索