react问题解决的一些方法

原文连接: https://segmentfault.com/a/1190000007811296?utm_source=tuicool&utm_medium=referraljavascript

初学者对React可能满怀期待,以为React可能完爆其它一切框架,甚至不切实际地认为React可能连原生的渲染都能完爆——对框架的狂热确实会出现这样的不切实际的期待。让咱们来看看React的官方是怎么说的。React官方文档在Advanced Performanec这一节,这样写道:

One of the first questions people ask when considering React for a project is whether their application will be as fast and responsive as an equivalent non-React versionhtml

显然React本身也其实只是想尽可能达到跟非React版本至关的性能。


java

你所不知道的render

react的组件渲染分为初始化渲染和更新渲染。
在初始化渲染的时候会调用根组件下的全部组件的render方法进行渲染,以下图(绿色表示已渲染,这一层是没有问题的):react

图片描述

可是当咱们要更新某个子组件的时候,以下图的绿色组件(从根组件传递下来应用在绿色组件上的数据发生改变):git

图片描述

咱们的理想状态是只调用关键路径上组件的render,以下图:es6

图片描述

可是react的默认作法是调用全部组件的render,再对生成的虚拟DOM进行对比,如不变则不进行更新。这样的render和虚拟DOM的对比明显是在浪费,以下图(黄色表示浪费的render和虚拟DOM对比)github

图片描述

 

Tips:segmentfault

  • 拆分组件是有利于复用和组件优化的。数据结构

  • 生成虚拟DOM并进行比对发生在render()后,而不是render()前。app

更新阶段的生命周期

  • componentWillReceiveProps(object nextProps):当挂载的组件接收到新的props时被调用。此方法应该被用于比较this.props 和 nextProps以用于使用this.setState()执行状态转换。(组件内部数据有变化,使用state,可是在更新阶段又要在props改变的时候改变state,则在这个生命周期里面)

  • shouldComponentUpdate(object nextProps, object nextState): -boolean 当组件决定任何改变是否要更新到DOM时被调用。做为一个优化实现比较this.props 和 nextProps 、this.state 和 nextState ,若是React应该跳过更新,返回false。

  • componentWillUpdate(object nextProps, object nextState):在更新发生前被当即调用。你不能在此调用this.setState()

  • componentDidUpdate(object prevProps, object prevState): 在更新发生后被当即调用。(能够在DOM更新完以后,作一些收尾的工做)


Tips:

  • React的优化是基于shouldComponentUpdate的,该生命周期默认返回true,因此一旦prop或state有任何变化,都会引发从新render。

 

shouldComponentUpdate

react在每一个组件生命周期更新的时候都会调用一个shouldComponentUpdate(nextProps, nextState)函数。它的职责就是返回true或false,true表示须要更新,false表示不须要,默认返回为true,即使你没有显示地定义 shouldComponentUpdate 函数。这就不难解释上面发生的资源浪费了。

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

图片描述

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

  • C1根节点,绿色SCU (true),表示须要更新,而后vDOMEq红色,表示虚拟DOM不一致,须要更新。

  • C2节点,红色SCU (false),表示不须要更新,因此C4,C5均再也不进行检查

  • C3节点同C1,须要更新

  • C6节点,绿色SCU (true),表示须要更新,而后vDOMEq红色,表示虚拟DOM不一致,更新DOM。

  • C7节点同C2

  • C8节点,绿色SCU (true),表示须要更新,而后vDOMEq绿色,表示虚拟DOM一致,不更新DOM。

带坑的写法:

  • {...this.props} (不要滥用,请只传递component须要的props,传得太多,或者层次传得太深,都会加剧shouldComponentUpdate里面的数据比较负担,所以,请慎用spread attributes(<Component {...props} />))。

  • ::this.handleChange()。(请将方法的bind一概置于constructor)

  • this.handleChange.bind(this,id)

  • 复杂的页面不要在一个组件里面写完。

  • 请尽可能使用const element

  • map里面添加key,而且key不要使用index(可变的)。具体可参考使用Perf工具研究React Key对渲染的影响

  • 尽可能少用setTimeOut或不可控的refs、DOM操做。

  • propsstate的数据尽量简单明了,扁平化。

  • 使用return null而不是CSS的display:none来控制节点的显示隐藏。保证同一时间页面的DOM节点尽量的少。

性能检测工具

React官方提供的:React.addons.Perf

react官方提供一个插件React.addons.Perf能够帮助咱们分析组件的性能,以肯定是否须要优化。
打开console面板,先输入Perf.start()执行一些组件操做,引发数据变更,组件更新,而后输入Perf.stop()。(建议一次只执行一个操做,好进行分析)
再输入Perf.printInclusive查看全部涉及到的组件render,以下图(官方图片):
Flfo-tdhVWQNu3Qou1bPgIlHFLln

