浅谈react性能优化的方法

React性能优化思路

软件的性能优化思路就像生活中去看病,大致是这样的:

使用工具来分析性能瓶颈(找病根)

尝试使用优化技巧解决这些问题(服药)

使用工具测试性能是否确实有提升(疗效确认)

初识react只是为了尽快完成项目,后期进行代码审查时候发现有很多地方需要优化,因此做了个小结。

  • Code Splitting
  • shouldComponentUpdate避免重复渲染
  • 使用不可突变数据结构
  • 组件尽可能的进行拆分、解耦
  • 列表类组件优化
  • bind函数优化
  • 不要滥用props
  • ReactDOMServer进行服务端渲染组件

Code Splitting

Code Splitting 可以帮你“懒加载”代码,如果你没办法直接减少应用的体积,那么不妨尝试把应用从单个 bundle 拆分成单个 bundle + 多份动态代码的形式。

webpack提供三种代码分离方法,详情见webpack官网

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 SplitChunks 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

在此,主要了解一下第三种动态导入的方法。

1、例如可以把下面的import方式

import { add } from './math';
console.log(add(16, 26));

改写成动态 import 的形式,让首次加载时不去加载 math 模块,从而减少首次加载资源的体积。

import("./math").then(math => {
 console.log(math.add(16, 26));
});

2、例如引用react的高阶组件react-loadable进行动态import。

import Loadable from 'react-loadable';
import Loading from './loading-component';

const LoadableComponent = Loadable({
 loader: () => import('./my-component'),
 loading: Loading,
});

export default class App extends React.Component {
 render() {
  return <LoadableComponent/>;
 }
}

上面的代码在首次加载时,会先展示一个 loading-component,然后动态加载 my-component 的代码,组件代码加载完毕之后,便会替换掉 loading-component

shouldComponentUpdate避免重复渲染

当一个组件的props或者state改变时,React通过比较新返回的元素和之前渲染的元素来决定是否有必要更新实际的DOM。当他们不相等时,React会更新DOM。

在一些情况下,你的组件可以通过重写这个生命周期函数shouldComponentUpdate来提升速度, 它是在重新渲染过程开始前触发的。 这个函数默认返回true,可使React执行更新。

为了进一步说明问题,引用官网的图解释一下,如下图( SCU表示shouldComponentUpdate,绿色表示返回true(需要更新),红色表示返回false(不需要更新);vDOMEq表示虚拟DOM比对,绿色表示一致(不需要更新),红色表示发生改变(需要更新)):

根据渲染流程,首先会判断shouldComponentUpdate(SCU)是否需要更新。如果需要更新,则调用组件的render生成新的虚拟DOM,然后再与旧的虚拟DOM对比(vDOMEq),如果对比一致就不更新,如果对比不同,则根据最小粒度改变去更新DOM;如果SCU不需要更新,则直接保持不变,同时其子元素也保持不变。

  • C1根节点,绿色SCU、红色vDOMEq,表示需要更新。
  • C2节点,红色SCU,表示不需要更新,同时
  • C4、C5作为其子节点也不需要检查更新。
  • C3节点,绿色SCU、红色vDOMEq,表示需要更新。
  • C6节点,绿色SCU、红色vDOMEq,表示需要更新。
  • C7节点,红色SCU,表示不需要更新。
  • C8节点,绿色SCU,表示React需要渲染这个组件;绿色vDOMEq,表示虚拟DOM一致,不更新DOM。

因此,我们可以通过根据自己的业务特性,重载shouldComponentUpdate,只在确认真实DOM需要改变时,再返回true。一般的做法是比较组件的props和state是否真的发生变化,如果发生变化则返回true,否则返回false。引用官网的案例。

class CounterButton extends React.Component {
 constructor(props) {
  super(props);
  this.state = {count: 1};
 }

 shouldComponentUpdate(nextProps, nextState) {
  if (this.props.color !== nextProps.color) {
   return true;
  }
  if (this.state.count !== nextState.count) {
   return true;
  }
  return false;
 }

 render() {
  return (
   <button
    color={this.props.color}
    onClick={() => this.setState(state => ({count: state.count + 1}))}>
    Count: {this.state.count}
   </button>
  );
 }
}

