本文主要对在React应用中可以采用的一些性能优化方式做一下总结整理

前言

目的

目前在工作中,大量的项目都是使用react来进行开展的,了解掌握下react的性能优化对项目的体验和可维护性都有很大的好处,下面介绍下在react中可以运用的一些性能优化方式;

性能优化思路

对于类式组件和函数式组件来看,都可以从以下几个方面去思考如何能够进行性能优化

  • 减少重新render的次数
  • 减少渲染的节点
  • 降低渲染计算量
  • 合理设计组件

减少重新render的次数

在react里时间耗时最多的一个地方是reconciliation(reconciliation 的最终目标是以最有效的方式,根据新的状态来更新 UI,我们可以简单地理解为 diff),如果不执行render,也就不需要reconciliation,所以可以看出减少render在性能优化过程中的重要程度了。

PureComponent

React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。
需要注意的是在使用PureComponent的组件中,在props或者state的属性值是对象的情况下,并不能阻止不必要的渲染,是因为自动加载的shouldComponentUpdate里面做的只是浅比较,所以想要用PureComponent的特性,应该遵守原则:

  • 确保数据类型是值类型
  • 如果是引用类型,不应当有深层次的数据变化(解构)

ShouldComponentUpdate

可以利用此事件来决定何时需要重新渲染组件。如果组件 props 更改或调用 setState,则此函数返回一个 Boolean 值,为true则会重新渲染组件,反之则不会重新渲染组件。
在这两种情况下组件都会重新渲染。我们可以在这个生命周期事件中放置一个自定义逻辑,以决定是否调用组件的 render 函数。
下面举一个小的例子来辅助理解下:
比如要在你的应用中展示学生的详细资料,每个学生都包含有多个属性,如姓名、年龄、爱好、身高、体重、家庭住址、父母姓名等;在这个组件场景中,只需要展示学生的姓名、年龄、住址,其他的信息不需要在这里展示,所以在理想情况下,除去姓名、年龄、住址以外的信息变化组件是不需要重新渲染的;
示例代码如下:

import React from "react";

export default class ShouldComponentUpdateUsage extends React.Component {

  constructor(props) {
super(props);
this.state = {
name: "小明",
age: 12,
address: "xxxxxx",
height: 165,
weight: 40
}
} componentDidMount() {
setTimeout(() => {
this.setState({
height: 168,
weight: 45
});
}, 5000)
} shouldComponentUpdate(nextProps, nextState) {
if(nextState.name !== this.state.name || nextState.age !== this.state.age || nextState.address !== this.state.address) {
return true;
}
return false;
} render() {
const { name, age, address } = this.state;
return (
<div>
<p>Student name: {name} </p>
<p>Student age:{age} </p>
<p>Student address:{address} </p>
</div>
)
}
}

按照 React 团队的说法,shouldComponentUpdate是保证性能的紧急出口,既然是紧急出口,那就意味着我们轻易用不到它。但既然有这样一个紧急出口,那说明有时候它还是很有必要的。所以我们要搞清楚到底什么时候才需要使用这个紧急出口。

使用原则

当你觉得,被改变的state或者props,不需要更新视图时,你就应该思考要不要使用它。
需要注意的一个地方是:改变之后,又不需要更新视图的状态,也不应该放在state中。
shouldComponentUpdate的使用,也是有代价的。如果处理得不好,甚至比多render一次更消耗性能,另外也会使组件的复杂度增大,一般情况下使用PureComponent即可;

React.memo

如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);

注意

与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。

合理使用Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。正是因为其这个特点,它是可以穿透React.memo或者shouldComponentUpdate的比对的,也就是说,一旦 Context 的 Value 变动,所有依赖该 Context 的组件会全部 forceUpdate.这个和 Mobx 和 Vue 的响应式系统不同,Context API 并不能细粒度地检测哪些组件依赖哪些状态。

原则

  • Context中只定义被大多数组件所共用的属性,例如当前用户的信息、主题或者选择的语言。

避免使用匿名函数

首先来看下下面这段代码

const MenuContainer = ({ list }) => (
<Menu>
{list.map((i) => (
<MenuItem key={i.id} onClick={() => handleClick(i.id)} value={i.value} />
))}
</Menu>
);

上面这个写法看起来是比较简洁,但是有一个潜在问题是匿名函数在每次渲染时都会有不同的引用,这样就会导致Menu组件会出现重复渲染的问题;可以使用useCallback来进行优化:

const MenuContainer = ({ list }) => {
const handleClick = useCallback(
(id) => () => {
// ...
},
[],
); return (
<Menu>
{list.map((i) => (
<MenuItem key={i.id} id={i.id} onClick={handleClick(i.id)} value={i.value} />
))}
</Menu>
);
};

减少渲染的节点

组件懒加载

组件懒加载可以让react应用在真正需要展示这个组件的时候再去展示,可以比较有效的减少渲染的节点数提高页面的加载速度

React官方在16.6版本后引入了新的特性:React.lazy 和 React.Suspense,这两个组件的配合使用可以比较方便进行组件懒加载的实现;
React.lazy
该方法主要的作用就是可以定义一个动态加载的组件,这可以直接缩减打包后bundle的体积,并且可以延迟加载在初次渲染时不需要渲染的组件,代码示例如下:
使用之前

import SomeComponent from './SomeComponent';

使用之后

const SomeComponent = React.lazy(() => import('./SomeComponent'));

使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。

React.Suspense
该组件目前主要的作用就是配合渲染lazy组件,这样就可以在等待加载lazy组件时展示loading元素,不至于直接空白,提升用户体验;
Suspense组件中的 fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置,你甚至可以用一个 Suspense 组件包裹多个懒加载组件。
代码示例如下:

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent')); function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}

有一点要特别注意的是:React.lazy 和 Suspense 技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,推荐使用 Loadable Components 这个库,可以结合这个文档服务端渲染打包指南来进行查看。

另外在业内也有一些比较成熟的react组件懒加载开源库:react-loadablereact-lazyload,感兴趣的可以结合看下;

虚拟列表

虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术,在开发一些项目中,会遇到一些不是直接分页来加载列表数据的场景,在这种情况下可以考虑结合虚拟列表来进行优化,可以达到根据容器元素的高度以及列表项元素的高度来显示长列表数据中的某一个部分,而不是去完整地渲染长列表,以提高无限滚动的性能。
可以关注下放两个比较常用的类库来进行深入了解

降低渲染计算量

useMemo

先来看下useMemo的基本使用方法:

function computeExpensiveValue(a, b) {
// 计算量很大的一些逻辑
return xxx
} const memoizedValue = useMemo(computeExpensiveValue, [a, b]);

useMemo 的第一个参数就是一个函数,这个函数返回的值会被缓存起来,同时这个值会作为 useMemo 的返回值,第二个参数是一个数组依赖,如果数组里面的值有变化,那么就会重新去执行第一个参数里面的函数,并将函数返回的值缓存起来并作为 useMemo 的返回值 。

注意

  • 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值;
  • 计算量如果很小的计算函数,也可以选择不使用 useMemo,因为这点优化并不会作为性能瓶颈的要点,反而可能使用错误还会引起一些性能问题。

遍历展示视图时使用key

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);

使用key注意事项:

  • 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key,当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key
  • 元素的 key 只有放在就近的数组上下文中才有意义。例如,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 元素上,而不是放在 ListItem 组件中的
  • 元素上。

合理设计组件

简化props

如果一个组件的props比较复杂的话,会影响shallowCompare的效率,也会使这个组件变得难以维护,另外也与“单一职责”的原则不符合,可以考虑进行拆解。

简化State

在设计组件的state时,可以按照这个原则来:需要组件响应它的变动或者需要渲染到视图中的数据,才放到 state 中;这样可以避免不必要的数据变动导致组件重新渲染。

减少组件嵌套

一般不必要的节点嵌套都是滥用高阶组件/RenderProps 导致的。所以还是那句话‘只有在必要时才使用 xxx’。 有很多种方式来代替高阶组件/RenderProps,例如优先使用 props、React Hooks

参考

https://react.docschina.org/docs/optimizing-performance.html

https://www.infoq.cn/article/2016/07/react-shouldcomponentupdate

