2019独角兽企业重金招聘Python工程师标准>>>

1. 基本原理

image.png

向更大的世界迈出第一步。

1.1 render()函数

一般来说,要尽可能少地在 render 函数中做操作。如果非要做一些复杂操作或者计算,也许你可以考虑使用一个 memoized 函数以便于缓存那些重复的结果。可以看看 Lodash.memoize,这是一个开箱即用的记忆函数。

反过来讲,避免在组件的 state 上存储一些容易计算的值也很重要。举个例子,如果 props 同时包含 firstName 和 lastName,没必要在 state 上存一个 fullName,因为它可以很容易通过提供的 props 来获取。如果一个值可以通过简单的字符串拼接或基本的算数运算从 props 派生出来,那么没理由将这些值包含在组件的 state 上。

1.2 Prop 和 Reconciliation

重要的是要记住,只要 props(或 state)的值不等于之前的值,React 就会触发重新渲染。如果 props 或者 state 包含一个对象或者数组,嵌套值中的改变会触发重新渲染。考虑到这一点,你需要注意在每次渲染的生命周期中,创建一个新的 props 或者 state 都可能无意中导致了性能下降。(注:对象或者数组只要引用不变,是不会触发rerender的)

例子: 函数绑定的问题

/*
给 prop 传入一个行内绑定的函数(包括 ES6 箭头函数)实质上是在每次父组件 render 时传入一个新的函数。
*/
render() {return (<div><a onClick={ () => this.doSomething() }>Bad</a><a onClick={ this.doSomething.bind( this ) }>Bad</a></div>);
}/*
应该在构造函数中处理函数绑定并且将已经绑定好的函数作为 prop 的值
*/constructor( props ) {this.doSomething = this.doSomething.bind( this );//orthis.doSomething = (...args) => this.doSomething(...args);
}
render() {return (<div><a onClick={ this.doSomething }>Good</a></div>);
}

例子: 对象或数组字面量

/*
对象或者数组字面量在功能上来看是调用了 Object.create() 和 new Array()。这意味如果给 prop 传递了对象字面量或者数组字面量。每次render 时 React 会将他们作为一个新的值。这在处理 Radium 或者行内样式时通常是有问题的。
*//* Bad */
// 每次渲染时都会为 style 新建一个对象字面量
render() {return <div style={ { backgroundColor: 'red' } }/>
}/* Good */
// 在组件外声明
const style = { backgroundColor: 'red' };render() {return <div style={ style }/>
}

例子 : 注意兜底值字面量

/*
有时我们会在 render 函数中创建一个兜底的值来避免 undefined 报错。在这些情况下,最好在组件外创建一个兜底的常量而不是创建一个新的字面量。
/*
/* Bad */
render() {let thingys = [];// 如果 this.props.thingys 没有被定义,一个新的数组字面量会被创建if( this.props.thingys ) {thingys = this.props.thingys;}return <ThingyHandler thingys={ thingys }/>
}/* Bad */
render() {// 这在功能上和前一个例子一样return <ThingyHandler thingys={ this.props.thingys || [] }/>
}/* Good */// 在组件外部声明
const NO_THINGYS = [];render() {return <ThingyHandler thingys={ this.props.thingys || NO_THINGYS }/>
}

1.3 尽可能的保持 Props(和 State)简单和精简

理想情况下,传递给组件的 props 应该是它直接需要的。为了将值传给子组件而将一个大的、复杂的对象或者很多独立的 props 传递给一个组件会导致很多不必要的组件渲染(并且会增加开发复杂性)。

我们使用 Redux 作为状态容器,所以在我们看来,最理想的是方案在组件层次结构的每一个层级中使用 react-redux 的 connect() 函数直接从 store 上获取数据。connect 函数的性能很好,并且使用它的开销也非常小。

1.4 组件方法

由于组件方法是为组件的每个实例创建的,如果可能的话,使用 helper/util 模块的纯函数或者静态类方法。尤其在渲染大量组件的应用中会有明显的区别。

2. 进阶

image.png

视图的变化是邪恶的

2.1 shouldComponentUpdate()

React 有一个生命周期函数 shouldComponentUpdate()。这个方法可以根据当前的和下一次的 props 和 state 来通知这个 React 组件是否应该被重新渲染。

然而使用这个方法有一个问题,开发者必须考虑到需要触发重新渲染的每一种情况。这会导致逻辑复杂,一般来说,会非常痛苦。如果非常需要,你可以使用一个自定义的shouldComponentUpdate()
方法,但是很多情况下有更好的选择。

2.2 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。

2.3 组件性能分析(在 Chrome 里)

在新版本的 Chrome 里,timeline 工具里有一个额外的内置功能可以显示哪些 React 组件正在渲染以及他们花费的时间。要启用此功能,将 ?react_perf
作为要测试的 URL 的查询字符串。React 渲染时间轴数据将位于 User Timing 部分。
更多相关信息,请查阅官方文档:Profiling Components with Chrome Timeline 。

