React的性能优化技巧

哪些场景下,父组件和子组件会重新渲染?

1.在同一组件或父组件中调用 setState 时。

2.从父级收到的“props”的值发生变化。

3.调用组件中的 forceUpdate。

1. 使用纯组件

如果 React 组件为相同的状态和 props 渲染相同的输出,则可以将其视为纯组件。

对于像 this 的类组件来说,React 提供了 PureComponent 基类。扩展 React.PureComponent 类的类组件被视为纯组件。

它与普通组件是一样的,只是 PureComponents 负责 shouldComponentUpdate——它对状态和 props 数据进行浅层比较(shallow comparison)。

如果先前的状态和 props 数据与下一个 props 或状态相同,则组件不会重新渲染。


import React from 'react';

export default class ApplicationComponent extends React.Component {
constructor() {
      super();

    this.state = {

        name: "Mayank"
    }

}

updateState = () => {

    setInterval(() => {

        this.setState({

        name: "Mayank"

        })

    }, 1000)

}

componentDidMount() {

    this.updateState();

}

render() {
    console.log("Render Called Again")
    return (
        <div>
            <RegularChildComponent name={this.state.name} />
            <PureChildComponent name={this.state.name} />
        </div>
    )

}

}
class RegularChildComponent extends React.Component {
    render() {
        console.log("Regular Component Rendered..");
        return <div>{this.props.name}</div>;
    }
}

class PureChildComponent extends React.PureComponent {

    // Pure Components are the components that do not re-render if the State data or props data is still the same

    render() {
        console.log("Pure Component Rendered..")
        return <div>{this.props.name}</div>;
    }
}

2. 使用 React.memo 进行组件记忆

React.memo 是一个高阶组件。与上面说的PureComponent很像,只是他是用在函数式组件上的,它允许你自定义比较逻辑,用户可以用自定义逻辑深度对比(deep comparison)对象。如果比较函数返回 false 则重新渲染组件,否则就不会重新渲染。如下。


// The following function takes "user" Object as input parameter in props

function CustomisedComponen(props) {
    return (
        <div>
            <b>User name: {props.user.name}</b>
            <b>User age: {props.user.age}</b>
            <b>User designation: {props.user.designation}</b>
        </div>
    )
}

function userComparator(previosProps, nextProps) {
    if(previosProps.user.name == nextProps.user.name ||
       previosProps.user.age == nextProps.user.age ||
       previosProps.user.designation == nextProps.user.designation) {
        return false
    } else {
        return true;
    }
}

var memoComponent = React.memo(CustomisedComponent, userComparator);

3. 使用shouldComponentUpdate生命周期事件

这个函数将 nextState 和 nextProps 作为输入,并可将其与当前 props 和状态做对比,以决定是否需要重新渲染。


import React from "react";

export default class ShouldComponentUpdateUsage extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      name: "Mayank";
      age: 30,
      designation: "Architect";
    }
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({
        designation: "Senior Architect"
      });
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
      if(nextState.age != this.state.age || netState.name = this.state.name) {
        return true;
      }
      return false;
  }

  render() {
    return (
      <div>
        <b>User Name:</b> {this.state.name}
        <b>User Age:</b> {this.state.age}
      </div>
    )
  }
}

4. 懒加载组件

使用 Suspense 和 lazy。


import React, { lazy, Suspense } from "react";

export default class CallingLazyComponents extends React.Component {
  render() {

    var ComponentToLazyLoad = null;

    if(this.props.name == "Mayank") {
      ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
    } else if(this.props.name == "Anshul") {
      ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
    }
    return (
        <div>
            <h1>This is the Base User: {this.state.name}</h1>
            <Suspense fallback={<div>Loading...</div>}>
                <ComponentToLazyLoad />
            </Suspense>
        </div>
    )
  }
}

假设有两个组件 WelcomeComponent 或 GuestComponents,我们根据用户是否登录而渲染其中一个。

我们可以根据具体的条件延迟组件加载,无需一开始就加载两个组件。


import React, { lazy, Suspense } from "react";

export default class UserSalutation extends React.Component {

    render() {
        if(this.props.username !== "") {
          const WelcomeComponent = lazy(() => import("./welcomeComponent"));
          return (
              <div>
                  <Suspense fallback={<div>Loading...</div>}>
                      <WelcomeComponent />
                  </Suspense>
              </div>
          )
        } else {
            const GuestComponent = lazy(() => import("./guestComponent"));
            return (
                <div>
                    <Suspense fallback={<div>Loading...</div>}>
                        <GuestComponent />
                    </Suspense>
                </div>
            )
        }
    }
}

5. 使用 React Fragments 避免额外标记

6. 不要使用内联函数定义


<input type="button" onClick={(e) => { this.setState({inputValue: e.target.value}) }} value="Click For Inline Function" />

如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例。

当 React 进行虚拟 DOM diffing 时,它每次都会找到一个新的函数实例;因此在渲染阶段它会会绑定新函数并将旧实例扔给垃圾回收。

因此直接绑定内联函数就需要额外做垃圾回收和绑定到 DOM 的新函数的工作。

7. 避免componentWillMount()中的异步请求

因为请求是异步的,首次render时数据也是空的,而且该钩子函数中无法获取页面dom

注意:React 16.3 不推荐使用 componentWillMount。如果你使用的是最新版本的 React,请避免使用这个生命周期事件。

8. 在 Constructor 的早期绑定函数

当我们在 React 中创建函数时,我们需要使用 bind 关键字将函数绑定到当前上下文。

