React学习之前端开发性能优化

38次阅读

共计 5493 个字符,预计需要花费 14 分钟才能阅读完成。

1. 单个 react 组件性能优化

1.1 render里面尽量减少新建变量和 bind 函数的使用,尽量减少传递参数的数量

render 中绑定函数,无非就是下面三种:

render() {
  return (
    <div className='app'>
      <span onClick={this.handleClick}>1</span>
      <span onClick={this.handleClick.bind(this)}>2</span>
      <span onClick={()=>this.handleClick()}>3</span>
    </div>
  )
}

第一种是在构造函数中绑定 this,第二种是在render() 函数里面绑定 this,第三种就是使用箭头函数,上述方法都能实现this 的绑定。

但是哪一种方法的性能最好,是我们要考虑的问题。毫无疑问第一种的性能最好

第一种方法,构造函数每渲染一次便会执行一遍;

第二种方法,在每次 render() 的时候都会重新执行一遍函数;

第三种方法,每一次 render() 的时候,都会生成一个新的箭头函数,即使两个箭头函数的内容是一样的。

react判断是否需要进行 render浅层比较 ,简单来说就是通过=== 来判断的,如果 state 或者 prop 的类型是字符串或者数字,只要值相同,那么浅层比较就会认为其相同;

但是如果前者的类型是复杂的对象的时候,我们知道对象是引用类型,浅层比较只会认为这两个 prop 是不是同一个引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是两个不同的prop

举个例子:

当我们给组件 App 名为 styleprop赋值;

<App style={{color:"green"}}

使用这种方法,每一次渲染都会被认为是一个 style 这个 prop 发生了变化,因为每一次都会产生一个对象给style

如果想要让 react 渲染的时候认为前后对象类型 prop 相同,则必须要保证 prop 指向同一个 javascript 对象,改进如下:

const appStyle = {color: "red"}; // 这个初始化只执行一次,不要放在 render 中,可以放在构造函数中
<App style={appStyle} />

1.2 定制 shouldComponentUpdate 函数

生命周期函数 shouldComponentUpdate 是决定 react 组件什么时候能够重新渲染的函数,但是这个函数默认的实现方式就是简单的返回一个 true。也就是说,默认每次更新的时候都要调用所用的生命周期函数,包括render 函数,重新渲染。

看看下面这个例子:

class App extends React.Component {constructor(props) {super(props);
    this.state = {
      count = 2,
      name = 'apple',
    }
    this.handleClick = this.handleClick.bind(this);
    this.handleName = this.handleName.bind(this);
  }
  this.handleClick() {// ...}
  this.handleName() {// ...}
  render() {
    return (
      <div className='app'>
        <span> 数量,{this.state.count}</span>
        <button onClick={this.handleClick}> 改变数量 </button>
        <button onClick={this.handleName}> 改变名字 </button>
        <Child title={this.state.name}></Child>
      </div>
    );
  }
}


class Child extends React.Component {render() {console.log('render 了一次');
    return (<h3> 我想吃,{this.props.title}</h3>
    );
  }
}

我们写了两个组件,AppChild 组件,并写两个方法,一个改变 App 中的 count 的值,一个是改变 name,我们在Childrender中打印了每次是否执行。

不出意外,虽然 Child 组件里的 title 值没有改变,但是还是 render 了。

为了进一步优化这个问题,我们这样改 Child 组件:

class Child extends React.Component {shouldComponentUpdate(nextProps,nextState) {if(nextProps.title == this.props.title) {return false;}
    return true;
  }

  render() {console.log('render 了一次');
    return (<h3> 我想吃,{this.props.title}</h3>
    );
  }
}

只有当 Childtitle值发生改变的时候,组件才会去render

在最新的 react 中,react给我们提供了 React.PureComponent,官方也在早期提供了名为react-addons-pure-render-mixin 插件来重新实现 shouldComponentUpdate 生命周期方法。

class Child extends React.PureComponent {// shouldComponentUpdate(nextProps,nextState) {//   if(nextProps.title == this.props.title) {
  //     return false;
  //   }
  //   return true;
  // }

  render() {console.log('render 了一次');
    return (<h3> 我想吃,{this.props.title}</h3>
    );
  }
}

通过上述的方法的效果也是和我们先前定制 shouldComponentUpdate 的效果是一致的。

但是我们要注意的是,这里的 PureRender 是浅比较的,因为深比较的场景是相当昂贵的。所以我们要注意我们在 1.1 中说到的一些注意点:不要直接为 props 设置对象或者数组 不要将方法直接绑定在元素上,因为其实函数也是对象。

1.3 Immutable.js

javascript中的对象一般都是可变的,因为使用了引用赋值,新的对象简单的引用了原始对象,改变新对象将影响到原始对象。

举个例子:

student = {age : 1};
school = student;
school.age = 2;

当我们给 school.age 赋值后,会发现 student.a 也变成了 2,虽然我们可以通过深拷贝与浅拷贝解决这个问题,但是这样做非常的昂贵,对 cpu 和内存会造成浪费。

这里就需要用到 Immutable,通过Immutable 创建的 Immutable Data 一旦被创建,就不能再更改。对 Immutable 对象进行修改、添加或删除操作,都会返回一个新的 Immutable 对象。

下面是三个比较重要且用到的数据结构

  • Map:键值对集合,对应 Object,ES6 中也有专门的 Map 对象
  • List:有序可重复列表,对应于 Array
  • ArraySet:有序且不可重复的列表

我们可以看一个例子:

使用 Map 生成一个 immutable 对象:

import {Map, is} from 'immutable';

let a = Map({
  'name': 'apple',
  'list': Map({name: 'orange'})
})

let b = a.set('name','banana');

console.log(a.get('course') === b.get('course')); // 返回 true
console.log(a === b); // 返回 false

Immutable.is 比较的是两个对象的 hashCodevalueOf(对于 JavaScript 对象)。由于 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。这样的算法避免了深度遍历比较,性能非常好。

Immutable优点

  • 减少内存的使用
  • 并发安全
  • 降低项目的复杂度
  • 便于比较复杂数据,定制 shouldComponentUpdate 方便
  • 时间旅行功能
  • 函数式编程

Immutable缺点

  • 学习成本
  • 库的大小(建议使用seamless-immutable
  • 对现有项目入侵严重
  • 容易与原生的对象进行混淆

2. 多个 react 组件性能优化

react组件在装载过程中,react通过在 render 方法在内存中产生一个树形结构,树上的节点代表一个 react 组件或者原生的 Dom 元素,这个树形结构就是我们所谓的 Vitural Domreact 根据这个来渲染产生浏览器的 Dom 树。

react在更新阶段对比原有的 Vitural Dom 和新生成的 Vitural Dom,找出不同之处,在根据不同来渲染Dom 树。

react为了追求高性能,采用了时间复杂度为 O(N) 来比较两个属性结构的区别,因为要确切比较两个树形结构,需要通过O(N^3),这会降低性能。

  • 节点类型不同
// A 组件
<div>
  <Todos />
</div>

// B 组件
<span>
  <Todos />
</span>

我们想把 A 组件更新成 B 组件,react在做比较的时候,发现最外面的根结点完全不一样,直接销毁之前的 <div> 节点,包括里面的子节点也一并销毁,这是一个巨大的浪费,但是为了避免 O(N^3) 的时间复杂度,只能采用这种方式。

所以在开发过程中,我们应该尽量避免上面的情况,不要将包裹节点的类型随意改变。

  • 两个节点类型一样

这里包括两种情况,一种是节点是 Dom 类型,还有一种 react 组件。

对于 dom 类型,我们举个例子:

// A 组件
<div style={{color: 'red',fontSize:15}} className="welcome">
  Hello World!!!
</div>

// B 组件
<div style={{color: 'green',fontSize:15}} className="react">
  Good Bye!!!
</div>

上述 A 和 B 组件的区别是文字、classNamestyle中的 color 发生改变,因为 Dom 元素没变,React只会修改他变化的部分。

针对 react 组件类型,渲染无非就是再执行一遍组件实例的更新过程,最主要的就是定制shouldComponentUpdate

  • 多个子组件情况

例子一:

// A
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
  <TodoItem text="Third" complete={false} />
</ul>

从 A 变到 B,如果 shouldComponentUpdate 处理得当,我们只需要更新装载 third 的那 一次 就行。

我们来看看下一个例子:

// A
<ul>
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem text="Zero" complete={false} />
  <TodoItem text="First" complete={false} />
  <TodoItem text="Second" complete={false} />
</ul>

这里因为 react 是采用 O(n) 的时间复杂度,所以会依次将 textFirst的改为 ZerotextSecond改为 First,在最后再加上一个组件,textSecond。现存的两个的 text 的属性都被改变了,所以会依次渲染。

如果我们这里有 100 个实例,那么就会发生 100 次更新。

这里我们就要用到 Key

简单来说,其实这一个 Key 就是 react 组件的身份证号。

我们将上一个例子改成如下,就可以避免上面的问题了,react就能够知道其实 B 里面的第二个和第三个组件其实就是 A 中的第一个和第二个实例。

// A
<ul>
  <TodoItem key={1} text="First" complete={false} />
  <TodoItem key={2} text="Second" complete={false} />
</ul>

// B
<ul>
  <TodoItem key={0} text="Zero" complete={false} />
  <TodoItem key={1} text="First" complete={false} />
  <TodoItem key={2} text="Second" complete={false} />
</ul>

不过现在,react也会提醒我们不要忘记使用key,如果没有加,浏览器中会报错。

关于 key 的使用我们要注意的是,这个 key 值要稳定不变的,就如同 身份证号 对于我们是稳定不变的一样。

一个常见的错误就是,拿数组的的下标值去当做key,这个是很危险的,代码如下,我们一定要避免

<ul>
  {todos.map((item, index) => {
      <TodoItem
        key={index}
        text={item.text}
        completed={item.completed}
      >
    })
  }
</ul>

未完待续 …

正文完
 0