react性能优化

初识react只是为了尽快完成项目,后期进行代码审查时候发现有不少地方须要优化,所以作了个小结。html

  1. Code Splitting
  2. shouldComponentUpdate避免重复渲染
  3. 使用不可突变数据结构
  4. 组件尽量的进行拆分、解耦
  5. 列表类组件优化
  6. bind函数优化
  7. 不要滥用props
  8. ReactDOMServer进行服务端渲染组件

Code Splitting

Code Splitting 能够帮你“懒加载”代码,若是你没办法直接减小应用的体积,那么不妨尝试把应用从单个 bundle 拆分红单个 bundle + 多份动态代码的形式。
webpack提供三种代码分离方法,详情见webpack官网node

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

在此,主要了解一下第三种动态导入的方法。
一、例如能够把下面的import方式react

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

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

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

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

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-componentgithub

shouldComponentUpdate避免重复渲染

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

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

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

clipboard.png

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

  • C1根节点,绿色SCU、红色vDOMEq,表示须要更新。
  • C2节点,红色SCU,表示不须要更新,同时C四、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 一直不会发生渲染。
避免此类问题最简单的方式是避免使用值可能会突变的属性或状态,如:

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

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

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

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

三、使用不可突变数据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种方式:
一、constructor绑定

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

二、使用时绑定

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

三、使用箭头函数

<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挂起。

clipboard.png

相关文章
相关标签/搜索