React性能优化总结

更新日期: 2022-04-20阅读: 898标签: 性能

前言

目的

目前在工作中,大量的项目都是使用 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";

exportdefaultclassShouldComponentUpdateUsageextendsReact.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) {
returntrue;
}
returnfalse;
}

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 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

functionMyComponent(props) {
/* 使用 props 渲染 */}
functionareEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/}
exportdefault 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'));

functionMyComponent() {
return (
<div><Suspense fallback={<div>Loading...</div>}>
<section><OtherComponent /><AnotherComponent /></section></Suspense></div> );
}

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

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

虚拟列表

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

可以关注下放两个比较常用的类库来进行深入了解

  • react-virtualized

  • react-window


降低渲染计算量

useMemo

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

functioncomputeExpensiveValue(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://fly63.com/article/detial/11371

提高js加载速度,实现js无阻塞加载方式,高性能的加载执行JavaScript

为解决JS加载速度慢,采用js的延时加载,和动态加载。由于js的堵塞特性,当浏览器在加载javascript代码时,不能同时做其他任何事情,如果javascript执行时间越久,浏览器等待响应的时间就越久。

如何提高CSS性能?CSS优化、提高性能提升总汇

如何提高CSS性能,根据页面的加载性能和CSS代码性能,主要表现为: 加载性能 (主要是从减少文件体积,减少阻塞加载,提高并发方面入手),选择器性能,渲染性能,可维护性。

前端性能优化_css加载会造成哪些阻塞现象?

css的加载是不会阻塞DOM的解析,但是会阻塞DOM的渲染,会阻塞link后面js语句的执行。这是由于浏览器为了防止html页面的重复渲染而降低性能,所以浏览器只会在加载的时候去解析dom树,然后等在css加载完成之后才进行dom的渲染以及执行后面的js语句。

2018 前端性能检查表

性能十分重要。然而,我们真的知道性能瓶颈具体在哪儿吗?是执行复杂的 JavaScript,下载缓慢的 Web 字体,巨大的图片,还是卡顿的渲染?研究摇树(Tree Shaking),作用域提升(Scope Hoisting)

高性能Javascript总结

Js高性能总结:加载和运行、数据访问、DOM编程、算法和流程控制、响应接口、Ajax 异步JavaScript和XML、编程实践...

优化网站性能规则_前端性能优化策略【网络加载、页面渲染】

前端网站性能优化规则:网络加载类、页面渲染类。包括:减少 HTTP 资源请求次数、减小 HTTP 请求大小、避免页面中空的 href 和 src、合理设置 Etag 和 Last-Modified、使用可缓存的 AJAX、减少 DOM 元素数量和深度等

前端性能的本质是什么?

性能一直以来是前端开发中非常重要的话题。随着前端能做的事情越来越多,浏览器能力被无限放大和利用:从 web 游戏到复杂单页面应用,从 NodeJS 服务到 web VR/AR 和数据可视化,前端工程师总是在突破极限

BigPipe_高性能流水线页面技术

BigPipe是一个重新设计的基础动态网页服务体系。大体思路是,分解网页成叫做Pagelets的小块,然后通过Web服务器和浏览器建立管道并管理他们在不同阶段的运行。这是类似于大多数现代微处理器的流水线执行过程:多重指令管线通过不同的处理器执行单元,以达到性能的最佳。

用CSS开启硬件加速来提高网站性能

你知道我们可以在浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能,从而提升性能吗?现在大多数电脑的显卡都支持硬件加速。鉴于此,我们可以发挥GPU的力量,从而使我们的网站或应用表现的更为流畅。

原生js实现懒加载并节流

像淘宝网站等,页面中有着大量图片,一次性全部加载这些图片会使浏览器发送大量请求和造成浪费。采用懒加载技术,即用户浏览到哪儿,就加载该处的图片。这样节省网络资源、提升用户体验、减少服务器压力。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!