使用immutable优化React

React在减少重复渲染方面确实是有一套独特的处理办法,那就是虚拟DOM,但显然在首次渲染的时候React绝无可能超越原生的速度,或者一定能将其它的框架比下去。尤其是在优化前的React,每次数据变动都会执行render,大大影响了性能,特别是在移动端。

React 默认的渲染行为

初始化渲染

在初始化渲染时,我们需要渲染整个应用
(绿色 = 已渲染节点)


image.png

提出改变

我们想更新一部分数据。这些改变只和一个叶子节点相关(绿色的)

image.png

理想更新

我们只想渲染通向叶子节点的关键路径上的这几个节点(绿色的)


image.png

默认行为

如果你不告诉 React 别这样做,它便会如此
(橘黄色 = 浪费的渲染)

image.png

从上图可以看见,组件除了必要渲染的三个节点外,还渲染了其他不必要渲染的节点,这对性能是一个很大的浪费。如果对于复杂的页面,这将导致页面的整体体验效果非常差。因此要提高组件的性能,就应该想尽一切方法减少不必要的渲染。

React的生命周期

React的生命周期如下,还没熟悉的同学可以去熟悉一下。


image.png

shouldComponentUpdate

因为其中的 shouldComponentUpdate 是优化的关键。React的重复渲染优化的核心其实就是在shouldComponentUpdate里面做数据比较。在优化之前,shouldComponentUpdate是默认返回true的,这导致任何时候触发任何的数据变化都会使component重新渲染。这必然会导致资源的浪费和性能的低下——你可能会感觉比较原生的响应更慢。

React性能优化的关键在于shouldComponentUpdate

image.png

在上面的示例中,因为 C2 的 shouldComponentUpdate 返回 false,React 就不需要生成新的虚拟 DOM,也就不需要更新 DOM,注意 React 甚至不需要调用 C4 和 C5 的 shouldComponentUpdate

C1 和 C3 的 shouldComponentUpdate 返回 true,所以 React 需要向下到叶子节点检查它们,C6 返回 true,因为虚拟 DOM 不相等,需要更新 DOM。最后感兴趣的是 C8,对于这个节点,React 需要计算虚拟 DOM,但是因为它和旧的相等,所以不需要更新 DOM。

React.PureComponent

在传入组件的props和state只有一层时,我们可以直接使用 React.PureComponent,它会自动帮我们进行浅比较(shallow-compare),从而控制shouldComponentUpdate的返回值。

但是,当传入props或state不止一层,或者未array和object时,浅比较(shallow-compare)就失效了。当然我们也可以在 shouldComponentUpdate() 中使用使用 deepCopydeepCompare 来避免无必要的 render(),但 deepCopydeepCompare一般都是非常耗性能的。这个时候我们就需要 Immutable

Immutable

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

foo={a: 1}; 
bar=foo; 
bar.a=2

你会发现此时 foo.a 也被改成了 2。虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,Mutable 带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。

而Immutable 可以很好地解决这些问题。

什么是Immutable Data

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

可以看看下面这个经典的动画:

4039331718-56cc22613b287_articlex.gif

immutable.js

Immutable.js本质上是一个JavaScript的持久化数据结构的库 ,但是由于同期的React太火,并且和React在性能优化方面天衣无缝的配合,导致大家常常把它们两者绑定在一起。

Immutable.js是Facebook 工程师 Lee Byron 花费 3 年时间打造,但没有被默认放到 React 工具集里(React 提供了简化的 Helper)。它内部实现了一套完整的 Persistent Data Structure,且数据结构和方法非常丰富(完全不像JS出身的好不好)。像 Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce、find函数式操作方法。同时 API 也尽量与 Object 或 Array 类似。 Immutable.js 压缩后下载有 16K。

其中有 3 种最重要的数据结构说明一下:(Java 程序员应该最熟悉了)

  • Map:键值对集合,对应于 Object,ES6 也有专门的 Map 对象

  • List:有序可重复的列表,对应于 Array

  • Set:无序且不可重复的列表

