一、痛点

在我们的印象中,React 好像就意味着组件化、高性能,我们永远只需要关心数据整体,两次数据之间的 UI 如何变化,则完全交给 React Virtual DomDiff 算法 去做。以至于我们很随意的去操纵数据,基本优化shouldComponentUpdate 也懒得去写,毕竟不写也能正确渲染。但随着应用体积越来越大,会发现页面好像有点变慢了,特别是组件嵌套比较多,数据结构比较复杂的情况下,随便改变一个表单项,或者对列表做一个筛选都要耗时 100ms 以上,这个时候我们就需要优化了!当然如果没有遇到性能瓶颈,完全不用担心,过早优化是邪恶的。这里我们总结一个很简单的方案来让 React 应用性能发挥到极致。在下面一部分,我们先回顾一下一些背景知识,包括:JavaScript 变量类型和 React 渲染机制,如果你是老鸟可以直接跳过。

二、一些背景知识的回顾

1. 变量类型

JavaScript的变量类型有两类:

  • 基本类型:6 种基本数据类型, UndefinedNullBooleanNumberStringSymbol
  • 引用类型:统称为 Object 类型,细分为:Object 类型、 Array 类型、 Date 类型、 RegExp 类型、 Function 类型等。

举个例子:

let p1 = { name: 'neo' };
let p2 = p1;
p2.name = 'dave';
console.log(p1.name); // dave

在引用类型里,声明一个 p1 的对象,把 p1 赋值给 p2 ,此时赋的其实是该对象的在堆中的地址,而不是堆中的数据,也就是两个变量指向的是同一个存储空间,后面 p2.name 改变后,也就影响到了 p1。虽然这样做可以节约内存,但当应用复杂后,就需要很小心的操作数据了,因为一不注意修改一个变量的值可能就影响到了另外一个变量。如果我们想要让他们不互相影响,就需要拷贝出一份一模一样的数据,拷贝又分浅拷贝与深拷贝,浅拷贝只会拷贝第一层的数据,深拷贝则会递归所有层级都拷贝一份,比较消耗性能。

2. React

React 中,每次 setStateVirtual DOM 会计算出前后两次虚拟 DOM 对象的区别,再去修改真实需要修改的 DOM 。由于 js 计算速度很快,而操作真实 DOM 相对比较慢,Virtual DOM 避免了没必要的真实 DOM 操作,所以 React 性能很好。但随着应用复杂度的提升, DOM 树越来越复杂,大量的对比操作也会影响性能。比如一个 Table 组件,修改其中一行 Tr 组件的某一个字段, setState 后,其他所有行 Tr 组件也都会执行一次 render 函数,这其实是不必要的。我们可以通过 shouldComponentUpdate 函数决定是否更新组件。大部分时候我们是可以知道哪些组件是不会变的,根本就没必要去计算那一部分虚拟 DOM

三、 PureComponent

React15.3 中新加了一个类PureComponent,前身是 PureRenderMixin ,和 Component 基本一样,只不过会在 render 之前帮组件自动执行一次shallowEqual(浅比较),来决定是否更新组件,浅比较类似于浅复制,只会比较第一层。使用 PureComponent 相当于省去了写 shouldComponentUpdate 函数,当组件更新时,如果组件的 propsstate

  1. 引用和第一层数据都没发生改变, render 方法就不会触发,这是我们需要达到的效果。
  2. 虽然第一层数据没变,但引用变了,就会造成虚拟 DOM 计算的浪费。
  3. 第一层数据改变,但引用没变,会造成不渲染,所以需要很小心的操作数据。

四、 Immutable.js

Immutable.js是 Facebook2014 年出的持久性数据结构的库,持久性指的是数据一旦创建,就不能再被更改,任何修改或添加删除操作都会返回一个新的 Immutable 对象。可以让我们更容易的去处理缓存、回退、数据变化检测等问题,简化开发。并且提供了大量的类似原生 JS 的方法,还有 Lazy Operation 的特性,完全的函数式编程。

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