在以上代码中,shouldComponentUpdate只检查props.color和state.count的变化。如果这些值没有变化,组件就不会更新。当你的组件变得更加复杂时,你可以使用类似的模式来做一个“浅比较”,用来比较属性和值以判定是否需要更新组件。这种模式十分常见,因此React提供了一个辅助对象来实现这个逻辑 - 继承自React.PureComponent。

大部分情况下,你可以使用React.PureComponent而不必写你自己的shouldComponentUpdate,它只做一个浅比较。但是当你比较的目标为引用类型数据,浅比较会忽略属性或状态突变的情况,此时你不能使用它,此时你需要关注下面的不可突变数据。

附:数据突变(mutated)是指变量的引用没有改变(指针地址未改变),但是引用指向的数据发生了变化(指针指向的数据发生变更)。例如const x = {foo:'foo'}。x.foo='none' 就是一个突变。

使用不可突变数据结构

引用官网中的例子解释一下突变数据产生的问题。例如,假设你想要一个ListOfWords组件来渲染一个逗号分隔的单词列表,并使用一个带了点击按钮名字叫WordAdder的父组件来给子列表添加一个单词。以下代码并不正确:

class ListOfWords extends React.PureComponent {
 render() {
  return <div>{this.props.words.join(',')}</div>;
 }
}

class WordAdder extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   words: ['marklar']
  };
  this.handleClick = this.handleClick.bind(this);
 }

 handleClick() {
  // 这段内容将会导致代码不会按照你预期的结果运行
  const words = this.state.words;
  words.push('marklar');
  this.setState({words: words});
 }

 render() {
  return (
   <div>
    <button onClick={this.handleClick} />
    <ListOfWords words={this.state.words} />
   </div>
  );
 }
}

导致代码无法正常工作的原因是 PureComponent 仅仅对 this.props.words的新旧值进行“浅比较”。在words值在handleClick中被修改之后,即使有新的单词被添加到数组中,但是this.props.words的新旧值在进行比较时是一样的(引用对象比较),因此 ListOfWords 一直不会发生渲染。
避免此类问题最简单的方式是避免使用值可能会突变的属性或状态,如:

1、数组使用concat,对象使用Object.assign()

handleClick() {
 this.setState(prevState => ({
  words: prevState.words.concat(['marklar'])
 }));
}
// 假设我们有一个叫colormap的对象,下面方法不污染原始对象
function updateColorMap(colormap) {
 return Object.assign({}, colormap, {right: 'blue'});
}

2、ES6支持数组或对象的spread语法

handleClick() {
 this.setState(prevState => ({
  words: [...prevState.words, 'marklar'],
 }));
};
function updateColorMap(colormap) {
 return {...colormap, right: 'blue'};
}

3、使用不可突变数据immutable.js

immutable.js使得变化跟踪很方便。每个变化都会导致产生一个新的对象,因此我们只需检查索引对象是否改变。

const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false

在这个例子中,x突变后返回了一个新的索引,因此我们可以安全的确认x被改变了。

不可突变的数据结构帮助我们轻松的追踪对象变化,从而可以快速的实现shouldComponentUpdate。

具体如何使用可参考下面文章:Immutable 详解及 React 中实践

组件尽可能的进行拆分、解耦

组件尽可能的细分,比如一个input+list组件,可以将list分成一个PureComponent,只在list数据变化时更新。否则在input值变化页面重新渲染的时候,list也需要进行不必要的DOM diff。

列表类组件优化

key属性在组件类之外提供了另一种方式的组件标识。通过key标识,在组件发生增删改、排序等操作时,可以根据key值的位置直接调整DOM顺序,告诉React 避免不必要的渲染而避免性能的浪费。
例,对于一个基于排序的组件渲染:

var items = sortBy(this.state.sortingAlgorithm, this.props.items);
return items.map(function(item){
 return <img src={item.src} />
});

当顺序发生改变时,React 会对元素进行diff操作,并改img的src属性。显示,这样的操作效率是非常低的。这时,我们可以为组件添加一个key属性以唯一的标识组件:

return <img src={item.src} key={item.id} />

