miaoyu

【React】什么时候使用shouldComponentUpdate方法?

miaoyu · 2017-05-10翻译 · 356阅读 原文链接

你听说React是一个很快的前端框架,决定试一试。然后你找了个样例开始了React之旅,这时你注意到shouldComponentUpdatePureRenderMixin,通过google找到一些技巧让React变得更快。但是React真的快吗?

答案是React确实很快...不过是在某些时候。其他时候,你应该使用shouldComponentUpdate,但是其他时候是什么时候呢?如果只是一个是或否的问题,你可能会问确定是否使用shouldComponentUpdate,会那么容易吗?你可能不信,但它就是那么容易。

shouldComponentUpdate将带来可测量和可感知的提升?

如果不能,那就别用。

那你的意思是我可能不会用它?

你可能应该避免用它。据React团队的说法,shouldComponentUpdate是一个保证性能的紧急出口,意思就是你不到万不得已就别用它。当然这也不是绝对。

如果它没用,React也不会有这样一个紧急的性能出口。当你使用它时,问自己是否需要?总之,你可能偶尔还是要用到的。

我们来看看背后的原理,从这里开始吧:

添加shouldComponentUpdate方法将拖慢你组件的性能。

是的,你没看错,一个保证性能的出口的确能拖慢组件的性能。如何做到的呢?回答这个问题,我们需要先问什么是React?

React非常聪明的实现了shouldComponentUpdate

但是,React做的不仅仅是算出是否该更新组件。还需要确定如何去更新它。

React是如何确定什么时候去渲染组件的呢?

这是一个好问题!所以你了解你为每个组件写的渲染方法吗?它看起像返回了一段类似Html的jsx代码(或者你用Raw React会返回ReactElement对象),但是你会惊讶的听到渲染方法返回的是一个普通的js对象。看起来是这样的:

{ type:  'ul', props:  { className:  'what-do-you-want-to-do-tonight'  }, children:  [  { type:  'li', children:  'The same thing we do every night, pinky.'  },  ]  }

当然,它可能会被装饰成JSX或者 冒充成React.createElement,但是正确形式的render()的输出是一个你标记的对象。如果那个对象在预渲染中发现没有改变,那么它将不会被渲染到DOM上。

或者用其他方式渲染,如果我们伪装render方法接受props来取代直接触发render方法,那么代码看起来是这样的:

shouldComponentUpdate(nextProps)  {  return  !deepEquals(render(this.props), render(nextProps))  }

我们知道deepEquals对于简单对象处理是很快的,但是对于嵌套很深的对象处理很慢,所以这个近似值给我们一个很好的法则:

如果render方法返回的对象很小,但是props对象很大,那么shouldComponentUpdate将对性能带来很大的拖累。

但是你会说有种情况是render方法返回的值足够大以至于你可以通过 shouldComponentUpdate 获得某些东西?

使用shouldComponentUpdate的收获通常是无关紧要的

所以当你渲染一个很大的table,你从props中提取数据,调用render方法来计算,然后通过Immutable.js 来判断数据是否相等。

在上面的场景中,shouldComponentUpdate比起React默认的处理方式带来了巨大的性能提升。事实上,以我的经验来看,至少10倍。但是在这个场景中,如何收获微不足道?答案是肯定的。

小于1毫秒的10倍仍然小于1毫秒。

Donald Knuth 曾经写过:“过早的优化是一切的万恶之源”。

维护shouldComponentUpdate很困难

所以如果我不得不猜测,React团队把shouldComponentUpdate叫做紧急出口,而不是叫性能按钮的原因就是维护困难,但是老实说,我认为紧急出口这个叫法不合适。shouldComponentUpdate应该更像是无保护的性行为。

原因是这样的,shouldComponentUpdate有时候是必要的,它能让你的应用性能更好。但是它也能引起巨大的bugs,而且不容易被发现。

维护shouldComponentUpdate的代码很难。比如加一个新的proprender方法,但是忘了更新shouldComponentUpdate?这就是一个bug。最糟糕的是这些bugs在测试中不会出现,有可能是你第一次给客户展示的时候出现。