可以看到,修改 map1 的属性返回 map2,他们并不是指向同一存储空间,map1 声明了只有,所有的操作都不会改变它。

ImmutableJS 提供了大量的方法去更新、删除、添加数据,极大的方便了我们操纵数据。除此之外,还提供了原生类型与 ImmutableJS 类型判断与转换方法:

import { fromJS, isImmutable } from "immutable";
const obj = fromJS({a: 'test',b: [1, 2, 4]
}); // 支持混合类型
isImmutable(obj); // true
obj.size(); // 2
const obj1 = obj.toJS(); // 转换成原生 `js` 类型

ImmutableJS 最大的两个特性就是: immutable data structures(持久性数据结构)与 structural sharing(结构共享),持久性数据结构保证数据一旦创建就不能修改,使用旧数据创建新数据时,旧数据也不会改变,不会像原生 js 那样新数据的操作会影响旧数据。而结构共享是指没有改变的数据共用一个引用,这样既减少了深拷贝的性能消耗,也减少了内存。比如下图:

左边是旧值,右边是新值,我需要改变左边红色节点的值,生成的新值改变了红色节点到根节点路径之间的所有节点,也就是所有青色节点的值,旧值没有任何改变,其他使用它的地方并不会受影响,而超过一大半的蓝色节点还是和旧值共享的。在 ImmutableJS 内部,构造了一种特殊的数据结构,把原生的值结合一系列的私有属性,创建成 ImmutableJS 类型,每次改变值,先会通过私有属性的辅助检测,然后改变对应的需要改变的私有属性和真实值,最后生成一个新的值,中间会有很多的优化,所以性能会很高。

五、 案例

首先我们看看只使用 React 的情况下,应用性能为什么会被浪费,代码地址:https://github.com/wulv/fe-ex... ,这个案例使用 create-react-app,检测工具使用 chrome 插件:React Perf。执行

git clone https://github.com/wulv/fe-example.git
cd fe-example/react-table
yarn
yarn start

可以打开页面,开始记录,然后随便对一列数据进行修改,结束记录,可以看到我们仅修改了一行数据,但在 Print Wasted 那一项里,渲染 Tr 组件浪费了5次:

无论是添加,删除操作,都会浪费 n-1render ,因为 App 组件的整个 state 改变了,所有的组件都会重新渲染一次,最后对比出需要真实 DOM 的操作。我们把 Table 组件和 Tr 继承的 Component 改成 PureComponent ,那么, Tr 组件每次更新都会进行一次 shallowEqual 比较,在记录一次,会发现修改操作没有了浪费,然而这个时候添加和删除操作却无效了,分析一下添加的操作是:

 add = () => {const  { data } = this.state;data.push(dataGenerate())this.setState({data})}

data.push 并没有改变 data 的引用,所以 PureComponentshallowEqual 直接返回了 true ,不去 render 了。这并不是我们想要的,所以如果使用 Component 必定带来性能浪费,使用 PureComponent 又必须保证组件需要更新时,propsstate 返回一个新引用,否则不会更新 UI

这个时候, ImmutableJS 就可以显示出它的威力了,因为它可以保证每次修改返回一个新的 Object,我们看看修改后的例子:代码地址:https://github.com/wulv/fe-ex... ,执行上面例子同样的操作,可以看到:

添加,删除,修改操作,没有一次浪费。没有浪费的原因是所有的子组件都使用了 PureComponentImmutableJS 保证修改操作返回一个新引用,并且只修改需要修改的节点(PureComponent 可以渲染出新的改动),其他的节点引用保持不变(PureComponent 直接不渲染)。可以看出, PureComponentImmutableJS 简直是天生一对啊,如果结合 redux ,那就更加完美了。因为 reduxreducer 必须每次返回一个新的引用,有时候我们必须使用 clone 或者 assign 等操作来确保返回新引用,使用 ImmutanleJS 天然保证了这一点,根本就不需要 lodash 等函数库了,比如我使用redux + immutable + react-router + express 写了一个稍微复杂点的例子:https://github.com/wulv/fe-ex...,可以看到 pageIndexstore 的状态是:

