React项目性能优化
1. 使用生产版本和Fragment
1. 生产版本
确保发布的代码是生产模式下(压缩)打包的代码。
一般运行npm run build命令。
直接从webpack看配置文件,需要设置mode = 'production'。 调用teaser-webpack-plugin
React Devtools可以根据地址栏右侧图标颜色判断是否是生产模式。
2. Fragment
减少不必要节点的生成。也可以使用空标签(<></>)
2. 类组件使用PureComponent
减少不必要的重复渲染!
PureComponent的原理是重新设置了shouldComponentUpdate(nextProps, nextState)方法。
在该方法中将nextProps和this.props对象浅比较,将nextState和this.state对象进行浅比较
PureComponent模拟代码实现:
import React from 'react'; function shallowCompare(obj1, obj2) {
if(obj1 === obj2) {
return true;
}
if(typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}
for(let key of obj1Keys) {
// 如果obj1[key]和obj2[key]为对象;浅比较有些情况会失效
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true
}
export default class PureComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return !(shallowCompare(nextProps, this.props) && shallowCompare(nextState, this.state))
}
}
测试用例:
/*******父组件********/
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
number: 1
}
this.ref = React.createRef();
}
add = () => {
// this.state.number通过prop传递给Counter
// Counter通过PureComponnet进行浅比较
// Number(this.ref.current.value)为0时,Counter不刷新
this.setState(state => ({
number: state.number + Number(this.ref.current.value)
}))
}
render() {
console.log('App Render');
return (
<div>
<Counter number={this.state.number} />
<input ref={this.ref} />
<button onClick={this.add}>+</button>
</div>
)
}
} /*********子组件***********/
import React from 'react';
import PureComponent from './pureComponent'; export default class Counter extends PureComponent {
constructor(props) {
super(props);
this.state = {
count: 1
}
}
// 证明PureComponent会对this.state和nextState进行浅比较
add = () => {
// 不会导致render
this.setState((state) => ({
count: state.count + 0 // 0不会刷新,其他值会刷新
}))
}
render() {
console.log('Counter Render');
return (
<div>
<div>
Counter:{this.state.count}
</div>
<button onClick={this.add}>+</button>
<div>
{this.props.number}
</div>
</div>
)
}
};
3. 不可变数据-immutable
上面的PureComponent只是对简单数据(值为原始类型)进行浅比较;
而实际开发应用中,数据都是多层嵌套的复杂数据;只使用PureComponent可能会导致render失效。
示例(错误示例-数组):
state= {words: []};
add = () => {
// 将一个对象赋值给另一个变量;两者对应同一个地址
let newWords = this.state.words;
// push,pop,shift,splice方法都不会改变原址;
newWords.push(this.ref.current.value);
// 如果在子组件使用了PureComponent浅比较words的值,nextProps.words === this.props.words
// 所以不会引起子组件Counter的render方法调用
this.setState({
words: newWords
});
}
上面的示例可以通过不可变数据修复BUG。
不可变数据: 保持原数据不变,生成一个新的对象进行赋值
示例1:
add = () => {
// concat方法调用后,返回一个新数组
this.setState(state => ({
words: state.words.concat(this.ref.current.value)
}))
}
示例2:
add = () => {
// []符号本身就是new Array()的简写形式
this.setState(state => ({
words: [...state.words, this.ref.current.value]
}))
}
上面的示例都是复合数据是数组的情形;对于数据是对象的示例如下:
错误示例(对象):
class App extends React.PureComponent{
constructor(props) {
super(props);
this.state = {
words: {}
}
this.ref = React.createRef();
}
add = () => {
// Object.assign()被修改对象在第一个参数时,直接在原state.words对象地址上修改;
// 对于PureComponent来说,nextState和this.state相同,永远不会render
this.setState(state => ({
words: Object.assign(state.words, {a:this.ref.current.value})
}))
}
render() {
console.log('App Render');
return (
<div>
<input ref={this.ref} />
<button onClick={this.add}>+</button>
</div>
)
}
}
使用不可变数据,生成新对象的方法修改;
示例1:
add = () => {
// Object.assign()第一个参数是空对象,表明生成一个新的对象
this.setState(state => ({
words: Object.assign({}, state.words, {a:this.ref.current.value})
}))
}
示例2:
add = () => {
// {}本身是new Object的简写形式
this.setState(state => ({
words: {...state.words, a: this.ref.current.value}
}))
}
上面的方法虽然解决了使用PureComponent组件时,对象导致的不刷新问题;
但是,会出现,只要是调用setState就重新的render的情况,尽管对应的值其实是一样的。
因为对于js引擎来说,任何两个对象都不相同。而且,嵌套层次特别深时,书写也复杂。
immutable库可以解决这个!
immutable
immutable不仅可以生成新的对象;还可以对对象进行深层次的值比较。
import { Map, is } from 'immutable'; class App extends React.Component{
constructor(props) {
super(props);
this.state = {
// is方法判断的时候,深层比较只能比较从immutable对象变化而来的对象
// 因为这里考虑words会进行深层变化,要追踪变化,需要将其变成immutable对象
words: Map({}),
number: 1
}
this.ref = React.createRef();
}
shouldComponentUpdate(nextProps, nextState) {
// is方法用于比较两个immutable对象是否值相等
return !(is(Map(nextState), Map(this.state)) && is(Map(nextProps), Map(this.props)))
}
add = () => {
// set方法给immutable对象赋值;
// 另可以通过get获取immutable对象的值
// this.state.words.get('a')
this.setState(state => ({
words: state.words.set('a', this.ref.current.value)
}))
}
reset = () => {
this.setState(state => ({
number: state.number + 1
}))
}
render() {
console.log('App Render');
return (
<div>
<input ref={this.ref} />
<button onClick={this.add}>+</button><br/>
<button onClick={this.reset}>reset</button>
</div>
)
}
}
4. 函数组件使用React.memo避免重复渲染
React.memo()本质上是将函数组件转为继承React.PureComponent的类组件的高阶组件。
稍有不同的是,只比较props的值。
代码实现如下:
function memo(WrappedComponent) {
return class extends React.PureComponent {
render() {
return (
<WrappedComponent {...this.props} />
)
}
}
}
对于深层比较,还可以接受第二个参数
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
5. React.lazy()和Suspense进行懒加载
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react'; const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About')); const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
其中:React.lazy()中import只支持默认导出的组件。
其外要包含在Suspense组件中。
6. 异常捕获边界(Error Boundaries)
捕获发生异常的React组件。将异常组件和正常组件分割开。
提高用户的体验性能。
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent')); const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
MyErrorBoundary代码:
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
} static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
} componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
} render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
} return this.props.children;
}
}
7. 骨架屏
骨架屏插件以react-content-loader(svg图片生层)为基础。
用于在页面初始加载时,避免出现白屏现象。
8.长列表优化
虚拟化长列表:只加载可视范围内的数据。
当网站需要加载大批量数据(成千上万)时,会加载特别慢。
这个时候我们可以使用“虚拟滚动”技术(react-window或者react-virtualized),只渲染当前屏幕范围内的数据。
鼠标滚动去触发事件,再渲染一屏。
react-window用法示例:
import { FixedSizeList as List } from 'react-window'; const Row = ({index, style}) => (
<div style={{...style, backgroundColor: getRandomColor()}}>
Row{index}
</div>
)
function Container(props) {
return (
<List
height={200}
itemCount={100}
itemSize={30}
width={'100%'}
>
{Row}
</List>
)
}
function getRandomColor() {
const color = Math.floor((Math.random()*0xFFFFFF)).toString(16)
if (color.length === 6) {
return '#' + color
}
return getRandomColor();
}
ReactDOM.render(<Container />, window.root)
原理是监听鼠标的滚动事件。
实现react-window的FixedSizeList的源码如下:
import React from 'react'; export class FixedSizeList extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.state = {
start: 0
}
}
componentDidMount() {
this.ref.current.addEventListener('scroll', () => {
// 注意: 监听函数内部有this.ref,需要使用箭头函数
let scrollTop = this.ref.current.scrollTop;
this.setState({
start: Math.floor(scrollTop/this.props.itemSize)
})
})
}
render() {
const { itemSize, itemCount, height, width, } = this.props;
const containerStyle = {
height, width, position: 'relative', overflow: 'auto'
}
let children = [];
const itemStyle = {
height: itemSize,
width: '100%',
position: 'absolute',
top: 0,
left: 0
}
const { start } = this.state;
for(let i=start; i< start + Math.ceil(height/itemSize); i++) {
children.push(this.props.children({index: i, style: {...itemStyle, top: i*30}}))
}
return (
<div style={containerStyle} ref={this.ref}>
<div style={{width: '100%', height: itemSize*itemCount}}>
{children}
</div>
</div>
)
}
}
9. 根据性能优化工具修改代码
1. 使用Chrome的Performance工具
2. React Devtools的Profiler工具分析;
通过React.Profiler组件包裹需要分析渲染时间的组件(不适合生产环境)。
10. SEO优化-预渲染
使用prerender-spa-plugin的puppeteer(无头浏览器)功能。
11. 图片懒加载
使用react-lazyload插件
12. key优化
React项目性能优化的更多相关文章
- React 组件性能优化
React组件性能优化 前言 众所周知,浏览器的重绘和重排版(reflows & repaints)(DOM操作都会引起)才是导致网页性能问题的关键.而React虚拟DOM的目的就是为了减少浏 ...
- 如何对react进行性能优化
React本身就非常关注性能,其提供的虚拟DOM搭配上DIff算法,实现对DOM操作最小粒度的改变也是非常高效的,然而其组件的渲染机制,也决定了在对组件更新时还可以进行更细致的优化. react组件 ...
- React Native 性能优化指南【全网最全,值得收藏】
2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了.文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能 ...
- React组件性能优化
转自:https://segmentfault.com/a/1190000006100489 React: 一个用于构建用户界面的JAVASCRIPT库. React仅仅专注于UI层:它使用虚拟DOM ...
- c#+wpf项目性能优化之OutOfMemoryException解密
近期,使用c#+wpf开发的软件准备正式投入使用了,使用前进行了大量的测试,测试后发现了一些问题,其中最让人头疼的就是软件的性能问题(稳定性). 这里的稳定性具体表现在机器的cpu占有率和内存使用情况 ...
- Reactjs项目性能优化
在construct中绑定函数this shouldComponentUpdate React.PureComponent 无状态组件 chrome浏览器性能优化工具 setTimeout,setIn ...
- C# 大型电商项目性能优化(一)
经过几个月的忙碌,我厂最近的电商平台项目终于上线,期间遇到的问题以及解决方案,也可以拿来和大家多做交流了. 我厂的项目大多采用C#.net,使用逐渐发展并流行起来的EF(Entity Framewor ...
- React组件性能优化总结
性能优化的思路 影响网页性能最大的因素是浏览器的重排(repaint)和重绘(reflow). React的Virtual DOM就是尽可能地减少浏览器的重排和重绘. 从React渲染过程来看,如何防 ...
- vuejs项目性能优化总结
在使用elementUI构建公司管理系统时,发现首屏加载时间长,加载的网络资源比较多,对系统的体验性会差一点,而且用webpack打包的vuejs的vendor包会比较大.所以通过搜集网上所有对于vu ...
随机推荐
- windows下java环境变量的一点心得
JAVA_HOME:D:\software\java\jdk1.8.0_121 CLASSPATH:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar ...
- java-TheadPoolExecutor
Executor的两极调度模型 第一级:java多线程程序把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程: 第二级:操作系统内核将这些线程映射到处 ...
- PHP trait介绍
Trait 自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait. Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制.Trait 为了减少单继承语言的限制, ...
- 3D数学基础_图形与游戏开发
https://blog.csdn.net/popy007/article/list/2?t=1& //向量计算相关文章 https://www.baidu.com/link?url=48C ...
- Win10安装PyQt5与Qt Designer
1.直接在cmd中通过pip安装PyQt5 1 pip install pyqt5 会自动下载PyQt5以及sip并安装,因为PyQt5不再提供Qt Designer等工具,所以需要再安装pyqt5- ...
- js如何获取数值
获取jsfunction返回的值1,首先,用初始化代码创建一个新的HTML5文件,如下所示.2,创建一个新的H1标记来接收JS中函数休闲鹿的返回值.3,创建一个新的脚本标记并在标记中创建一个新函数.函 ...
- 阮一峰:jQuery官方基础教程笔记
jQuery是目前使用最广泛的javascript函数库. 据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库.微软公司甚至把jQuery作为他们的官方库. 对于网页开发者来 ...
- 六、TreeMap的使用 及其源码解析
TreeMap中的元素默认按照keys的自然排序排列 1. 构造函数TreeMap(): 创建一个空的TreeMap ,keys按照自然排序TreeMap(Comparator comparator) ...
- K2 BPM_快消零售连锁行业门店选址解决方案_十年专注业务流程管理系统
>>>业务流程管理软件选型攻略 快消零售连锁行业门店选址解决方案 业内有句名言:“门店最重要的是什么?第一是选址,第二是选址,第三还是选址” 选址是一个很复杂的综合性商业决策过 ...
- Android开发之图像处理那点事——滤镜
在Android开发中,一般对图像的处理就是Bitmap(位图),它包含了图像的全部数据,即点阵和颜色值,点阵就是包含像素点的矩阵,而颜色值就是ARGB,分别代表透明.红色.绿色.蓝色通道,它们共同决 ...