增加key后,React就不是diff,而是直接使用insertBefore操作移动组件位置,而这个操作是移动DOM节点最高效的办法。

bind函数

绑定this的方式:一般有下面3种方式:

1、constructor绑定

constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this); //构造函数中绑定
}
//然后可以
<p onClick={this.handleClick}>

2、使用时绑定

<p onClick={this.handleClick.bind(this)}>

3、使用箭头函数

<Test click={() => { this.handleClick() }}/>

以上三种方法,第一种最优。

因为第一种构造函数只在组件初始化的时候执行一次,

第二种组件每次render都会执行

第三种在每一次render时候都会生成新的箭头函数。例:Test组件的click属性是个箭头函数,组件重新渲染的时候Test组件就会

因为这个新生成的箭头函数而进行更新,从而产生Test组件的不必要渲染。

不要滥用props

props尽量只传需要的数据,避免多余的更新,尽量避免使用{...props}

ReactDOMServer进行服务端渲染组件

为了用户会更快速地看到完整渲染的页面,可以采用服务端渲染技术,在此了解一下ReactDOMServer

为了实现SSR,你可能会用nodejs框架(Express、Hapi、Koa)来启动一个web服务器,接着调用 renderToString 方法去渲染你的根组件成为字符串,最后你再输出到 response。

// using Express
import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";
app.get("/", (req, res) => {
 res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
 res.write("<div id='content'>");
 res.write(renderToString(<MyPage/>));
 res.write("</div></body></html>");
 res.end();
});

客户端使用render方法来生成HTML

import ReactDOM from 'react-dom';
import MyPage from "./MyPage";
ReactDOM.render(<MyPage />, document.getElementById('app'));

react性能检测工具

react16版本之前,我们可以使用react-addons-perf工具来查看,而在最新的16版本,我们只需要在url后加上?react_pref。
首先来了解一下react-addons-perf

react-addons-perf这是 React 官方推出的一个性能工具包,可以打印出组件渲染的时间、次数、浪费时间等。
简单说几个api,具体用法可参考官网

  • Perf.start() 开始记录
  • Perf.stop() 结束记录
  • Perf.printInclusive() 查看所有设计到的组件render
  • Perf.printWasted() 查看不需要的浪费组件render