或者输入Perf.printWasted()查看下不须要的的浪费组件render,以下图(官方图片): 
Fpcch1iZkcJU9U-mlUxjnX9lcO9S

优化前:
FuX9A-2VfmgFMDycQYvtnR1ovBEb
优化后:
Fi4w1W_Fq4A3eUdsv_0U67Z5WZ8N

其余的检测工具

react-perf-tool为React应用提供了一种可视化的性能检测方案,该工程一样是基于React.addons,可是使用图表来显示结果,更加方便。
图片描述

 

React官方的解决方案

PureRenderMixin(es5)

复制代码
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
  mixins: [PureRenderMixin],

  render: function() {
    return <div className={this.props.className}>foo</div>;
  }
});
复制代码

Shallow Compare (es6)

复制代码
var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}
复制代码

es7装饰器的写法:

复制代码
import pureRender from "pure-render-decorator"
...

@pureRender
class Person  extends Component {
  render() {
    console.log("我re-render了");
    const {name,age} = this.props;

      return (
        <div>
          <span>姓名:</span>
          <span>{name}</span>
          <span> age:</span>
          <span>{age}</span>
        </div>
      )
  }
}
复制代码

pureRender很简单,就是把传进来的component的shouldComponentUpdate给重写掉了,原来的shouldComponentUpdate,不管怎样都是return ture,如今不了,我要用shallowCompare比一比,shallowCompare代码及其简单,以下:

function shallowCompare(instance, nextProps, nextState) {
  return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}

缺点

shallowEqual其实只比较props的第一层子属性是否是相同,若是props是以下

{
  detail: { name: "123", age: "123" } }

他只会比较props.detail ===nextProps.detail,致使在传入复杂的数据的状况下,优化失效。

补充(4.25)

React在15.3.0里面加入了了React.PureComponent - 一个可继承的新的基础类, 用来替换react-addons-pure-render-mixin。用法:

复制代码
class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

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

在ES6里面写起来简直爽歪歪,惋惜同样只支持浅比较。

immutable.js

咱们也能够在 shouldComponentUpdate() 中使用使用 deepCopy 和 deepCompare 来避免无必要的 render(),但 deepCopy 和 deepCompare 通常都是很是耗性能的。

Immutable Data 就是一旦建立,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操做都会返回一个新的 Immutable 对象。

Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据建立新数据时,要保证旧数据同时可用且不变。同时为了不 deepCopy 把全部节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即若是对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:

FpPDekdncL-A9N69NnI3-O8CgGQ8

Immutable 则提供了简洁高效的判断数据是否变化的方法,只需 === 和 is 比较就能知道是否须要执行 render(),而这个操做几乎 0 成本,因此能够极大提升性能。修改后的 shouldComponentUpdate 是这样的:

复制代码
import { is } from 'immutable';

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (!is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}
复制代码

react-immutable-render-mixin

这是一个facebook/immutable-js的react pure render mixin 的库,能够简化不少写法。
使用react-immutable-render-mixin能够实现装饰器的写法。

复制代码
import React from 'react';
import { immutableRenderDecorator } from 'react-immutable-render-mixin';

@immutableRenderDecorator
class Test extends React.Component {
  render() {
    return <div></div>;
  }
}
复制代码

这里可参考个人另外一篇blog:使用immutable优化React

无状态组件

为了不必定程度的浪费,react官方还在0.14版本中加入了无状态组件
这种组件没有状态,没有生命周期,只是简单的接受 props 渲染生成 DOM 结构。无状态组件很是简单,开销很低,若是可能的话尽可能使用无状态组件。好比使用箭头函数定义:

// es6
const HelloMessage = (props) => <div> Hello {props.name}</div>;
render(<HelloMessage name="John" />, mountNode);

由于无状态组件只是函数,因此它没有实例返回,这点在想用 refs 获取无状态组件的时候要注意,参见DOM 操做。

高阶组件(接下来的方向)

大部分使用mixin和class extends的地方,高阶组件都是更好的方案——毕竟组合优于继承

参考文章

使用ES6编写React应用(4):使用高阶组件替代Mixins
Mixin 已死,Composition 万岁

React同构直出(接下来方向)

同构基于服务端渲染,却不止是服务端渲染。

React在减小重复渲染方面确实是有一套独特的处理办法,那就是virtual DOM,但显示在首次渲染的时候React绝无可能超越原生的速度。所以,咱们在作优化的时候,接下来能够作的事情就是:首屏时间可能会比较原生的慢一些,但能够尝试用React Server Render (又称Isomorphic)去提升效率

相关文章
相关标签/搜索