React 组件性能最佳优化实践分享

目录
  • React 组件性能优化最佳实践
    • 组件卸载前进行清理操作
  • 类组件使用纯组件PureComponent
    • 什么是纯组件
    • 什么是浅层比较
    • shouldComponentUpdate
  • 纯函数组件使用React.memo优化性能
    • memo 基本使用
    • memo 传递比较逻辑
    • 使用组件懒加载
    • 路由组件懒加载
    • 根据条件进行组件懒加载(适用于组件不会随条件频繁切换)
    • 使用Fragment 避免额外标记
    • 不要使用内联函数定义
    • 在构造函数中进行函数this绑定
    • 类组件中的箭头函数
    • 优化条件渲染
    • 避免使用内联样式属性
    • 避免重复无限渲染
    • 避免数据结构突变

React 组件性能优化最佳实践

React 组件性能优化的核心是减少渲染真实 DOM 节点的频率,减少 Virtual DOM 比对的频率。如果子组件未发生数据改变不渲染子组件。

组件卸载前进行清理操作

以下代码在组件挂载时会创建一个interval组件销毁后清除定时器,间隔1秒会触发渲染count+1,组件销毁后如果不清除定时器它会一直消耗资源

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"

const App = () => {
  let [index, setIndex] = useState(0)
  useEffect(() => {
    let timer = setInterval(() => {
      setIndex(prev => prev + 1)
      console.log('timer is running...')
    }, 1000)
    return () => clearInterval(timer)
  }, [])
  return (
    <button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById("root"))}>
      {index}
    </button>
  )
}

export default App

每次数据更新都会触发组件重新渲染,这里的优化为:组件销毁清理定时器

类组件使用纯组件PureComponent

什么是纯组件

纯组件会对组件输入数据进行浅层比较,如果当前输入数据和上次输入数据相同,组件不会重新渲染

什么是浅层比较

比较引用数据类型在内存中的引用地址是否相同,比较基本数据类型的值是否相同。

为什么不直接进行 diff 操作, 而是要先进行浅层比较,浅层比较难道没有性能消耗吗

和进行 diff 比较操作相比,浅层比较将消耗更少的性能。diff 操作会重新遍历整颗 virtualDOM 树, 而浅层比较只操作当前组件的 state 和 props。

import React from "react"
export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三"}
  }
  updateName() {
    setInterval(() => this.setState({name: "张三"}), 1000)
  }
  componentDidMount() {
    this.updateName()
  }
  render() {
    return (
      <div>
        <RegularComponent name={this.state.name} />
        <PureChildComponent name={this.state.name} />
      </div>
    )
  }
}
class RegularComponent extends React.Component {
  render() {
    console.log("RegularComponent")
    return <div>{this.props.name}</div>
  }
}

class PureChildComponent extends React.PureComponent {
  render() {
    console.log("PureChildComponent")
    return <div>{this.props.name}</div>
  }
}

组件挂载以后会有一个定时器间隔1秒设置一次name,我们可以看到RegularComponent一直在渲染,即使数据没有发生变化也会渲染。PureChildComponent只有一次渲染,因此使用纯组件会对props state进行进行比较,数据相同不会重新渲染。

shouldComponentUpdate

纯组件只能进行浅层比较,要进行深层比较,使用 shouldComponentUpdate,它用于编写自定义比较逻辑。

返回 true 重新渲染组件,返回 false 阻止重新渲染。

函数的第一个参数为 nextProps, 第二个参数为 nextState。

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三", age: 20, job: "waiter"}
  }
  componentDidMount() {
    setTimeout(() => this.setState({ job: "chef" }), 1000)
  }
  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.name !== nextState.name || this.state.age !== nextState.age) {
      return true
    }
    return false
  }
  render() {
    console.log("rendering")
    let { name, age } = this.state
    return <div>{name} {age}</div>
  }
}

即使继承了Component的组件定时器一直修改数据也不会触发重新渲染

纯函数组件使用React.memo优化性能

memo 基本使用

将函数组件变为纯组件,将当前 props 和上一次的 props 进行浅层比较,如果相同就阻止组件重新渲染。

import React, { memo, useEffect, useState } from "react"
function ShowName({ name }) {
  console.log("showName render...")
  return <div>{name}</div>
}

const ShowNameMemo = memo(ShowName)
function App() {
  const [index, setIndex] = useState(0)
  const [name] = useState("张三")
  useEffect(() => {
    setInterval(() => {
      setIndex(prev => prev + 1)
    }, 1000)
  }, [])
  return (
    <div>
      {index}
      <ShowNameMemo name={name} />
    </div>
  )
}
export default App

memo 传递比较逻辑

(使用 memo方法自定义比较逻辑,用于执行深层比较。)

import React, { memo, useEffect, useState } from "react";