但是如果shouldComponentUpdate真的容易引起很多bug,它显然需要一些使用定律,所以问题就变成了...

什么时候使用shouldComponentUpdate

一旦shouldComponentUpdate给你带来了可感知的性能提升,就使用它。

希望上一节已经说服你, shouldcomponentupdate有一些缺陷。带着这个想法,当你确定要使用它时,你需要一把尺子,是一个非常花哨的技术。

测量性能提升

这里有个 方法 你可以测量 shouldComponentUpdate带来的提升, 下面我将集中讨论JavaScript分析器.

什么是分析器?它就是我之前提到的尺子。一个好的分析器可以让你测量你所有的程序。它可以得到你每个方法运行的时间。

通过比较render方法在添加shouldComponentUpdate之前和之后花费的时间,我们能得到性能提升的数值。当然,我们也将需要考虑shouldComponentUpdate所花费的时间 - 它不是免费使用的。最后得出性能的提升比起维护成本是否是值得的。

很简单对吧?不要相信我的话哈,我们用一个例子来演示一下。通过订阅我的消息可以免费获取一个高性能的react组件。

Get the PDF cheatsheet!

PDF组件

练习

我把演示例子放到了 fiddle上. Enterprise组件为了满足需求,将使用shouldComponentUpdate; 然后对原始数据进行处理, 使得它频繁的触发渲染方法, 其次是它的 props 包含不可变数据.

你只需要进入 the project按步骤来完成练习.

开始之前,你需要知道如何打开浏览器的分析器。你可以在这里了解到:Chrome 或者 Firefox

第一步: 测试不加shouldComponentUpdate的版本

当触发点击的时候,我们来测量渲染的时间:

  1. 打开js分析器

  2. 开始记录

  3. 点击 “Toggle synergy!” 让页面循环5秒

  4. 点击你开始记录的按钮来停止记录

  5. 在列表的最上面部分找到render方法,记录所花的时间,

如果render所花的时间足够的少,没有出现在时间花费列表前面,恭喜你!你不用使用shouldComponentUpdate。或者说你在解决其他性能问题前你不必考虑它。

第二步: 测试加了shouldComponentUpdate的版本

这一步,我们加了shouldComponentUpdateApp组件,来测试渲染时间。shouldComponentUpdate方法,代码已经为你准备好了:

shouldComponentUpdate(nextProps, nextState)  {  return  !Immutable.is(this.state.synergy, nextState.synergy)  },

当你加了shouldComponentUpdate,重复第一步的步骤,以下是结果:

组件中写了shouldComponentUpdaterender:

shouldComponentUpdate:

上面的方法不一定准确,实际的结果可能相差很大。如果你不相信我,你可以尝试上面的步骤来试试。如果结果不准确,那么怎么抉择呢?

讲真的,这种方法得出的结果相差很大,这种方法只可以给你提供一个粗略的测算。(请不要把这个方法给统计学家看)

抉择

从我的经验来看,如果添加shouldComponentUpdate方法,可以节约一半的渲染时间,那么它是对你有利的。

如果没加shouldComponentUpdate之前的渲染只花了100ms,那就没有必要用shouldComponentUpdate了。

正确的写 shouldComponentUpdate方法

现在我摇着你的手告诉你用“Immutable.js”就可以了,此时我猜你的内心os应该这样的:

Immutable.js不是完美的解决方案,也不是唯一的,ES6的Object.assign也可以达到差不多的效果

如果不可变的状态不能解决你的问题,那什么才可以呢?

我很高兴你这么问!正真的解决方案是良好的状态结构。事实上,好的状态结构将解决你 shouldComponentUpdate部署问题;它解决了你所有问题。

怎样构造好的状态呢?我将在这个帖子上回答 - Luddite’s guide to Redux State(快完成了),不想错过的话?订阅我消息!

最后,我想看到你的问题、建议和意见,如果你有什么想说的,发邮件给我james@jamesknelson.com,我期待你的来信。

扩展阅读

相关文章