2.4 有用的工具: why-did-you-update

这是一个很棒的 NPM 包,他们给 React 添加补丁,当一个组件触发了不必要的重新渲染时,它会在控制台输出一个 console 提示。

注意: 这个模块在初始化时可以通过一个过滤器匹配特定的想要优化的组件,否则你的命令行可能会被垃圾信息填满,并且可能你的浏览器会挂起或者崩溃,查阅 why-did-you-update 文档获取更多详细信息。

3. 常见性能陷阱

image.png

3.1 setTimeout() 和 setInterval()

在 React 组件中使用 setTimeout() 或者 setInterval() 要十分小心。几乎总是有更好的选择,例如 'resize' 和 'scroll' 事件(注意:有关注意事项请参阅下一节)。

如果你需要使用 setTimeout() 和 setInterval(),你必须遵守下面两条建议

不要设置过短的时间间隔。

当心那些小于 100 ms 的定时器,他们很可能是没意义的。如果确实需要一个更短的时间,可以使用 window.requestAnimationFrame()。

保留对这些函数的引用,并且在 unmount 时取消或者销毁他们。

setTimeout() 和 setInterval() 都返回一个延迟函数的引用,并且需要的时候可以取消它们。由于这些函数是在全局作用域执行的,他们不在乎你的组件是否存在,这会导致报错甚至程序卡死。

注意: 对 window.requestAnimationFrame() 来说也是如此

解决这个问题最简答的方法是使用 react-timeout 这个 NPM 包,它提供了一个可以自动处理上述内容的高阶组件。它将 setTimeout/setInterval 等功能添加到包装组建的 props 上。(特别感谢 Vixlet 的开发人员 Carl Pillot 提供这个方法)
如果你不想引入这个依赖,并且希望自行解决此问题,你可以使用以下的方法:

// 如何正确取消 timeouts/intervalscompnentDidMount() {this._timeoutId = setTimeout( this.doFutureStuff, 1000 );this._intervalId = setInterval( this.doStuffRepeatedly, 5000 );
}
componentWillUnmount() {/*高级提示:如果操作已经完成,或者值未被定义,这些函数也不会报错*/clearTimeout( this._timeoutId );clearInterval( this._intervalId );
}

如果你使用 requestAnimationFrame() 执行的一个动画循环,可以使用一个非常相似的解决方案,当前代码要有一点小的修改:

// 如何确保我们的动画循环在组件消除时结束componentDidMount() {this.startLoop();
}componentWillUnmount() {this.stopLoop();
}startLoop() {if( !this._frameId ) {this._frameId = window.requestAnimationFrame( this.loop );}
}loop() {// 在这里执行循环工作this.theoreticalComponentAnimationFunction()// 设置循环的下一次迭代this.frameId = window.requestAnimationFrame( this.loop )
}stopLoop() {window.cancelAnimationFrame( this._frameId );// 注意: 不用担心循环已经被取消// cancelAnimationFrame() 不会抛出异常
}

3.2 未去抖频繁触发的事件

某些常见的事件可能会非常频繁的触发,例如 scroll,resize
。去抖这些事件是明智的,特别是如果事件处理程序执行的不仅仅是基本功能。Lodash 有 _.debounce 方法。在 NPM 上还有一个独立的 debounce 包.
“但是我真的需要立即反馈 scroll/resize 或者别的事件”

我发现一种可以处理这些事件并且以高性能的方式进行响应的方法,那就是在第一次事件触发时启动 requestAnimationFrame() 循环。然后可以使用 debounce() 方法并且将 trailing 这个配置项设为 true
这意味着该功能只在频繁触发的事件流结束后触发)来取消对值的监听,看看下面这个例子。

class ScrollMonitor extends React.Component {constructor() {this.handleScrollStart = this.startWatching.bind( this );this.handleScrollEnd = debounce(this.stopWatching.bind( this ),100,{ leading: false, trailing: true } );}componentDidMount() {window.addEventListener( 'scroll', this.handleScrollStart );window.addEventListener( 'scroll', this.handleScrollEnd );}componentWillUnmount() {window.removeEventListener( 'scroll', this.handleScrollStart );window.removeEventListener( 'scroll', this.handleScrollEnd );//确保组件销毁后结束循环this.stopWatching();}// 如果循环未开始,启动它startWatching() {if( !this._watchFrame ) {this.watchLoop();}}// 取消下一次迭代stopWatching() {window.cancelAnimationFrame( this._watchFrame );}// 保持动画的执行直到结束watchLoop() {this.doThingYouWantToWatchForExampleScrollPositionOrWhatever()this._watchFrame = window.requestAnimationFrame( this.watchLoop )}}

3.3 密集CPU任务线程阻塞

某些任务一直是 CPU 密集型的,因此可能会导致主渲染线程的阻塞。举几个例子,比如非常复杂的数学计算,迭代非常大的数组,使用 File api 进行文件读写,利用 <canvas> 对图片进行编码解码。

在这些情况下,如果有可能最好使用 Web Worker 将这些功能移到另一个线程上,这样我们的主渲染线程可以保持顺滑。

欢迎关注极客教程微信公众号平台:geekjc

转载于:https://my.oschina.net/cllgeek/blog/1584704

React优化性能的经验教训相关推荐