function ShowName({ person }) {
  console.log("showName render...");
  return (
    <div>
      {person.name}
      丨
      {person.job}
    </div>
  );
}

function comparePerson(prevProps, nextProps) {
  if (
    prevProps.person.name !== nextProps.person.name ||
    prevProps.person.age !== nextProps.person.age
  ) {
    return false
  }
  return true
}
const ShowNameMemo = memo(ShowName, comparePerson);
function App() {
  const [person, setPerson] = useState({ name: "张三", job: "developer" });
  useEffect(() => {
    setInterval(() => {
      setPerson((data) => ({ ...data, name: "haoxuan" }));
    }, 1000);
  }, []);
  return (
    <div>
      <ShowNameMemo person={person} />
    </div>
  );
}
export default App;

使用组件懒加载

使用组件懒加载可以减少 bundle 文件大小, 加快组件呈递速度。

路由组件懒加载

import React, { lazy, Suspense } from "react"
import { BrowserRouter, Link, Route, Switch } from "react-router-dom"

const Home = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"))
const List = lazy(() => import(/* webpackChunkName: "List" */ "./List"))

function App() {
  return (
    <BrowserRouter>
      <Link to="/">Home</Link>
      <Link to="/list">List</Link>
      <Switch>
        <Suspense fallback={<div>Loading</div>}>
          <Route path="/" component={Home} exact />
          <Route path="/list" component={List} />
        </Suspense>
      </Switch>
    </BrowserRouter>
  )
}
export default App

根据条件进行组件懒加载(适用于组件不会随条件频繁切换)

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

function App() {
  let LazyComponent = null
  if (true) {
    LazyComponent = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"))
  } else {
    LazyComponent = lazy(() => import(/* webpackChunkName: "List" */ "./List"))
  }
  return (
    <Suspense fallback={<div>Loading</div>}>
      <LazyComponent />
    </Suspense>
  )
}
export default App

使用Fragment 避免额外标记

为了满足这个条件我们通常都会在最外层添加一个div, 但是这样的话就会多出一个无意义的标记, 如果每个组件都多出这样的一个无意义标记的话, 浏览器渲染引擎的负担就会加剧。

import { Fragment } from "react"

function App() {
  return (
    <Fragment>
      <div>message a</div>
      <div>message b</div>
    </Fragment>
  )
}
function App() {
  return (
    <>
      <div>message a</div>
      <div>message b</div>
    </>
  )
}

不要使用内联函数定义

在使用内联函数后, render 方法每次运行时都会创建该函数的新实例, 导致 React 在进行 Virtual DOM 比对时, 新旧函数比对不相等,导致 React 总是为元素绑定新的函数实例, 而旧的函数实例又要交给垃圾回收器处理。

错误示范:

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  render() {
    return (
      <input
        value={this.state.inputValue}
        onChange={e => this.setState({ inputValue: e.target.value })}
        />
    )
  }
}

正确的做法是在组件中单独定义函数, 将函数绑定给事件:

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  setInputValue = e => {
    this.setState({ inputValue: e.target.value })
  }
  render() {
    return (
      <input value={this.state.inputValue} onChange={this.setInputValue} />
    )
  }
}

在构造函数中进行函数this绑定

在类组件中如果使用 fn() {} 这种方式定义函数, 函数 this 默认指向 undefined. 也就是说函数内部的 this 指向需要被更正.

可以在构造函数中对函数的 this 进行更正, 也可以在行内进行更正, 两者看起来没有太大区别, 但是对性能的影响是不同的

export default class App extends React.Component {
   constructor() {
    super()
     // 方式一
     // 构造函数只执行一次, 所以函数 this 指向更正的代码也只执行一次.
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    console.log(this)
  }
  render() {
    // 方式二
    // 问题: render 方法每次执行时都会调用 bind 方法生成新的函数实例.
    return <button onClick={this.handleClick.bind(this)}>按钮</button>
  }
}

类组件中的箭头函数

在类组件中使用箭头函数不会存在 this 指向问题, 因为箭头函数本身并不绑定 this。

export default class App extends React.Component {
  handleClick = () => console.log(this)
  render() {
    return <button onClick={this.handleClick}>按钮</button>
  }
}

箭头函数在 this 指向问题上占据优势, 但是同时也有不利的一面.

当使用箭头函数时, 该函数被添加为类的实例对象属性, 而不是原型对象属性. 如果组件被多次重用, 每个组件实例对象中都将会有一个相同的函数实例, 降低了函数实例的可重用性造成了资源浪费.

综上所述, 更正函数内部 this 指向的最佳做法仍是在构造函数中使用 bind 方法进行绑定

优化条件渲染

频繁的挂载和卸载组件是一项耗性能的操作, 为了确保应用程序的性能, 应该减少组件挂载和卸载的次数.

