如何提升你的 React 应用的性能

How to greatly improve your React app performance)- Noam Elboim / from mediumcss

本文旨在总结常见的性能缺陷,以及如何来避免这些缺陷。html

(本文所涉及的示例源码 请到译者github下载)

性能问题在web应用开发中不是什么新鲜事。vue

咱们每一个人都有这样的时刻,当你把一个新的Component组件放到你的app中,你会忽然发现你尝试的每个用户交互动做都与指望的效果有很明显的滞后。有时,你能够重复使用多个一样的组件,这种尴尬的动效滞后会更加明显。像下面这样:node



在那一刻你也许内心已经给写这个组件的人起了好几个绰号了。可是最好的办法是:作些什么,是的,你能够的!react

咱们将重点解决如下几个常见的 React 性能问题:git

1.错误的 shouldComponentUpdate 实现 ,为何 PureComponent 没能拯救你。github

2.太快的改变 DOM。web

3.滥用事件(events)和 回调(callbacks)。chrome

对于以上的每一个问题,咱们先解释问题的根源,而后咱们提出一些简单易用的方法来避免它。api

管好你的 shouldComponentUpdate

组件的component 钩子函数 shouldComponentUpdate 的本意是用来阻止一些非必需的渲染(render), shouldComponentUpdate将即将更新的props和state做为参数,若是返回值是true, render函数就执行,不然不执行 render.

React.Component 默认的实现 shouldComponentUpdate 是返回true.

越多的render渲染意味着耗费越多的时间。因此咱们须要防止没必要要的更新来减小额外的时间。

为此,你会想到咱们应该在实现 shouldComponentUpdate 的时候更谨慎些。

问题

让咱们看一个简单的使用 shouldComponentUpdate 的例子:



Simple shallow implementation: 'this.props.children !== nextProps.children', but it's always returning true

code

等下,为何不起做用呢?

不起做用是由于 React 每次渲染的时候建立了一个新的 ReactElement!

这就意味着 在 shouldComponentUpdate函数中 Shallow Comparision 如:return this.props.children !== nextProps.children;几乎就至关于return true;

根据个人经验,大多数组件一般都以某种方式支持 ReactElement props(PropTypes.node or PropTyps.elemtn)好比像children这是很常见的状况。

那么, PureComonent又是怎样的呢?

React.PureComponent 是React.Component 的另一种方式。它不是总在其 shouldComponent 实现中返回true,而是 props 和 state 的浅层比较。

使用 PureComponent 会返回一样的结果,以下:



PureComponent component is still always returning true


这是 PureComponent 特性的bug吗?我不肯定。咱们须要知道的是,PureComponent 在大多数状况下不起做用,它并不能阻止一些没必要要的更新。

可能的解决方案

咱们第一点想到的是——进行深度比较! 这确实管用,可是它有两个重要缺陷:

1. 运行深度比较自己是一个过程比较长,比较重,比较耗时的动做。所以,在 shouldComponent 函数运行结束以后,render 函数才能运行。这样一来性能非但不能提高反而会变得更差。

2. 这只是基于当前的 React Elements 实现,在将来版本中可能会取消。

综上,在我看来,使用深度比较并非一个好的解决办法。

为了寻找到更好的解决方案,我研究了一些其余的虚拟 DOM 库,看看他们是怎么解决这个问题的。

我发现了 Vue 做者Evan You 一个关于在Vue.js中添加 类React shouldeComponent 的 feature request 发表的一个有意思的评论。他解释到,这个问题并不能经过 "diffing" 虚拟DOM解决,由于它有不少未知的问题。依赖 React Elements 来检测组件中的状态变化并非一个可行的解决方案。

在实际应用中,不该该在 shouldComponentUpdate 的实现中使用 React Elements 的比较做为返回结果。相反,应该使用某种状态的改变来告诉组件是否应该更新。

咱们应该基于prop的不一样来通知 state 的改变,而不是经过使用this.props.children !== nextProps.children。最好是一个数字或者字符串,这样比较会更快。

咱们甚至可使用一个新的 prop 专门用来通知组件是否应该更新。

更进一步,我和个人同事建立了一个高阶组件(HOC)。这个组件使用继承反转(Inheritance Inversion)来扩展通用的 shouldComponent 实现,也是 PureComponent 的替代方案。 并且确实有效。代码在这里:

github.com/NoamELB/sho…

必须说明的是,这只是一个通用的实现,因此并非适用全部的状况。具体能够参考这里

例子在这里,使用了一个自定义的 shouldComponentUpdate 实现。正如上面提到的,它确实不会再进行没必要要的渲染了。



几种比较:



具体示例代码能够参考这里

容许你的组件扩张

你是否在你的应用中屡次使用相同的组件,导致你的应用很是重动画也很卡顿,有时候即便使用一个也会致使应用性能的损耗?

问题

在建立复杂的组件时,你可能须要执行一些自定义 DOM 的操做。在建立的时候你可能会遇到两个问题:

1. 触发太多布局(Layout)而没有使用触发复合(Composite)或者重绘(Paint)

2. 太多不必的Layout.屡次读写DOM,致使 DOM没必要要的从新计算。

让咱们看下 原生 Collapse 组件,在0和内容高度之间改变它的高度。点击查看



当使用一个这样的组件时,能够正常展现。可是当你屡次使用的时候......



点击查看具体效果

若是你不是在移动设备上查看,可能感受不明显。须要将你的chrome performance选项调到 6x slowdown



可能的解决方案

让咱们分析下 Collapse 组件发生了什么——这是高度改变的时候的代码:



这里有两个问题须要注意:

1. 咱们改变的height属性,根据csstriggers.com这个列表,改变高度(height)触发了布局(Layout)的从新计算。若是咱们设法改变相似transform的东西,那只会触发Composite,而且会更平滑些,对吗?

事实正式如此,这样会表现更好,可是这样就会在Collapse组件下留下一个空白,由于咱们没有改变它的高度。

2. 上面代码的第三行,这是常见的改变高度出发Layout的滥用:咱们从DOM读取了高度this.contentEl.scrollHeight而后又经过this.containerEl.style.height对DOM设置了高度,而后屡次重复这样的操做。

若是咱们能够成组的一次性读取过来高度,而后再一次性设置高度,这样不是更好吗?

批量的读写 DOM 是一个很好的减小 Layout 的尝试。咱们可使用requestAnimationFrame对DOM 读写进行批量处理,像下面这样:



requestAnimationFrame能保证你的代码在浏览器下一帧触发,减小页面绘制成本,按需批量绘制。让你的动画更流畅。点击查看具体实现

这样用起来可能比较麻烦,那么可使用内置组件或者使用第三方库好比Fastdom, Fastdom也是基于requesAnimationFrame 的原理经过批量处理DOM 读取/写入 操做来消除频繁的Layout操做。

值得一提的是,因为浏览器和设备功能的限制,有时您可能没法得到足够好的性能。在这些状况下,最好的解决方案多是变动产品需求。

最后,你可能听过css的will-change属性。在特定的状况下它能够帮助你,可是使用很差也会有必定的风险。最好不要过分使用它。

管住你的 callbacks

当咱们调用任何 DOM 事件的时候,有一个去抖(debounce)或者节流(throttle)函数时颇有必要的。它可让咱们把这个函数的调用次数减小到咱们想要的最低限度,以此来提升性能。

一般像这样写:window.addEventListener(‘resize’, _.throttle(callback)),可是为何咱们不能把它也运用到 React Components callbacks 里呢?

问题

让咱们看下面这个组件:



有没有注意到,咱们每次输入改变都会调用this.props.onChange, 它会被调用屡次,虽然不少调用都是非必需的。若是父级正在根据onChange回调进行 DOM 更改或者任何其余比较繁重的操做,咱们的应用会变得很卡顿。

可能的解决办法

其实咱们能够这样改进:



Debounce the event

如今,只有在用户输入完成后才调用props.onChange, 这样就阻止了不少没必要要的事件操做。

另外类似的解决办法还有函数节流(throtle).点击查看throttledebounce区别

总结

这些工具应该能够帮助您处理一些咱们在React应用程序中遇到的性能问题。经过明智地使用shouldComponentUpdate,控制你对DOM作的改变,并经过debounce / throttle来延迟回调,你能够大大地提升你的应用程序的性能。

若是你想测试开发遇到的状况,请查看UiZoo。它是React组件的一个动态组件库,它能够解析你的组件并展现给你,让你能够开发,测试或与他人共享。

(本文所涉及的示例源码 请到译者github下载)
相关文章
相关标签/搜索