React性能调优实践
在介绍过前端优化方法后,我试图总结一下代码角度,自己在React项目实践的优化方法。下面从几个方面总结一下React代码优化:
以下大部分方法是基于React的渲染角度去做优化,React的刷新过程中会导致大量不必要的diff计算,即使很多时候state没有发生变化,此时我们不需要更新UI,也就不需要diff计算,而我们正是需要找到办法来减少这一部分的性能消耗。
1. 使用纯函数组件
const FunctionalComponent = (props) => (
纯函数组件
)
export default FunctionalComponent
- 语法更简洁,比起有状态组件,少了很多代码,不用声明类,没有constructor等函数代码
- 占内存更小(class 有 props context _context 等诸多属性),首次 render 的性能更好
- 可以写成无副作用的纯函数,输入输出都是可控可预知的,并且便于测试
- 可拓展性更强(函数的 compose,currying 等组合方式,比 class 的 extend/inherit 更灵活)
首先我们要明确一点,React中只要父组件的render了,那么默认情况下就会触发子组件的render过程,子组件的render过程又会触发它的子组件的render过程,一直到React元素(即jsx中的这样的元素)。当render过程到了叶子节点,即React元素的时候,diff过程便开始了,这时候diff算法会决定是否需要更新DOM元素。【React性能调优实践】为了避免子组件无意义的render,React提供了shouldComponentUpdate方法,让我们自己选择要不要render组件,我们可以把props做一个比较,如果props没有变化,我们就不重渲染组件
export default class ShouldUpdateComponent extends React.Component{
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.custom !== this.props.custom) {
return true
}
return false
}
render() {
console.log('render')
return 使用shouldComponentUpdate做了一个浅比较
}
}
或者使用PureComponent
import React, { PureComponent } from 'react';
export default class Foo extends PureComponent {
//...
}
但是我们要注意一点,以上两种方法都是对props做了一个浅比较,无法判断生层次的属性改变,会导致组件不正常更新,关于这点,我们可以使用lodash的cloneDeep或者immutable库来实现不变的数据,这里之后详细探讨下。然而我们可以做的是,尽量扁平化props,避免过深的对象
3. key 对于循环或者迭代生成的元素,一定要有key值,这个想必大家都知道,因为在没有赋key的时候,React会在控制台输出警告信息。
这里强调一下,不要用循环时的index做key,这是非常低效的做法,这个key是每次用来做Virtual DOM diff的,用index的话相当于用了随机键,每次更新都会重新渲染,要使用循环中唯一ID,一般来说是后台返回的该条数据的ID
从diff算法的角度来看key的必须性,React diff中有一个策略,就是相同层级的一组子节点,它们可以通过唯一ID进行区分,如果ID没变,React认为这个节点可以复用,无需经历非常消耗性能的删除和构建操作,为了做到能复用节点,我们必须要给到子节点唯一ID,以便React进行判断。
Ref详解-这篇文章讲解key可谓非常翔实4. React中的动态加载 我一直在项目中使用
react-loadable
这个库来实现异步加载组件,大部分时候用来结合路由,实现路由级组件的按需加载。// 引入组件
const TodoApp = Loadable({
loader: () => import('../pages/todoapp'),
delay: 300, // 避免loading组件的闪烁
timedOut: 5000, // 超时处理
loading: MyLoadingComponent // loading组件
});
// 放置于路由组件中 可做到不同路由的按需加载
// 自己定义的Loading组件
import React from 'react';
const MyLoadingComponent = ({isLoading, error, pastDelay, retry, timeOut}) => {
// 处理延时展示loading情况
if (pastDelay) {
return Loading...;
}
// 处理组件发生错误的情况
else if (error) {
return Error! ;
}
// 处理组件加载超时的组件
else if (timeOut) {
return Timeout! ;
}
else {
return null;
}
};
export default MyLoadingComponent;
5. 有关事件处理函数的优化 我们知道最好不要在render中使用箭头函数或者bind进行this绑定,因为每次组件渲染都会触发render方法,那么会在每次组件渲染时创建一个新的函数,会引起一些性能消耗。所以最好的方法是render外定义事件函数并进行this绑定,然后传入组件。
class Foo extends Component {
// Note: this syntax is experimental and not standardized yet.
handleClick = () => {
console.log('Click happened');
}
render() {
return ;
}
}
在需要优化大量元素或使用依赖于React.PureComponent相等性检查的渲染树的场景下,使用箭头函数或者bind的方式为回调函数传递参数必然不是最优方案,此时我们可以通过data-属性传递参数
handleClick(e) {
this.setState({
justClicked: e.target.dataset.letter
});
}render() {
return (
{this.state.letters.map(letter =>
-
{letter}
)}
)
}
6. 事件处理函数的节流和防抖 7. 在ReactV16版本中可以使用的性能优化API
- React.memo
使用该api可以创建自动进行props浅比较的函数式组件,之前因为函数式组件没有自己的生命周期,也就无法应用shouldComponentUpdate做文章了,但是有了这个api一切都将不同,pure functional component诞生了!
import React from 'react';
const MemoComponent = React.memo(props => {
return (使用react16新特性 memo 来做函数式组件的浅比较
{props.data})
});
export default MemoComponent;
- lazy 和 Suspense
作为React官方推出的异步加载方案,还是很值得一试的,实现的功能其实与react-loadable
这个库类似,但是其中有不少异同的细节,之后希望专门写一篇文章讨论一下这个差异。
import React, { Fragment, lazy, Suspense } from 'react';
const ShouldUpdateComponent = lazy(() => import('../components/performance/shouldUpdateComponent'));
使用该方法后,ShouldUpdateComponent这个组件以chunk的形式异步加载,起到了减少主文件代码体积的作用
高性能 React:3 个新工具加速你的应用
本文介绍了如何借助工具检测到react项目中不必要的组件渲染,值得借鉴
推荐阅读
- react|react 安装
- 数据库|SQL行转列方式优化查询性能实践
- 性能测试中QPS和TPS的区别
- React.js学习笔记(17)|React.js学习笔记(17) Mobx
- React.js的表单(六)
- javascript|javascript 性能测试笔记
- 【React|【React Native填坑之旅】从源码角度看JavaModule注册及重载陷阱
- react-navigation|react-navigation 动态修改 tabBar 样式
- 使用交叉点观察器延迟加载图像以提高性能
- golang锁竞争性能