{loading: false,tableData: [{"name": "gyu3w0oa5zggkanciclhm2t9","age": 64,"height": 121,"width": 71,"hobby": {"movie": {"name": "zrah6zrvm9e512qt4typhkt9","director": "t1c69z1vd4em1lh747dp9zfr"}}}],totle: 0
}

如果我需要快速修改 width 的值为90,比较一下使用深拷贝、 Object.assignImmutableJS 三种方式的区别:

// payload = { name: 'gyu3w0oa5zggkanciclhm2t9', width: 90 }
// 1. 使用深拷贝updateWidth(state, payload) {const newState = deepClone(state);return newState.tableData.map(item => {if (tem.name === payload.name) {item.width = payload.width;}return item;});}
// 2. 使用Object.assignupdateWidth(state, payload) {return Object.assign({}, state, {tableData: state.state.map(item => {if (item.name === payload.name) {return Object.assign({}, item, { width: payload.width });}return item;})})}
// 3. 使用ImmutableJSupdateWidth(state, payload) {return state.update('tableData', list => list.update(list.findIndex((item) => item.get('name') === payload.name),item => item.set('width', payload.width)));}

使用深拷贝是一个昂贵的操作,而且引用都改变了,必然造成 re-render, 而 Object.assign 会浅复制第一层,虽然不会造成 re-render,但浅复制把其他的属性也都复制了一次,在这里也是很没有必要的,只有使用 ImmutableJS 完美的完成了修改,并且代码也最少。

六、 优势与不足

可以看出, ImmutableJS 结合 PureComponent 可以很大程度的减少应用 re-render 的次数,可以大量的提高性能。但还是有一些不足的地方:

  1. 获取组件属性必须用 getgetIn 操作(除了 Record 类型),这样和原生的.操作比起来就麻烦多了,如果组件之前已经写好了,还需要大量的修改。
  2. ImmutableJS 库体积比较大,大概56k,开启 gzip 压缩后16k。
  3. 学习成本。
  4. 难以调试,在 redux-logger 里面需要在 stateTransformer 配置里执行 state.toJS()

七、 最佳实践

其实,重要的是编程者需要有性能优化的意识,熟悉 js 引用类型的特性,了解事情的本质比会使用某个框架或库更加重要。用其他的方法也是完全可以达到 ImmutableJS 的效果,比如添加数据可以使用解构操作符的方式:

 add = () => {const  { data } = this.state;this.setState({data: [...data, dataGenerate()]})}

只不过如果数据嵌套比较深,写起来还是比较麻烦。以下有一些小技巧:

  1. 还有两个轻量库可以实现不可变数据结构:seamless-immutable或者immutability-helper,只不过原理完全不一样,效率也没那么高。
  2. 避免大量使用 toJS 操作,这样会浪费性能。
  3. 不要将简单的 JavaScript 对象与 Immutable.JS 混合
  4. 结合 redux 的时候,要使用import { combineReducers } from 'redux-immutablejs';,因为 reduxcombineReducers 期望 state 是一个纯净的 js 对象。
  5. 尽量将 state 设计成扁平状的。
  6. 展示组件不要使用 Immutable 数据结构。
  7. 不要在 render 函数里一个 PureComponent 组件的 props 使用 bind(this) 或者 style={ { width: '100px' } },因为 shallowEqual 一定会对比不通过。

八、 参考链接

  • Immutable.js, persistent data structures and structural sharing
  • immutable.js is much faster than native javascript
  • Immutable 详解及 React 中实践

本文首发于有赞技术博客。

