前言
目的
目前在工作中,大量的项目都是使用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 (
-
-
Student name: {name}
-
Student age:{age}
-
Student address:{address}
-
- )
- }
- }
按照 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) {
-
- }
- function areEqual(prevProps, nextProps) {
-
- }
- 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 }) => (
-
- {list.map((i) => (
-
- ))}
-
- );
上面这个写法看起来是比较简洁,但是有一个潜在问题是匿名函数在每次渲染时都会有不同的引用,这样就会导致Menu组件会出现重复渲染的问题;可以使用useCallback来进行优化:
- const MenuContainer = ({ list }) => {
- const handleClick = useCallback(
- (id) => () => {
- // ...
- },
- [],
- );
-
- return (
-
- {list.map((i) => (
-
- ))}
-
- );
- };
减少渲染的节点
组件懒加载
组件懒加载可以让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 (
-
-
Loading... }> -
-
-
-
-
-