今天给朋友们详细介绍react如何进行性能优化。

首先要了解网页性能不好的罪魁祸首

浏览器的回流【重排版】和重绘(reflows&repaints)(DOM操作都会引起)才是导致网页性能问题的关键。而React虚拟DOM的目的就是为了减少浏览器的重绘和回流。 ​

一:react中的问题

有时候组件的render方法会在不必要的情况下被调用。

比如:

  组件渲染的过程中,并没有使用props或者state的值,或者组件的props或者state并没有在父组件重新渲染时发生改变。这意味着重新渲染这个组件会得到和已知虚拟DOM一模一样的结果。

二:优化关键shouldComponentUpdate

【shouldComponentUpdate的作用与使用pureComponent和React.memo作用一致】

  组件更新生命周期中必调用shouldComponentUpdate,字面意思是组件是否应该更新。shouldComponentUpdate默认返回true,必定更新。所有当我们判断出组件没必要更新时,shouldComponentUpdate可以返回false,就达到优化效果。

shouldComponentUpdate生命周期有俩个参数,一个是nextProps,另一个是nextState,而我们就用这俩个上一次的props和state与这一次的props和state去做比较,如果俩者相同,那么就return false,不让它进行更新就可以了。


注意点:
shouldComponentUpdate()生命周期正常情况下只会进行浅比较。如果要进行深比较请继续往下看。
深比较和浅比较涉及到堆内存和栈内存的知识点,可以观看笔者的另外一篇博客去深入了解。


示例代码如下:

shouldComponentUpdate(nextProps,nextState) {
if (nextProps === this.props && nextState === this.state)
return false
return true
}

如果说你的组件不需要对比props或者state可以把if判断中对应的代码去掉

三:传参优化

  React中,组件嵌套是十分常见的,在父组件往子组件传递对象时,应该将对象的key和value在render()内先定义再使用,不然每一次使用子组件时都会生成新的对象进行传递。

  切记将父组件的props/state以展开形式传递给子组件,除非子组件需要用到父组件的所有props/state。

四:多组件优化

  在父组件因状态的变化更改,而子组件并没有状态变化时,若子组件随着父组件一起更新,会造成比较大的性能浪费,为减少子组件额外渲染而浪费性能,可使用的方法有:

1. shouldComponentUpdate()前面有介绍,忘了的朋友可以回退到上面第二个小标题观看。

2. 使用React.PureComponent替换React.Component【函数组件的话就是React.memo】

  在使用shouldComponentUpdate函数比较前后的props/state是否一致时,通常会涉及到深层或浅层的比较,在React默认进行的浅层比较中,可以使用React.PureComponent让组件根据传来的数据进行渲染而不是全部数据的渲染,这比自己写shouldComponentUpdate函数进行比较来的简单且性能更好,但只适用于组件只根据传进来的数据进行渲染而没有内部状态时使用,可以最大限度的提升性能。

3. ImmutableJS:

  在比较props/state时,应使用深层比较的形式,但要手动写shouldComponentUpdate函数的深层比较需要写一个递归的函数,通过层层递归比较出当前值和next值的数据结构是否相同,这在性能方面是不可接受的,所以React建议也是默认的比较是只做浅对比,即不考虑props/state的数据结构,只考虑数值是否相同。所以在设计组件数据的传递时,不应做深层次的嵌套(如数据为对象,对象内有多个值,值内还是一个对象的形式)。而为了使组件在数据传递过程中保证渲染时当前值与next值一定是不相同的,facebook提供了immutable-js这个库,ImmutableJS提供了不可变的数据,即要让数据改变只能通过创建新数据的方式,而不能直接修改,这很大程度的降低了前后两个数据比较时的复杂度。

  由于Immutable库比较大【855kb】,所以如果在React中引用该库也是比较大的负担,有一个Immutablejs库的简易版叫做seamless-immutable【87.1kb】,该库只支持Map,Set,List三种数据类型,但相对Immutable来说较小,对应用的负担也小。

五:Key

  对于数组形式的数据,遍历时React会要求你为每一个数据值添加Key,而Key必须时独一无二的,在选取Key值时尽量不要用索引号,因为如果当数据的添加方式不是顺序添加,而是以其他方式(逆序,随机等),会导致每一次添加数据,每一个数据值的索引号都不一样,这就导致了Key的变化,而当Key变化时,React就会认为这与之前的数据值不相同,会多次执行渲染,会造成大量的性能浪费。所以只在万不得已时,才将数据的索引号当做Key。

六:做分片打包

使用React.Lazy和React.Suspense做分片打包

import React, { lazy } from "react";
const Home = lazy(() => import('./Home'));
const NotFound = lazy(() => import('./NotFound'));
const Login = lazy(() => import('./Login'));

export const routes = [
{
path: '/Home',
component: Home,
extra: true,
},
{
path: '/404',
component: NotFound,
},
{
path: '/Login',
component: Login,
extra: true,
},
];

import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import { routes } from './pages/router';
import BasicLayout from "./Layout";
import 'antd/dist/antd.css';

function App() {
return (
<Router>
<BasicLayout>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
{
routes.map((item, index) => (
<Route
key={index}
exact={item.extra}
path={item.path}
component={item.component}
/>
))
}
<Redirect from='/' to='/Home' />
<Redirect to='/404' />
</Switch>
</Suspense>
</BasicLayout>
</Router>
);
}

export default App;

引入react-loadable做分片打包【以前的版本没有React.Lazy和React.Suspense所以用这个插件,可做一个了解】

引入该插件后可以如下使用,实现组件的按需加载,大大提高页面速度。

import Loadable from 'react-loadable'
import Loading from './Loading' //Loading页面导出一个方法,return一个null或者其他的出来
import { xxx } from 'xxx' //引入你的页面

const test = Loadable({
loader:() => import('./NotFound'), //你的文件位置
loading:Loading
})

export {
xxx,
test,
}
七:使用React.Fragment或者一个空标签避免添加额外的DOM
<React.Fragment>
<h1>Hello!</h1>
<h1>Hello world!</h1>
<h1>Hello world again!</h1>
</React.Fragment>
// or
<>
<h1>Hello!</h1>
<h1>Hello world!</h1>
<h1>Hello world again!</h1>
</>
八:调整CSS而不是强制组件加载和卸载

尽量的减少组件的创建和销毁,这样对于性能还是有一定的损耗的,我们可以将组件隐藏掉,比如加hidden属性,控制css的display、opacity、visibility等等隐式隐藏的方法。

九:数据格式的优化统一
1. 后端返回数据的高度统一性,例如都是{data:[...],hasMore:true}这种形式
2. 前端组件的高度拆分和抽象,以便做到最大极限的灵活拼接。
十:做计算缓存

使用memozation类似的库进行计算缓存,或者自己写方法也可以,很简单。。。

使用函数组件的useMemo或者useCallback

Redux性能优化:

一:使用reselect库

  在使用Redux进行数据的传递时,特别是经常有重复性的数据传递操作时,可以使用reselect库在内部对数据进行缓存处理,在重复调用时便可使用缓存快速加载,加强性能。

  在调用到已经执行过的数据时,react不会再次对数据进行渲染,而是从reselector中取出缓存数据加载,减少了重新渲染,达到性能优化的效果。