绑定可以在构造函数中完成,也可以在我们将函数绑定到 DOM 元素的位置上完成。

两者之间似乎没有太大差异,但性能表现是不一样的。

import React from "react";

export default class DelayedBinding extends React.Component {
  constructor() {
    this.state = {
      name: "Mayank"
    }
  }

  handleButtonClick() {
    alert("Button Clicked: " + this.state.name)
  }

  render() {
    return (
      <>
        <input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />
      </>
    )
  }
}

在上面的代码中,我们在 render 函数的绑定期间将函数绑定到按钮上。

上面代码的问题在于,每次调用 render 函数时都会创建并使用绑定到当前上下文的新函数,但在每次渲染时使用已存在的函数效率更高。优化方案如下:

......
constructor() {
    this.state = {
      name: "Mayank"
    }
    this.handleButtonClick = this.handleButtonClick.bind(this)
  }

这将减少将函数绑定到当前上下文的开销,无需在每次渲染时重新创建函数,从而提高应用的性能。

9. 箭头函数与构造函数中的绑定

处理类时的标准做法就是使用箭头函数。使用箭头函数时会保留执行的上下文。

箭头函数好处多多,但也有缺点。

当我们添加箭头函数时,该函数被添加为对象实例,而不是类的原型属性。这意味着如果我们多次复用组件,那么在组件外创建的每个对象中都会有这些函数的多个实例。

每个组件都会有这些函数的一份实例,影响了可复用性。此外因为它是对象属性而不是原型属性,所以这些函数在继承链中不可用。

因此箭头函数确实有其缺点。实现这些函数的最佳方法是在构造函数中绑定函数,如上所述。

10. 避免使用内联样式属性

使用内联样式时浏览器需要花费更多时间来处理脚本和渲染,因为它必须映射传递给实际 CSS 属性的所有样式规则。

import React from "react";

export default class InlineStyledComponents extends React.Component {
  render() {
    return (
        <b style={{"backgroundColor": "blue"}}>Welcome to Sample Page</b>
    )
  }
}

在上面创建的组件中,我们将内联样式附加到组件。添加的内联样式是 JavaScript 对象而不是样式标记。

样式 backgroundColor 需要转换为等效的 CSS 样式属性,然后才应用样式。这样就需要额外的脚本处理和 JS 执行工作。

更好的办法是将 CSS 文件导入组件。

11. 使用唯一键迭代

如果开发人员没有为元素提供键,则它将 index 作为默认键。在下面的代码中我们默认不添加任何键,因此 index 将用作列表的默认键。

使用 index 作为键就不会出现标识不唯一的问题了,因为 index 只会标识所渲染的组件。

我们可以在以下场景中使用 index 作为键:
1.列表项是静态的,项目不随时间变化。
2.Items 没有唯一 ID。
3.List 永远不会重新排序或过滤。
4.不会从顶部或中间添加或删除项目。

12.事件节流和防抖

13.使用 CDN

使用 CDN 有以下好处:

*. 不同的域名。浏览器限制了单个域名的并发连接数量,具体取决于浏览器设置。假设允许的并发连接数为 10。如果要从单个域名中检索 11 个资源,那么同时完成的只有 10 个,还有 1 个需要再等一会儿。CDN 托管在不同的域名 / 服务器上。因此资源文件可以分布在不同的域名中,提升了并发能力。

*. 文件可能已被缓存。有很多网站使用这些 CDN,因此你尝试访问的资源很可能已在浏览器中缓存好了。这时应用将访问文件的已缓存版本,从而减少脚本和文件执行的网络调用和延迟,提升应用性能。

*. 高容量基础设施。这些 CDN 由大公司托管,因此可用的基础设施非常庞大。他们的数据中心遍布全球。向 CDN 发出请求时,它们将通过最近的数据中心提供服务,从而减少延迟。这些公司会对服务器做负载平衡,以确保请求到达最近的服务器并减少网络延迟,提升应用性能。

14.使用 Web Workers 处理 CPU 密集任务

JavaScript 是一个单线程应用,但在渲染网页时需要执行多个任务:

处理 UI 交互、处理响应数据、操纵 DOM 元素、启用动画等。所有这些任务都由单个线程处理。

可以使用 worker 来分担主线程的负载。

Worker 线程在后台运行,可以在不中断主线程的情况下执行多个脚本和 JavaScript 任务。

每当需要执行长时间的 CPU 密集任务时,可以使用 worker 在单独的线程上执行这些逻辑块。

它们在隔离环境中执行,并且使用进程间线程通信与主线程交互。主线程就可以腾出手来处理渲染和 DOM 操作任务。

参考文章:https://mp.weixin.qq.com/s/iZqV6GAi5zyX5P48hR4VLA##

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 作为一个合格的开发者,不要只满足于编写了可以运行的代码。而要了解代码背后的工作原理;不要只满足于自己的程序...
    六个周阅读 8,428评论 1 33
  • 1. 基本原理 1.1 render()函数 一般来说,要尽可能少地在 render 函数中做操作。如果非要做一些...
    cbw100阅读 1,621评论 0 10
  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,810评论 0 24
  • 原文链接:https://www.jianshu.com/p/582346a54a3d 1. 前言 在 React...
    小豆soybean阅读 590评论 0 0
  • 压抑的心情①天①天的积压着,我不知道如何开口来说出我的想法,老板一味的感觉不到这种变化,还是在用他的方式对我,现在...
    赛亚米业阅读 223评论 0 0