在 React 中我们经常会根据条件渲染不同的组件. 条件渲染是一项必做的优化操作。

function App() {
  if (true) {
    return (
      <>
        <AdminHeader />
        <Header />
        <Content />
      </>
    )
  } else {
    return (
      <>
        <Header />
        <Content />
      </>
    )
  }
}

在上面的代码中, 当渲染条件发生变化时, React 内部在做 Virtual DOM 比对时发现, 刚刚第一个组件是 AdminHeader, 现在第一个组件是 Header, 刚刚第二个组件是 Header, 现在第二个组件是 Content, 组件发生了变化, React 就会卸载 AdminHeader、Header、Content, 重新挂载 Header 和 Content, 这种挂载和卸载就是没有必要的。

function App() {
  return (
    <>
      {true && <AdminHeader />}
      <Header />
      <Content />
    </>
  )
}

避免使用内联样式属性

当使用内联 style 为元素添加样式时, 内联 style 会被编译为 JavaScript 代码, 通过 JavaScript 代码将样式规则映射到元素的身上, 浏览器就会花费更多的时间执行脚本和渲染 UI, 从而增加了组件的渲染时间。

function App() {
  return <div style={{ backgroundColor: "skyblue" }}>App works</div>
}

避免重复无限渲染

当应用程序状态发生更改时, React 会调用 render 方法, 如果在 render 方法中继续更改应用程序状态, 就会发生 render 方法递归调用导致应用报错.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三"}
  }
  render() {
    this.setState({name: "李四"})
    return <div>{this.state.name}</div>
  }
}

与其他生命周期函数不同, render 方法应该被作为纯函数. 这意味着, 在 render 方法中不要做以下事情, 比如不要调用 setState 方法, 不要使用其他手段查询更改原生 DOM 元素, 以及其他更改应用程序的任何操作. render 方法的执行要根据状态的改变, 这样可以保持组件的行为和渲染方式一致.

避免数据结构突变

组件中 props 和 state 的数据结构应该保持一致, 数据结构突变会导致输出不一致.

import React, { Component } from "react"

export default class App extends Component {
  constructor() {
    super()
    this.state = {
      employee: {
        name: "张三",
        age: 20
      }
    }
  }
  render() {
    const { name, age } = this.state.employee
    return (
      <div>
        {name}
        {age}
        <button
          onClick={() =>
            this.setState({
              ...this.state,
              employee: {
                ...this.state.employee,
                age: 30
              }
            })
          }
        >
          change age
        </button>
      </div>
    )
  }
}