React 的性能优化(一)当 PureComponent 遇上 ImmutableJS相关推荐

  1. React进阶—性能优化

    React性能优化思路 软件的性能优化思路就像生活中去看病,大致是这样的: 使用工具来分析性能瓶颈(找病根) 尝试使用优化技巧解决这些问题(服药) 使用工具测试性能是否确实有提升(疗效确认) Reac ...

  2. 前端jquery、vue、react之性能优化

    一.jquery 选择器性能优化建议 总是从#id选择器来继承 这是jQuery选择器的一条黄金法则.jQuery选择一个元素最快的方法就是用ID来选择了. $( '#content' ).hide( ...

  3. [react] react的性能优化在哪个生命周期?它优化的原理是什么?

    [react] react的性能优化在哪个生命周期?它优化的原理是什么? shouldComponentUpdate 减少不必要的重新渲染 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易 ...

  4. JavaScript 大揭秘:React、性能优化以及多线程

    @开发者,在后端仅提供原始数据集的情况下,如何让所有搜索逻辑都在前端完成?不仅如此,还要能够实现预测用户输入.忽略错别字输入.快速自动补齐等智能功能?本文的作者就深入 JavaScript 技术特性, ...

  5. 如何对react进行性能优化

    React本身就非常关注性能,其提供的虚拟DOM搭配上DIff算法,实现对DOM操作最小粒度的改变也是非常高效的,然而其组件的渲染机制,也决定了在对组件更新时还可以进行更细致的优化.  react组件 ...

  6. React 组件性能优化之 PureComponent 的使用

    文章出自个人博客:https://knightyun.github.io/2021/05/09/js-react-purecomponent,转载请申明 在 React 类组件中,如果状态(state ...

  7. React Native性能优化总结

    React Native开源已经接近2年时间,京东.携程.58同城等互联网公司都在使用,公司于今年也开始使用,并推广到各个新项目.本文重点分享我们遇到的一些问题以及优化方案. 一.为什么会引入Reac ...

  8. Unity性能优化之内存篇(上)

    原文链接:http://blog.uwa4d.com/archives/optimzation_memory_1.html 项目的性能优化主要围绕CPU.GPU和内存三大方面进行.接上期CPU优化专讲 ...

  9. React的性能优化

    React的更新流程如下,我们可以有两种优化角度 1.props/state变化--->render函数变化这个阶段,减少render的执行次数 2.新旧DOM树进行diff--->计算出 ...

最新文章

  1. android点滴 之 进度条
  2. 使用Wireshark分析工控协议
  3. 数据切分——Atlas介绍
  4. Hdu1166单点更新线段树
  5. java json utf-8_Java 编码 和JSON
  6. selenium的安装
  7. 页面上通过地址栏传值时出现乱码的两种解决方法
  8. 大数据技术原理与应用答案 林子雨 第二版
  9. 空间直线与球面相交算法
  10. 高一计算机课期中考试总结反思,信息技术期中考试试卷分析与反思
  11. 区块链报告会心得体会3000_区块链讲座观后感6
  12. java winhex_winhex模版
  13. STM32F103_study49_The punctual atoms(STM32 Bit operation and logical operation in C language )
  14. CF #768 E.Flipping Ring
  15. 【精通特征工程】学习笔记(二)
  16. OpenGL-使用Assimp加载3d模型
  17. Effective C++ 规则39:明智而谨慎的使用private继承
  18. JavaScript实现简单日历
  19. java 输入控制_java控制台输入
  20. 解决WORD2013输入时光标老跳的问题

热门文章

  1. 毫末智行,现在是中国营收增速最快的无人车公司
  2. 工业机器人应用行业大盘点
  3. 40亿次仿真学习:人工智能5:0大胜人类飞行员
  4. 美国人到底为什么不待见人脸识别技术?
  5. 最萌算法学习来啦,看不懂才怪!
  6. 关于机器学习和AI的区别最经典的解释
  7. 揭秘丨“北京八分钟”里中国制造的科技力量
  8. 干货丨达沃斯群英纵论人工智能,核心观点汇总
  9. 卫星对于物联网来说是一个非常好的选择
  10. 25万亿规模!中国智慧城市建设刚需在哪?