React 性能优化总结

简介: React 性能优化总结
🙋🏻‍♀️编编拎重点:本文总结整理了在 React 应用中可采用的一些性能优化方式,提供了4种优化思路,欢迎查阅。


前言

目的

目前在工作中,大量的项目都是使用 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://react.docschina.org/docs/optimizing-performance.html

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

相关文章
|
4月前
|
存储 前端开发 JavaScript
深入理解React Fiber架构及其性能优化
【10月更文挑战第5天】深入理解React Fiber架构及其性能优化
155 1
|
3月前
|
前端开发 JavaScript 算法
探索现代前端框架——React 的性能优化策略
探索现代前端框架——React 的性能优化策略
35 0
|
3月前
|
前端开发 JavaScript API
探索现代前端框架——React 的性能优化策略
探索现代前端框架——React 的性能优化策略
40 0
|
3月前
|
Web App开发 前端开发 JavaScript
React性能优化指南:打造流畅的用户体验
React性能优化指南:打造流畅的用户体验
|
9月前
|
前端开发 API 开发者
你可能没有关注过的 React 性能优化,帮你突破瓶颈
你可能没有关注过的 React 性能优化,帮你突破瓶颈
|
5月前
|
前端开发 JavaScript UED
深入React Hooks与性能优化实践
深入React Hooks与性能优化实践
69 0
|
6月前
|
开发者 搜索推荐 Java
超越传统:JSF自定义标签库如何成为现代Web开发的个性化引擎
【8月更文挑战第31天】JavaServer Faces(JSF)框架支持通过自定义标签库扩展其内置组件,以满足特定业务需求。这涉及创建`.taglib`文件定义标签库及组件,并实现对应的Java类与渲染器。本文介绍如何构建和应用JSF自定义标签库,包括定义标签库、实现标签类与渲染器逻辑,以及在JSF页面中使用这些自定义标签,从而提升代码复用性和可维护性,助力开发更复杂且个性化的Web应用。
90 0
|
6月前
|
前端开发 测试技术 UED
React性能优化的神奇之处:如何用懒加载与代码分割让你的项目一鸣惊人?
【8月更文挑战第31天】在现代Web开发中,性能优化至关重要。本文探讨了React中的懒加载与代码分割技术,通过示例展示了如何在实际项目中应用这些技术。懒加载能够延迟加载组件,提高页面加载速度;代码分割则将应用程序代码分割成多个块,按需加载。两者结合使用,可以显著提升用户体验。遵循合理使用懒加载、编写测试及关注性能等最佳实践,能够更高效地进行性能优化,提升应用程序的整体表现。随着React生态的发展,懒加载与代码分割技术将在未来Web开发中发挥更大作用。
65 0
|
6月前
|
缓存 前端开发 JavaScript
React.memo 与 useMemo 超厉害!深入浅出带你理解记忆化技术,让 React 性能优化更上一层楼!
【8月更文挑战第31天】在React开发中,性能优化至关重要。本文探讨了`React.memo`和`useMemo`两大利器,前者通过避免不必要的组件重渲染提升效率,后者则缓存计算结果,防止重复计算。结合示例代码,文章详细解析了如何运用这两个Hook进行性能优化,并强调了合理选择与谨慎使用的最佳实践,助你轻松掌握高效开发技巧。
144 0
|
7月前
|
缓存 监控 前端开发
react 性能优化方案?
【7月更文挑战第15天】改善React应用性能的关键策略包括:使用生产环境构建减少体积,避免不必要的渲染(如用React.memo或PureComponent),正确设置列表渲染的key,简化组件层级,实施懒加载,避免render中的复杂计算,选择优化过的库,控制重渲染范围,监控性能并合并state更新。这些优化能提升响应速度和用户体验。
73 0