到此这篇关于React 组件性能最佳优化实践分享的文章就介绍到这了,更多相关React 组件性能优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解使用React.memo()来优化函数组件的性能

    React核心开发团队一直都努力地让React变得更快.在React中可以用来优化组件性能的方法大概有以下几种: 组件懒加载(React.lazy(...)和<Suspense />) Pure Component shouldComponentUpdate(...){...}生命周期函数 本文还会介绍React16.6加入的另外一个专门用来优化函数组件(Functional Component)性能的方法: React.memo. 无用的渲染 组件是构成React视图的一个基本单元.有些组件

  • React函数式组件的性能优化思路详解

    优化思路 主要优化的方向有2个: 减少重新 render 的次数.因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction. 减少计算的量.主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用. 在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate和 PureComponent 那么在函数式组件中,我们怎么做性能

  • 浅谈React组件之性能优化

    高德纳: "我们应该忘记忽略很小的性能优化,可以说97%的情况下,过早的优化是万恶之源,而我们应该关心对性能影响最关键的另外3%的代码." 不要将性能优化的精力浪费在对整体性能提高不大的代码上,而对性能有关键影响的部分,优化并不嫌早.因为,对性能影响最关键的部分,往往涉及解决方案核心,决定整体的架构,将来要改变的时候牵扯更大. 1. 单个React组件的性能优化 React利用Virtual DOM来提升渲染性能,虽然每一次页面更新都是最组件的从新渲染,但是并不是将之前的渲染内容全部抛

  • React 组件性能最佳优化实践分享

    目录 React 组件性能优化最佳实践 组件卸载前进行清理操作 类组件使用纯组件PureComponent 什么是纯组件 什么是浅层比较 shouldComponentUpdate 纯函数组件使用React.memo优化性能 memo 基本使用 memo 传递比较逻辑 使用组件懒加载 路由组件懒加载 根据条件进行组件懒加载(适用于组件不会随条件频繁切换) 使用Fragment 避免额外标记 不要使用内联函数定义 在构造函数中进行函数this绑定 类组件中的箭头函数 优化条件渲染 避免使用内联样式

  • 浅谈React + Webpack 构建打包优化

    本文介绍了React + Webpack 构建打包优化,分享给大家,具体如下: 使用 babel-react-optimize对 React 代码进行优化 检查没有使用的库,去除 import 引用 按需打包所用的类库,比如 lodash . echart 等 lodash 可以采用babel-plugin-lodash进行优化. 需要注意的是 在 babel-react-optimize 中使用了 babel-plugin-transform-react-remove-prop-types 这

  • 详解React 代码共享最佳实践方式

    任何一个项目发展到一定复杂性的时候,必然会面临逻辑复用的问题.在React中实现逻辑复用通常有以下几种方式:Mixin.高阶组件(HOC).修饰器(decorator).Render Props.Hook.本文主要就以上几种方式的优缺点作分析,帮助开发者针对业务场景作出更适合的方式. Mixin 这或许是刚从Vue转向React的开发者第一个能够想到的方法.Mixin一直被广泛用于各种面向对象的语言中,其作用是为单继承语言创造一种类似多重继承的效果.虽然现在React已将其放弃中,但Mixin的

  • react后台系统最佳实践示例详解

    目录 一.中后台系统的技术栈选型 1. 要做什么 2. 要求 3. 技术栈怎么选 二.hooks时代状态管理库的选型 context redux recoil zustand MobX 三.hooks的使用问题与解决方案 总结 一.中后台系统的技术栈选型 本文主要讲三块内容:中后台系统的技术栈选型.hooks时代状态管理库的选型以及hooks的使用问题与解决方案. 1. 要做什么 我们的目标是搭建一个适用于公司内部中后台系统的前端项目最佳实践. 2. 要求 由于业务需求比较多,一名开发人员需要负

  • 使用store来优化React组件的方法

    在使用 React 编写组件的时候,我们常常会碰到两个不同的组件之间需要共享状态情况,而通常的做法就是提升状态到父组件.但是这样做会有一个问题,就是尽管只有两个组件需要这个状态,但是因为把状态提到了父组件,那么在状态变化的时候,父组件以及其下面的所有子组件都会重新 render,如果你的父组件比较复杂,包含了其他很多子组件的话,就有可能引起性能问题. Redux 通过把状态放在全局的 store 里,然后组件去订阅各自需要的状态,当状态发生变化的时候,只有那些订阅的状态发生变化的组件才重新 r

  • Asp.net 网站性能优化二则分享

    随着后端优化空间越来越小,现在越来越多的网站更注重前端性能的优化,就是浏览器,http层面的优化,这里写两点最简单最有效的 asp.net网站优化技巧. 了解常见的网站性能优化技巧 首先我们要学一些优化网站性能和体验的理论和基础知识,雅虎已经帮我们总结好了,详见参考链接中的几篇文章,有好心人已经给翻译成中文了.这里面提到的一些技巧可操作性都很强,建议每一个网站开发人员都仔细学习并进行实践,可以说不了解这里面说的优化技巧,提升网站前端性能就无从谈起. 诊断网站性能 要想优化网站的前端性能,首先要查

  • 编写React组件项目实践分析

    当我刚开始写React的时候,我看过很多写组件的方法.一百篇教程就有一百种写法.虽然React本身已经成熟了,但是如何使用它似乎还没有一个"正确"的方法.所以我(作者)把我们团队这些年来总结的使用React的经验总结在这里.希望这篇文字对你有用,不管你是初学者还是老手. 开始前: 我们使用ES6.ES7语法如果你不是很清楚展示组件和容器组件的区别,建议您从阅读这篇文章开始如果您有任何的建议.疑问都清在评论里留言 基于类的组件 现在开发React组件一般都用的是基于类的组件.下面我们就来

  • React 条件渲染最佳实践小结(7种)

    在 React 中,条件渲染可以通过多种方式,不同的使用方式场景取决于不同的上下文. 在本文中,我们将讨论所有可用于为 React 中的条件渲染编写更好的代码的方法. 条件渲染在每种编程语言(包括 javascript)中都是的常见功能. 在 javascript 中,我们通常使用if else 语句,switch case语句和三元运算符编写条件渲染. 以上所有这些方法都适用于 React. 但是问题是,我们如何才能有效地使用它们? 像你知道的那样,React 具有 JSX 标记,通常我们需要

  • Cookbook组件形式:优化 Vue 组件的运行时性能

    Vue 2.0 在发布之初,就以其优秀的运行时性能著称,你可以通过这个第三方 benchmark来对比其他框架的性能.Vue 使用了 Virtual DOM 来进行视图渲染,当数据变化时,Vue 会对比前后两棵组件树,只将必要的更新同步到视图上. Vue 帮我们做了很多,但对于一些复杂场景,特别是大量的数据渲染,我们应当时刻关注应用的运行时性能. 本文仿照Vue Cookbook组织形式,对优化 Vue 组件的运行时性能进行阐述. 基本的示例 在下面的示例中,我们开发了一个树形控件,支持基本的树

随机推荐