翻译原文链接:https://blog.vixlet.com/react-at-light-speed-78cd172a6411
个人翻译小站链接:http://www.zcfy.cc/article/react-at-light-speed-vixlet-2920.html
在过去的几年里, 我们Vixlet (http://www.vixlet.com) 的web团队,着手了一项激动人心的项目,将我们的整个web应用迁往React+Redux的建设。对于我们整个团队来说
这是一个不断增长的机会,而且在这个项目过程中,我们一直充满着挑战。
自此我们的web-app中,包含有几百甚至上千的媒体,文本,视频以及链接等内容,我们花了很长的时间去摸索关于react构建的方式,这里我们将分享一些踩过的经验。
声明: 讲解的这些经验和方法,已经能够很好的满足我们特殊应用的需求,然而,如果是整体开发的话,将你的应用和团队考虑其中至关重要,React的速度是相当快的,所以你可能没有必要很精确的了解到我们的使用场景,但是,我们希望你能发现一些发送的有益的信息。
基本原理
向更大的世界迈出一步.
The render() 函数
作为一个通用原则,尽量在render函数里面少做事,如果实在很有必要运行一些复杂的操作和计算,也许可以考虑将他们移到 memoized 函数里面, 这样得到的结果能够被缓存.了解 Lodash.memoize 关于 memoization函数的模型.
相反地,去避免更改巨大的,轻易计算的值在组件中的状态是很重要的。例如,如果props参数同时包含 firstName
和 lastName
,那么就没必要在state状态里面包含 fullName
,因为它很容易从props属性里面获得。 如果一个值能以一种很有效的方式通过props获得,通过使用通用的字符串连接,或者基本的的算法操作,那么我们就没必要更改状态来获取该值了。
Prop 和 Reconciliation
这是很重要的,记住React触发时,是会重新渲染的,其prop值(或者state状态)跟之前的值是不相等的。它包含了在嵌套组件,且其props和state包含对象或者数组之内的任何变化,记住这一点,这是很重要的情况,就是可能不经意的render循环,去创造prop或者state的值,就可能引起巨大的性能冲击。
例如: 函数绑定问题
例如: 对象或者数组的字面值
例如_: 注意fallback返回的值
尽可能的保证Props(和state)最小
一般来讲,props是在被需要的情况下,才会传递给一个组件,通过一个大且复杂的对象,或者很多个单独的props传递给组件,仅仅只是为了给其子组件传递值,但这会引起不必要的组件渲染(同时增加了开发的复杂性).
在我们Vixlet这边,我们使用Redux作为state的容器,因此在我们的这种情况,大部分是从react-redux中使用connect()函数,根据组件中的每一个等级去直接获取需要的数据。connect()函数是非常高性能并且占用的开销非常小。
组件方法
自从给每一个组件实列赋予了组件的方法,如果可以的,你要么使用纯粹的来自helper/util模板中的函数,要么使用静态类方法,这里有一个特别值得注意的地方,就是当有很大数字的组件在app里面进行渲染的时候。
高级
读完我的观点,jank变得邪恶了!
shouldComponentUpdate()
React生命周期中包含一个的方法 [shouldComponentUpdate()](https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate)
.这个方法可以告诉React,组件是否应该被渲染或者不依赖当前或者下一个props/state的值。
然而使用这个方法有个问题,那就是开发者必须仔细考虑每一种重新渲染发生的条件。这就使逻辑变得很复杂,通常来说,这太痛苦了,如果你真的需要它,那你就使用 shouldComponentUpdate()
方法,但是对于很多的情况,一般都有更好的选择。
React.PureComponent
React v15版本开始,其包含了 PureComponent类,可以用来建立组件。React.PureComponent
基本实现了它自己的shouldComponentUpdate()
方法,并且能够自动判断出当前以及下一个props/state组件之间的比较。如果想看更多的信息,请至Stack Overflow:
http://stackoverflow.com/questions/36084515/how-does-shallow-compare-work-in-react
在几乎所有的情况中,React.PureComponent
无疑是比React.Component
更好的选择.当创建一个新的组件时候,试着将其建立为一个纯粹的组件先,并且将组件功能引入,使用React.Component
.
更多信息: [React.PureComponent](https://facebook.github.io/react/docs/react-api.html#react.purecomponent)
官方React文档
组件运行分析 (in Chrome)
在最新Chrome版本,新增了一个建立时间表功能的工具,它可以将React组件渲染详细的信息,以及所花的时间显示出来。使用该功能只需要添加?react_perf
放在你想测试的url的后面即可。React渲染的时间列表数据,将会在用户时间部分之下显示。
更多信息: Profiling Components with Chrome Timeline官方React文档
有用的Utility: why-did-you-update
这是一个很好的NPM包,当没有必要重新渲染组件的时候,它能进行补丁,在console里面打印出来通知。
注: 这个模块能够使用过滤器进行初始化,去匹配特殊的你想要的组件,否则你的所有console出来的东西,都会被识别为垃圾信息,或者你的浏览器可能会崩溃,至 why-did-you-update docs获取更多细节信息。
常见的性能陷阱
setTimeout() and setInterval()
使用 setTimeout()
or setInterval()
在一个 React组件之中,需要极其的小心。这里几乎是更好的选择去使用 ‘resize’ 和 ‘scroll’ 事件 (注: 见下一章节需要注意的事项).
如果你需要使用 setTimeout()
or setInterval()
, 你必须 遵循下面几个禁令
极其短时间内持续发生不要使用
短时间内持续发生是没有必要使用的,特别是小于100ms的,如果更短的时间内,真的需要,也许你可以使用 [window.requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
来代替.
参考这些取消/清除函数
setTimeout()
and setInterval()
都返回一个唯一的标识符,对于延迟函数,如果有必要可以对其进行取消操作。自从这些函数在全局范围内运行,他们不需要关心你的组件是否已经不在,它会直接导致报错或者停止运行。+
注: 这是真的对于window.requestAnimationFrame()
来说
最简单的解决这个问题的方案其实是使用 react-timeoutNPM包,它可以提供高阶组件,确保它能够自动使用上面提到的那个东西,它添加了包裹在组件的props中的setTimeout/setInterval等函数(特别感谢Vixlet开发者Carl Pillot 可戳这里进入)
如果你不希望引入一个依赖,并且想要用你自己的方案去解决这个问题,下面这些可能对你的会有帮助:
如果你在使用requestAnimationFrame()来控制动画循环,你可以使用很小的通知来进行类似的处理。
去抖和防流事件
一定程度上来说,正常的事件是极其迅速的,像”scroll”,”resize”.这是很明智的,去抖事件,特别是如果这些事件被解决的话,远远比那些极其基本的功能执行的好。
Lodash有[_.debounce](https://lodash.com/docs/#debounce)
方法. 这里依然是一个独立的[debounce](https://www.npmjs.com/package/debounce)
NPM包
“但是我真的需要scroll/resize/无论什么事件立即响应”
我曾经发现了一种模式可以以一种高性能的方式来解决这类事件响应问题,那就是使用 requestAnimationFrame()
来监听事件开始和结束的时间,然后, [debounce()](https://lodash.com/docs#debounce)
函数通过使用 trailing
选项来设置为 true
(这就意味着函数仅仅在节流函数结束之后才结束)来阻止监听这个值,看下面的例子
密集的CPU任务导致线程阻塞
毫无疑问,很多任务一起将会加强CPU的损耗,因此引起主要的渲染阻塞,一些例子包括非常复杂的数学计算,通过一个很大的数组进行迭代,通过使用File
api来进行渲染/覆写. 加密或者解密图像数据从`` object中.
如果在所有的情节当中,也许使用web搬运工,将其一些功能性的东西搬移到另外一个线程中会更好,因此,我们的主要渲染进程中能够顺利的进行。
阅读这些
MDN 文章: Using Web Workers
MDN 文档: Worker API
结尾
我们希望你已经发现上面这些有用的建议和信息。最后说一句,如果没有我们的Vixlet团队,上面这些技巧和提示方法是不可能被搜索出来,并且很好的运行的,他们真的是我工作以来,有幸接触到的一群非常优秀的团队伙伴。
在React之外进行升华,持续学习和练习,希望和你一起进步!
谢谢 Matt Lubner