简单示例


import { Map } from "immutable";
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50

seamless-immutable

seamless-immutable是另一套持久化数据结构的库,它并没有实现完整的 Persistent Data Structure,而是使用 Object.defineProperty(因此只能在 IE9 及以上使用)扩展了 JavaScript 的 Array 和 Object 对象来实现,只支持 Array 和 Object 两种数据类型,API 基于与 Array 和 Object ,因此许多不用改变自己的使用习惯,对代码的入侵非常小。同时,它的代码库也非常小,压缩后下载只有 2K。

简单示例

// 使用  seamless-immutable.js 后
import Immutable from 'seamless-immutable';
var array = Immutable(["totally", "immutable", {hammer: "Can’t Touch This"}]);

array[1] = "I'm going to mutate you!"
array[1] // "immutable"

array[2].hammer = "hm, surely I can mutate this nested object..."
array[2].hammer // "Can’t Touch This"

for (var index in array) { console.log(array[index]); }
// "totally"
// "immutable"
// { hammer: 'Can’t Touch This' }

JSON.stringify(array) // '["totally","immutable",{"hammer":"Can’t Touch This"}]'

seamless-immutable的实现依赖于ECMAScript 5 的一些特性,如Object.definePropertyObject.freeze,因此会在浏览器兼容性方面有所欠缺

不过这不是问题啦,可以使用 polyfill es-shims/es5-shim 来解决。

对比

虽然 Immutable.js 尽量尝试把 API 设计的原生对象类似,有的时候还是很难区别到底是 Immutable 对象还是原生对象,容易混淆操作。

Immutable 中的 Map 和 List 虽对应原生 Object 和 Array,但操作非常不同,比如你要用 map.get('key')而不是 map.keyarray.get(0) 而不是 array[0]。另外 Immutable 每次修改都会返回新对象,也很容易忘记赋值。

当使用外部库的时候,一般需要使用原生对象,也很容易忘记转换。

当然也有一些办法来避免类似问题发生:

  • 使用 Flow 或 TypeScript 这类有静态类型检查的工具

  • 约定变量命名规则:如所有 Immutable 类型对象以 开头。

  • 使用 Immutable.fromJS 而不是 Immutable.MapImmutable.List 来创建对象,这样可以避免 Immutable 和原生对象间的混用。

但是还有一个致命的问题是,对现有代码的改造,使用 Immutable.js 成本实在太大。

seamless-immutable虽然数据结构和API不如Immutable.js丰富,但是对于只想使用Immutable Data来对React进行优化以避免重复渲染的我们来说,已经是绰绰有余了。而且Array和Object原生的方法等都可以直接使用,原有项目改动极小。

React中使用

由于seamless-immutable的实现依赖于ECMAScript 5 和原生的Array、Object天然的兼容性,导致其在React中的使用非常简单,只要注意三点就可以达到效果:

初始化state

初始化state数据的时候,使用Immutable的初始化方式。

import Immutable from 'seamless-immutable';

state: {
    orderList: Immutable([]),
  }

修改state数据

修改state数据的时候,同样也要注意:

saveOrderList(state, {payload: items}) {
      return {...state, orderList: Immutable(items)};
    }

shouldComponentUpdate

使用pure-render-decorator,真是方便、快捷又优雅。当然,由于decorator属于ES7的特性,babel还需要自己配置。

import React from 'react';
import pureRender from 'pure-render-decorator';

@pureRender
class OrderListView extends React.Component {
  render() {
    const {orderList} = this.props;
    return (
      <div>
        {
          orderList.map((item) => {
            return (
              <div key={item.orderNum}>
                <div>{item.orderNum}</div>
                <div>{item.createTime}</div>
                <div>{item.contact}</div>
                <hr/>
              </div>
            );
          })
        }
      </div>
    );
  }
}

export default OrderListView;

怎么样,传说中的React的SCU的优化就是这么简单,赶紧去试试吧。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342