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节点更新、插入、替换时,它都会闪现一个颜色。然而,元素面板的闪现,或者说是从新渲染也将影响到性能!常常我会从元素窗口切换到控制台,来更准确地感知每秒的帧数。
在用进行React开发时,当一个组件被渲染时,常常要进行PropType 校验。组件所接收到的 prop
先被检测来帮助调试和开发。使用 Chrome 提供的 JS Profiler
,你能够发现React组件在这个校验的方法上花费了很长一段时间。
尽管开发环境的警告提示有助于调试,但它们是会有一些性能方面的代价的,这些代价则不会反映在生产环境。有时我会使用切换到生产构建环境来忽略这种迟缓的错觉。(只要把NODE_ENV
改成 production
,就能够启动生产环境构建模式了:https://facebook.github.io/react/downloads.html#npm.)
在深刻讲解常见问题的修复前,重点强调一下,你必须只花时间来修复你所能把控的那些问题。若是你毫无约束地乱优化是很容易走进死胡同的。啰嗦一下,应该专一于构建,而且只把时间花在修复主要的性能瓶颈上。
使用标准的调试工具来识别性能瓶颈仍然是可行的,可是常常很难来解释数据,由于实际应用的代码会比在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>
在上面这个例子中,若是 complexFormProps
和 items
来自同一个 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的更新和解。
刚刚咱们看了不少 (你应该看过原列表的!), 可是关键的两点就是你要习惯 profiling
和 shouldComponentUpdate
。 我但愿这些都可以帮到你!
任何建议、评论等,若是咱们错过了,欢迎经过 benchling.com 让咱们知悉。
请继续关注本系列文章的第二篇,咱们将讨论 React 的调试工做流,深刻存在性能问题的代码实例,进而示范如何修复。
更新: 第二篇已经出来啦! Check it out - A Deep Dive into React Perf Debugging.
咱们一如既往地欢迎喜欢咱们产品的朋友来加入这个团队. :)