前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React 性能工程

React 性能工程

作者头像
疯狂的技术宅
发布2019-03-27 14:26:29
5920
发布2019-03-27 14:26:29
举报
文章被收录于专栏:京程一灯京程一灯

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

然而,如果你是在构建一个DNA设计工具、一个胶体图片分析器、一个富文本编辑器,或者一个全能的电子数据表,你就会触碰到性能的瓶颈了。这时候,就有必要来解决这个问题了。在构建Benchling这个项目的过程中,我们遇到了很多问题。所以,本文的目的是给那些网络开发者和关注Benchling的粉丝分享我们学到的一些方法。

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

基准

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

使用React, 不需要做额外的工作,就可以取得性能上的优势:

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

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

代码语言:javascript
复制
`someNode.style.left = parseInt(someNode.style.left) + 10 + "px";`

我们可以声明式地调用 `` 来触发组件,不需要从DOM元素读取数据,就可以简单地更新状态了。

代码语言:javascript
复制
`this.setState({left: this.state.left + 10}).`

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

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

调试带来的性能问题

请注意 -- 调试本身也会带来一些问题,导致混淆调试部分,以为这部分不会留在生产中。

元素窗口

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

PropTypes

在用进行React开发时,当一个组件被渲染时,经常要进行PropType 校验。组件所接收到的 prop 先被检测来帮助调试和开发。使用 Chrome 提供的 JSProfiler ,你可以发现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

我们可以这样写:

代码语言:javascript
复制
<IntermediateBinder
  deleteItem={this.deleteItem}
  boundArg={item.id}>
  {(boundProps) => <TodoItem deleteItem={boundProps.deleteItem} />}
</IntermediateBinder>

(我们探索的另一个可能的做法是,使用一个自定义的绑定函数,这个函数本身储存了元数据, 它和一个更高端的检测函数结合使用,就可以检测到功能的结合实际上还没有改变。这似乎不能满足我们的需求。)

构造数组、对象字面量

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

代码语言:javascript
复制
> ['important', 'starred'] === ['important', 'starred']
false

如果你不希望这个对象被改变,你就可以把它放到一个模块常量或者组件静态变量中:

代码语言:javascript
复制
`const TAGS = ['important', 'starred'];`

子组件

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

代码语言:javascript
复制
<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 变化时才会更新状态。

代码语言:javascript
复制
<div>
  <CustomList items={this.props.items} />
  <ComplexForm props={this.props.complexFormProps} />
</div>

缓存昂贵的计算

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

代码语言:javascript
复制
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 的表单输入是很简单的。但如果你像我们一样,在多个组件之间串联,那就会遇到问题了。状态链接实施如下:

代码语言:javascript
复制
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。 我希望这些都能够帮到你!


往期精选文章

使用虚拟dom和JavaScript构建完全响应式的UI框架

扩展 Vue 组件

使用Three.js制作酷炫无比的无穷隧道特效

一个治愈JavaScript疲劳的学习计划

全栈工程师技能大全

WEB前端性能优化常见方法

一小时内搭建一个全栈Web应用框架

干货:CSS 专业技巧

四步实现React页面过渡动画效果

让你分分钟理解 JavaScript 闭包



小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京程一灯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基准
  • 调试带来的性能问题
    • 元素窗口
      • PropTypes
      • 通过React.addons.Perf来识别性能问题
        • 构造数组、对象字面量
          • 子组件
            • 缓存昂贵的计算
              • 状态链接
              • 编译程序的优化
              • 总结
              相关产品与服务
              检测工具
              域名服务检测工具(Detection Tools)提供了全面的智能化域名诊断,包括Whois、DNS生效等特性检测,同时提供SSL证书相关特性检测,保障您的域名和网站健康。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档