再来了解一下,react16版本的方法,在url后加上?react_pref,就可以在chrome浏览器的performance,我们可以查看User Timeing来查看组件的加载时间。点击record开始记录,注意记录时长不要超过20s,否则可能导致chrome挂起。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • react性能优化达到最大化的方法 immutable.js使用的必要性

    一行代码胜过千言万语.这篇文章呢,主要讲述我一步一步优化react性能的过程,为什么要用immutable.js呢.毫不夸张的说.有了immutable.js(当然也有其他实现库)..才能将react的性能发挥到极致!要是各位看官用过一段时间的react,而没有用immutable那么本文非常适合你.那么我开始吧! 1.对于react的来说,如果父组件有多个子组件 想象一下这种场景,一个父组件下面一大堆子组件.然后呢,这个父组件re-render.是不是下面的子组件都得跟着re-render.可

  • 浅谈React组件之性能优化

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

  • 浅谈react性能优化的方法

    React性能优化思路 软件的性能优化思路就像生活中去看病,大致是这样的: 使用工具来分析性能瓶颈(找病根) 尝试使用优化技巧解决这些问题(服药) 使用工具测试性能是否确实有提升(疗效确认) 初识react只是为了尽快完成项目,后期进行代码审查时候发现有很多地方需要优化,因此做了个小结. Code Splitting shouldComponentUpdate避免重复渲染 使用不可突变数据结构 组件尽可能的进行拆分.解耦 列表类组件优化 bind函数优化 不要滥用props ReactDOMSe

  • 浅谈android性能优化之启动过程(冷启动和热启动)

    本文介绍了浅谈android性能优化之启动过程(冷启动和热启动) ,分享给大家,具体如下: 一.应用的启动方式 通常来说,启动方式分为两种:冷启动和热启动. 1.冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动. 2.热启动:当启动应用时,后台已有该应用的进程(例:按back键.home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热

  • 浅谈Android性能优化之内存优化

    1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

  • 浅谈PHP性能优化之php.ini配置

    内存 默认设置 memory_limit = 128M 单个进程可使用的内存最大值,这个值的设定可以从以下几点考虑: 应用的类型.如果是内存集中型应用,可增加该值: 单个 PHP 进程平均消耗的内存,该值可通过多次运行同一个脚本来计算平均值: 能负担多少个 php-fpm 进程:该值等于分配的总内存除以单个 PHP 进程平均消耗的内存: 文件上传 默认设置 file_uploads = On max_file_uploads = 20 upload_max_filesize = 2M max_e

  • 浅谈 Vue 项目优化的方法

    好久不写博文了,本文作为我使用半年 vue 框架的经验小结,随便谈谈,且本文只适用于 vue-cli 初始化的项目或依赖于 webpack 打包的项目. 前几天看到大家说 vue 项目越大越难优化,带来很多痛苦,这是避免不了的,问题终究要解决,框架的性能是没有问题的,各大测试网站都有相关数据.下面进入正题 基础优化 所谓的基础优化是任何 web 项目都要做的,并且是问题的根源.HTML,CSS,JS 是第一步要优化的点 分别对应到 .vue 文件内的,<template>,<style&

  • 浅谈Vue 性能优化之深挖数组

    背景 最近在用 Vue 重构一个历史项目,一个考试系统,题目量很大,所以核心组件的性能成为了关注点.先来两张图看下最核心的组件 Paper 的样式. 从图中来看,分为答题区与选择面板区. 稍微对交互逻辑进行下拆解: 答题模式与学习模式可以相互切换,控制正确答案显隐. 单选与判断题直接点击就记录答案正确性,多选是选择答案之后点击确定才能记录正确性. 选择面板则是记录做过的题目的情况,分为六种状态(未做过的,未做过且当前选择的,做错的,做错的且当前选择的,做对的,做对的且当前选择的),用不同的样式去

  • React性能优化系列之减少props改变的实现方法

    React性能优化的一个核心点就是减少render的次数.如果你的组件没有做过特殊的处理(SCU -- shouldComponentUpdate或使用PureComponent),那每次父组件render时,子组件就会跟着一起被重新渲染.通常一个复杂的子组件都会进行一些优化,比如:SCU 使用PureComponent组件.对于SCU基本上进行的也都是浅比较,深比较的代价太高. 对于这些被优化的子组件,我们要减少一些不必要的props改变:比如事件绑定.对于那些依赖于配置项的组件,我们更是减少

  • React 性能优化方法总结

    目录 前言 为什么页面会出现卡顿的现象? React 到底是在哪里出现了卡顿? React 有哪些场景会需要性能优化? 一:父组件刷新,而不波及子组件. 第一种:使用 PureComponent 第三种:函数组件如何判断props的变化的更新呢? 使用 React.memo函数 使用 React.useMemo来实现对子组件的缓冲 一:组件自己控制自己是否刷新 三:减少波及范围,无关刷新数据不存入state中 场景一:无意义重复调用setState,合并相关的state 场景二:和页面刷新没有相

  • 浅谈React中组件逻辑复用的那些事儿

    基本每个开发者都需要考虑逻辑复用的问题,否则你的项目中将充斥着大量的重复代码.那么 React 是怎么复用组件逻辑的呢?本文将一一介绍 React 复用组件逻辑的几种方法,希望你读完之后能够有所收获.如果你对这些内容已经非常清楚,那么略过本文即可. 我已尽量对文中的代码和内容进行了校验,但是因为自身知识水平限制,难免有错误,欢迎在评论区指正. 1. Mixins Mixins 事实上是 React.createClass 的产物了.当然,如果你曾经在低版本的 react 中使用过 Mixins,

  • 浅谈react+es6+webpack的基础配置

    这是模块化开发.主流框架和最新版的ECMAScript语法规范的一个小demo 准备工作 安装 nodeJs 首先进入node官网,去下载最新版的nodeJs webpack 安装webpack npm install webpack -g 参数-g表示全局安装webpack,你在cmd命令中哪个文件夹下都可以使用webpack的命令,如果不加-g的话,是只可以在你安装webpack的目录下使用webpack这个命令 webpack 也有一个 web 服务器 npm install webpac

随机推荐