react-native性能优化与重绘机制

减少重绘和并发执行

实现

  1. 减少不必要的setState
    setState是用来改变数据,触发视图更新的方法,调用setState会触发组件重绘。在复杂组件中setState会触发整个组件包括其子组件的重绘,这个是导致渲染进程被阻塞的主要原因。因此,应尽量细化组件,在必要的子组件中执行setState,减少在复杂组件中setState带来的重绘成本。
  2. setNativeProps更新属性
    用原生属性更新setNativeProps的方法去替代setState。一些样式属性上的切换和变化,可以用setNativeProps去更新,从而减少重绘。
  3. shouldComponentUpdate减少不必要的重绘
    在组件shouldComponentUpdate周期,即重绘render前,判断组件state和props的数据变化,没有变化则不执行重绘。Immutable.js判断复合类型的变化
  4. PureComponent组件
    组件的setState如果只有非对象数据类型的变化,可以用PureComponent,不会根据父组件的重绘而导致自身重绘。
  5. 减少动画时执行阻塞
    组件初始化后,利用InteractionManager使数据请求在动画完成后再执行,减少js线程和掉帧现象,避免切换组件时动画卡顿和延迟,但是同时会延迟了数据请求,会导致再次渲染。
  6. 提前获取数据,减少渲染
    组件渲染慢的问题,像个人中心,好友列表等,在进入页面前先把数据加载完毕,在组件初始化的时候赋值state数据,使组件先获得数据,只渲染一次。(若在组件渲染完之后,再获取数据,会再次渲染)与第5点是矛盾的,可以根据进入导航的交互体验去取舍。

组件生命周期及重绘机制

组件的三种状态及生命周期函数:

  1. 挂载阶段 Mounting
    • componentWillMount:组件挂载之前执行,在render之前调用
    • componentDidMount: 组件渲染完成,在所有子组件都render完之后调用
  2. 组件变化 Updating
    • componentWillUpdate:组件将要重新渲染
    • componentDidUpdate:组件重新渲染完成
  3. 卸载阶段 UnMounting
    • componentWillUnmount: 卸载组件
  4. 两种特殊状态的处理函数

    • componentWillRecevieProps:组件将要接收新的props时执行

    • shouldComponentUpdate(nextProps, nextState):判断组件是否应该重新渲染,默认是true

      • setState执行时,一般会触发组件视图重绘。
        • 前后不改变state值 和 无数据交换的父组件的重渲染 的 setState 都会导致组件的重复渲染
        • 组件的功能细分与dom差异化重绘成本有关
        • 重绘步骤
          1. react重新构建虚拟dom树。
          2. 与上一个虚拟dom树对比diff,得出dom结构的差异。
          3. 对发生变化的组件进行重绘。
      • 判断setState没有发生变化时,执行return false可阻止不必要的渲染
      • Immutable.is() / lodash _.isEqual() 判断复合类型的数据变化

复合类型的数据变化比较

Immutable.is()

  • 实现

    1
    2
    3
    4
    5
    6
    import { is } from 'immutable';

    shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
    return !(this.props === nextProps || is(this.props, nextProps)) ||
    !(this.state === nextState || is(this.state, nextState));
    }
  • 性能
    Immutable使用Structural Sharing(结构共享)比较的是两个对象的 hashCode 或 valueOf(对于 JavaScript 对象)。由于 immutable 内部使用了 Trie 数据结构来存储,只要两个对象的 hashCode 相等,值就是一样的。

    • 减少内存
    • 避免了深度遍历比较,便于比较复杂数据

lodash _.isEqual()

  • 实现

    1
    2
    3
    4
    5
    6
    shouldComponentUpdate (nextProps, nextState) {
    if (_.isEqual(this.state, nextState) && _.isEqual(this.props, nextProps)) {
    return false
    }
    return true
    }
  • lodash _isEqual() 思路

    • 同数据类型

      1. 数组 equalArrays

        • 数组长度
        • set关联
      2. 对象 equalObject

        • key属性名
        • value值
        • constructor是否相同
      3. 其他类型 equalByTag

        • Buffer
        • Boolean、Date、Number,通过+value转化为0、1比较
        • error 比较error.name && error.message
        • regExp、string,转化为string比较
        • map、set,转化为array,equalArrays
    • 不同类型 -> false
  • 性能
    _isEqual 对象层级越深,越耗时

diff算法

  • tree diff
    • dom tree分层级
    • 稳定dom的结构有利于提高新能,应尽量减少dom频繁的移除或添加
  • component diff

    • 同一类型组件
      • v-dom变化 tree diff
      • v-dom不变,使用shouldComponentUpdate判断是否进行重绘,提高性能
    • 不同类型组件 dirty component,替换整个组件下的子节点
  • element diff

    • 插入节点、移动节点、移除节点
    • 同一层级的子节点添加唯一key值进行区分,通过key值判断集合中是否存在相同的节点,以此判断对节点的更新:移动或增删,是否可复用元素
    • 避免大量节点拖拽排序

资料
资料2

触发重绘步骤

触发重绘步骤

资料
react+redux+immutablejs

减少新建变量

事件绑定this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
handleClick = () => { // 属性初始化
console.log('this is:', this);
}
<button onClick={this.handleClick}>btn1</button> // 每次render只执行一次

handleClick (id, e) {
console.log('this is:', this); // 若不绑定this,会返回undefined
}
<button onClick={(e) => this.handleClick(id, e)}>btn2</button> // 每次渲染都创建新的的箭头函数,可用作事件回调传参,但不建议用作props传入组件中,会带来不必要的渲染
<button onClick={this.handleClick.bind(this, id)}>btn3</button> // 每次render重新执行

<Foo style={{ color: "red" }}/> // 每一次渲染都被认为 props 发生变化
const fooStyle = { color: "red" }
<Foo style={btnStyle}/> // 同一个引用,props没有发生变化

组件更新优化

  1. 单个组件更新:避免父节点类型随意更改
  2. 组件列表,key优化:key是唯一且稳定不变的,避免用数组index作为key

redux性能优化:reselect

实现原理:只要相关的状态不变,就直接用上一次缓存的结果
资料