React性能优化总结的更多相关文章

  1. react性能优化

    前面的话 本文将详细介绍react性能优化 避免重复渲染 当一个组件的props或者state改变时,React通过比较新返回的元素和之前渲染的元素来决定是否有必要更新实际的DOM.当他们不相等时,R ...

  2. React性能优化记录(不定期更新)

    React性能优化记录(不定期更新) 1. 使用PureComponent代替Component 在新建组件的时候需要继承Component会用到以下代码 import React,{Componen ...

  3. 关于React性能优化

    这几天陆陆续续看了一些关于React性能优化的博客,大部分提到的都是React 15.3新加入的PureComponent ,通过使用这个类来减少React的重复渲染,从而提升页面的性能.使用过Rea ...

  4. React性能优化之PureComponent 和 memo使用分析

    前言 关于react性能优化,在react 16这个版本,官方推出fiber,在框架层面优化了react性能上面的问题.由于这个太过于庞大,我们今天围绕子自组件更新策略,从两个及其微小的方面来谈rea ...

  5. React 性能优化 All In One

    React 性能优化 All In One Use CSS Variables instead of React Context https://epicreact.dev/css-variables ...

  6. React 性能优化总结

    初学者对React可能满怀期待,觉得React可能完爆其它一切框架,甚至不切实际地认为React可能连原生的渲染都能完爆--对框架的狂热确实会出现这样的不切实际的期待.让我们来看看React的官方是怎 ...

  7. react 性能优化

    React 最基本的优化方式是使用PureRenderMixin,安装工具 npm i react-addons-pure-render-mixin --save,然后在组件中引用并使用 import ...

  8. React性能优化总结(转)

    原文链接: https://segmentfault.com/a/1190000007811296?utm_source=tuicool&utm_medium=referral 初学者对Rea ...

  9. react性能优化要点

    1.减少render方法的调用 1.1继承React.PureComponent(会自动在内部使用shouldComponentUpdate方法对state或props进行浅比较.)或在继承自Reac ...

随机推荐

  1. Django基础007--filter&tag

    1.Django自带的过滤器filter def index(request): print('index...........') navs = '今天天气真好fdsfds!!!' title='& ...

  2. 关于 IPv6 国家有大动作啦!快来瞅瞅行动计划都说了什么~

    随着进入三伏天开始,杭州就像突然被丢上了炭火炉,没有空调的高温厕所,彻底断绝了二狗子带薪摸鱼的快乐.深感绝望的二狗子只能痛苦地把自己的摸鱼地点改成了空调大开的零食角."哎,真的很不喜欢零食角 ...

  3. CentOS 7 文件权限之访问控制列表(ACL)

    Linux的ACL是文件权限访问的一种手段.当拥有者所属组其他人(own,group,other)不能满足给一个单独的用户设置单独的权限时,ACL的出现就很好的解决了该问题. 比如其他用户own,不属 ...

  4. [刘阳Java]_SpringMVC文件上传第1季_第10讲

    今天来介绍一个关于SpringMVC框架的文件上传功能.首先我个人感觉SpringMVC框架的文件上传还是要比Struts2框架要好用一些,灵活性更强.因为SpringMVC框架的文件上传有几种不同的 ...

  5. 前端js重组树形结构数据方法封装

    不知道大家平时工作中,有没有遇到这样一种情况:后端接口返回的数据,全都是一维的数组,都是平铺直叙式的数据,业务需求却要你实现树形结构的功能.那么,针对这种情况该怎么办呢?是跟后台好好沟通一下呢,还是沟 ...

  6. Day8 方法详解及递归思想.

    何为方法 Java方法是语句的集合,它们在一起执行一个功能. 方法是解决一类问题步骤的有序组合 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 设计方法的原则: 方法的本意是功能块,就是实 ...

  7. 一个很多人不知道的SpringBoot小技能!!

    大家好,我是冰河~~ 最近,发现很多小伙伴在修改了SpringBoot的配置文件后,都要重新编译整个项目,极大的浪费了开发时间.我身边就有很多小伙伴一直是这样做的.那么,有没有什么方式能够修改配置文件 ...

  8. 第十四篇 -- QMainWindow与QAction(清空-全选-撤销-重做-关闭-语言选择)

    效果图: 这次添加了关闭-撤销-重做-全选-清空等功能,并添加了字体和字体大小选择.基本方法跟前面几篇类似. ui_mainWindow.py # -*- coding: utf-8 -*- # Fo ...

  9. Win10强制程序高DPI缩放设置

    起因 工作原因,需要在win10上安装数个古老vc版本(vc6,vc2008,vc2010),但是显示器是2K的,DPI缩放有问题 尝试 VC6比较好解决:右键,属性,兼容性,更改高DPI设置,勾选替 ...

  10. 1.1 MATLAB系统环境

    专题一 MATLAB基础知识 1.1 MATLAB系统环境 1. 续行符(三个点) 2. 当前文件夹 先建立当前文件夹,再cd 3.工作区窗口 4.搜索路径       01当前文件夹下的程序文件 变 ...