  1. 2018 react 大会_React Conf 2018的经验教训

    2018 react 大会 by Yangshun Tay 阳顺泰 React Conf 2018的经验教训 (Lessons Learned at React Conf 2018) I was fo ...

  2. 前端不哭!最新优化性能经验分享来啦 | 技术头条

    作者 | Dimitris Kiriakakis 译者 | 风车云马 编辑 | Jane 出品 | Python大本营(id:pythonnews) [导语]Angular.React.VueJS 是 ...

  3. JavaScript 大揭秘:React、性能优化以及多线程

    @开发者,在后端仅提供原始数据集的情况下,如何让所有搜索逻辑都在前端完成?不仅如此,还要能够实现预测用户输入.忽略错别字输入.快速自动补齐等智能功能?本文的作者就深入 JavaScript 技术特性, ...

  4. 我在React Native中构建时获得的经验教训

    by Amanda Bullington 通过阿曼达·布林顿(Amanda Bullington) 我在React Native中构建时获得的经验教训 (Lessons I learned while ...

  5. React 的性能优化(一)当 PureComponent 遇上 ImmutableJS

    一.痛点 在我们的印象中,React 好像就意味着组件化.高性能,我们永远只需要关心数据整体,两次数据之间的 UI 如何变化,则完全交给 React Virtual Dom 的 Diff 算法 去做. ...

  6. React进阶—性能优化

    React性能优化思路 软件的性能优化思路就像生活中去看病,大致是这样的: 使用工具来分析性能瓶颈(找病根) 尝试使用优化技巧解决这些问题(服药) 使用工具测试性能是否确实有提升(疗效确认) Reac ...

  7. [react] react的性能优化在哪个生命周期?它优化的原理是什么?

    [react] react的性能优化在哪个生命周期?它优化的原理是什么? shouldComponentUpdate 减少不必要的重新渲染 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易 ...

  8. React 组件性能优化之 PureComponent 的使用

    文章出自个人博客:https://knightyun.github.io/2021/05/09/js-react-purecomponent,转载请申明 在 React 类组件中,如果状态(state ...

  9. VMware vSphere 性能优化设计经验+优化方法 | 周末送资料

    VMware vSphere 性能优化设计经验+优化方法 | 周末送资料 https://mp.weixin.qq.com/s?__biz=MjM5NTk0MTM1Mw==&mid=26506 ...

最新文章

  1. 这个图像生成领域的PyTorch库火了,涵盖18+ SOTA GAN实现
  2. 如何查看dede版本信息
  3. UVALive 3905 Meteor (扫描线)
  4. 后处理没有pui文件怎么打开_UG NX10.0四轴后处理下载(带PUI文件)
  5. GIS宣传片《地理空间信息革命》视频全集
  6. cad一键标注闭合区域lisp_CAD快捷键大全,你值得学会!
  7. 数据科学入门与实战:玩转pandas之一
  8. 编程通用知识 系统编程
  9. PMP考试真题模拟PMP考试模拟试题及答案详解
  10. UCOS操作系统——时间片轮转调度(五)
  11. 利用PMOS实现LED恒流驱动芯片的通断控制
  12. 网页游戏外挂辅助AMF模拟通讯必备
  13. 如何自学编写手游脚本_自学如何编写代码时应做的5件事
  14. 在线成语接龙答题有奖微信小程序源码V1.5.1
  15. 好好讲一讲,到底什么是Java高级架构师!
  16. 混合式学习,以学习者为中心的新型教学形式
  17. Python实现对比两个Excel数据内容并标出不同
  18. Hyperlynx学习心得
  19. Google 出现崩溃 错误代码:STATUS_BREAKPOINT
  20. 力扣(300,674)补9.11

热门文章

  1. java从入门到精通_Java入门到精通、学习路线、就业方向、薪资及前景分析(上篇)...
  2. Unity Pixel 人物设计(1)
  3. 牛客网Java刷题知识点之插入排序(直接插入排序和希尔排序)、选择排序(直接选择排序和堆排序)、冒泡排序、快速排序、归并排序和基数排序(博主推荐)...
  4. 【转】软件需求分析方法
  5. android 5.0 下载编译
  6. HDU 1850 Being a Good Boy in Spring Festival
  7. 转:Excel导出问题(导出时不去掉前面的0)
  8. Predator:比微软Kinect更强的视频追踪算法-来自捷克博士论文
  9. 智能车C车电机传递函数计算
  10. C#控件常用设计整理大全