React渲染问题研究以及Immutable的应用
写在前面
这里主要介绍自己在React
开发中的一些总结,关于react
的渲染问题的一点研究。
另外本人一直希望在React
项目中尝试使用,因此在之前已经介绍过immutable
的API,可以参看这里Immutable日常操作之深入API,算是对其的一个补充。
本文所有代码请参看github
仓库:https://github.com/Rynxiao/immutable-react
渲染房间列表
这个例子主要是写了同时渲染1000个房间,如果我添加一个房间或者修改一个房间,在react
中不同的实现方式下render
函数将会表现出什么样的结果?以及针对不同结果的一些思考和优化。大致的列表例子如下:生成1000个不同的房间盒子,颜色随机。
项目整体目录结构大致是这样的:
下面主要来看ListDetail.js
中是如何写的:
- 父组件
List
- 子组件
RoomDetail
,子组件的功能只是纯粹的渲染功能,自身并没有任何操作
子组件:
// 子组件
class RoomDetail extends React.Component {
constructor(props) {
super(props);
}
render() {
let room = this.props.room;
return (
<li
className="list-item"
style={{ backgroundColor: room.backgroundColor }}>
{ room.number }
</li>
);
}
}
父组件:
// 父组件
export default class List extends React.Component {
// ...
constructor(props) {
super(props);
this.addRoom = this.addRoom.bind(this);
this.modifyRoom = this.modifyRoom.bind(this);
this.state = {
roomList: this.generateRooms(),
newRoom: 0
};
}
// ...
render() {
return (
<div>
<h2 className="title">React的列表渲染问题</h2>
<div><a className="back" href="#/">返回首页</a></div>
<div className="btn-operator">
<button onClick={ this.addRoom }>Add A Room</button>
<button onClick={ this.modifyRoom }>Modify A Room</button>
</div>
<ul className="list-wrapper">
{
this.state.roomList.map((room, index) => {
return <RoomDetail key={ `roomDetail${index}` } room={ room } />
})
}
</ul>
</div>
);
}
}
下面我们来添加一个房间试试
// 添加房间
addRoom() {
let newRoom = { number: `newRoom${++this.state.newRoom}`, backgroundColor: '#f00' };
let newList = this.state.roomList;
newList.push(newRoom);
this.setState({ roomList: newList });
}
这个操作主要是生成一个新的房间,然后从state
中取出当前的房间列表,然后再当前的房间列表中添加一个新的房间,最后将整个列表从新设置到状态中。
很显然,此时由于父组件的状态发生了变化,会引起自身的render
函数执行,同时列表开始重新遍历,然后将每一个房间信息重新传入到子组件中。是的,重新传入,就代表了子组件将会重新渲染。我们可以来做一个测试,在子组件的render
方法中加入如下打印:
render() {
let room = this.props.room;
console.log(`.No${room.number}`);
return (
// ...
);
}
不出意外的发现了所有的子组件渲染的证据:
同时利用chorme
的Performance
检测的信息如下:
调用的方法堆栈如下:
渲染子组件的时间达到764ms
,同时在堆栈中可以看到大量的receiveComponent
和updateChildren
方法的执行。那么有没有什么办法只渲染改变的部分呢?在react官网性能监控这一小节中有提到一个方法,将子组件继承React.PureComponent
可以局部有效防止渲染。加上之后的代码是这样的:
class RoomDetail extends React.PureComponent {
// ...
}
所有的东西都没有变化,只是将Component
换成了PureComponent
。下面我们再来测试一下:
性能检测图如下:
效果出奇的好,果然只是渲染了一次,并且速度提升了10几倍之多。
其中的原理是在组件的shouldComponentUpdate
方法中进行了props
与state
的比较,如果认为他们相等,则会返回false
,否则则会返回true
。
// react/lib/ReactComponentWithPureRenderMixin.js
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function (nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
}
};
同时官网也说了,这只是局部有效,为什么呢?因为这些值的比较都只是浅比较,也就是只是第一层的比较。那么会出现什么问题,我们来看下面的操作:
修改其中的一个房间:
// 修改房间
modifyRoom() {
let newList2 = this.state.roomList;
newList2[0] = { number: 'HAHA111', backgroundColor: '#0f0' };
this.setState({ roomList: newList2 });
}
很意外,当我添加了一个房间之后,发现第一个房间并没有我们想象中的发生变化。为什么?
原因是我虽然修改了第一个房间的数据,当时我并没有修改他的引用地址。类似下面这样的:
var arr = [{ a: 1 }, { b: 2 }];
var arr2 = arr1;
arr2[0] = { c: 1 };
arr === arr2; // true
因此在子组件中比较房间的时候,就会出现比较的值相等的情况,此时将会返回false
那么有没有办法改变这个问题,我找到了两个办法:
- 从数据源头入手
- 从子组件是否渲染条件入手
从数据源头入手,即为改造数据,将数据进行深拷贝,使得原先的引用与新得到的对象的引用不相同即可。关于深拷贝的实现方法有很多,我这里贴一个,之后再仔细做研究。
// 这个函数可以深拷贝 对象和数组
var deepCopy = function(obj){
var str, newobj = obj.constructor === Array ? [] : {};
if(typeof obj !== 'object'){
return;
} else if(window.JSON){
str = JSON.stringify(obj), //系列化对象
newobj = JSON.parse(str); //还原
} else {
for(var i in obj){
newobj[i] = typeof obj[i] === 'object' ?
cloneObj(obj[i]) : obj[i];
}
}
return newobj;
};
在ES6中提供了一种解构方式,这种方式也可以实现数组的深层次拷贝。类似这样的
let arr = [1, 2, 3, 4];
let arr1 = [...arr];
arr1 === arr; // false
// caution
let arr = [{ a: 1 }, { b: 2 }];
let arr1 = [...arr];
arr1 === arr; // false
arr1[0] = { c: 3 };
arr1[0] === arr[0]; // false
arr1[1] === arr[1]; // true
因此我把modifyRoom
函数进行了如此改造:
// 修改房间
modifyRoom() {
let newList2 = [...this.state.roomList];
newList2[0] = { number: 'HAHA111', backgroundColor: '#0f0' };
this.setState({ roomList: newList2 });
}
因此在比较第一个对象的时候,发现它们已经不相等了,则会重新渲染。
从子组件是否渲染条件入手,可以不需要使用React.PureComponent
,而直接在shouldComponentUpdate
方法入手。因为两次值改变之后,我清楚得可以知道,改变的值只是第一个对象中的数值改变。那么我可以这么写来判断:
class RoomDetail extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.room.number === this.props.room.number) {
return false;
}
return true;
}
render() {
let room = this.props.room;
return (
<li
className="list-item"
style={{ backgroundColor: room.backgroundColor }}>
{ room.number }
</li>
);
}
}
同样得可以达到效果。但是如果在shouldComponentUpdate
中存在着多个props
和state
中值改变的话,就会使得比较变得十分复杂。
应用Immutable.js来检测React中值的变化问题
在官网上来说,immutable
提供的数据具有不变性,被称作为Persistent data structure
,又或者是functional data structure
,非常适用于函数式编程,相同的输入总会预期到相同的输出。
2.1 immutable的性能
在immutable
官网以及在知乎中谈到为什么要使用immutable
的时候,会看到一个关键词efficient
。高效地,在知乎上看到说是性能十分好。在对象深复制、深比较上对比与Javascript
的普通的深复制与比较上来说更加地节省空间、提升效率。我在这里做出一个实验(这里我并不保证实验的准确性,只是为了验证一下这个说法而已)。
实验方法:我这里会生成一个对象,对象有一个广度与深度,广度代表第一层对象中有多少个键值,深度代表每一个键值对应的值会有多少层。类似这样的:
{
"width0": {"key3": {"key2": {"key1": {"key0":"val0"}}}},
"width1": {"key3": {"key2": {"key1": {"key0":"val0"}}}},
"width2": {"key3": {"key2": {"key1": {"key0":"val0"}}}},
// ...
"widthN": {"key3": {"key2": {"key1": {"key0":"val0"}}}}
}
因此实际上在javascript
对象的复制和比较上,需要遍历的次数其实是width * deep
。
在复制的问题上,我做了三种比较。
- deepCopy(obj)
- JSON.parse(JSON.stringify(obj))
- Immutable
最终得到的数据为:
deepCopy( μs ) | JSON( μs ) | Immutable( μs ) | |
---|---|---|---|
20 * 50 | 4000 | 9000 | 20 |
20 * 500 | 8000 | 10000 | 20 |
20 * 5000 | 10000 | 14000 | 20 |
在比较上,我只比较了两种方式:
- javascript deep compare
- Immutable.is
代码如下:
let startTime1 = new Date().getTime();
let result1 = Equals.equalsObject(gObj, deepCopyObj);
let endTime1 = new Date().getTime();
console.log(result1);
console.log(`deep equal time ${(endTime1-startTime1)*1000}μs`);
let startTime2 = new Date().getTime();
let result2 = is(this.state.immutableObj, this.state.aIObj);
let endTime2 = new Date().getTime();
console.log(result2);
console.log(`immutable equal time ${(endTime2-startTime2)*1000}μs`);
最终得到的数据为:
deepCompare( μs ) | Immutable.is( μs ) | |
---|---|---|
20 * 5 | 0 | 7000 |
20 * 50 | 1000 | 27000 |
20 * 500 | 6000 | 24000 |
20 * 5000 | 84000 | 5000 |
数据的设计上可能太过单一,没有涉及到复杂的数据,比如说对象中再次嵌套数组,并且在每一个键值对应的值得广度上设计得也太过单一,只是一条直线下来。但是当数据量达到一定的程度时,其实也说明了一些问题。
总结:
- 对象复制上来说,基本上
Immutable
可以说是零消耗 - 对象比较上,当对象深层嵌套到一定规模,反而
Immutable.is()
所用的时间会更少 - 但是在数据方面来说,
Immutable
并快不了多少
当然只是测试,平时中的纵向嵌套达到三层以上都会认为是比较恐怖的了。
于是我去google
翻了翻,看看有没有什么更好的demo
,下面我摘录一些话。
What is the benefit of immutable.js?
Immutable.js makes sure that the "state" is not mutated outside of say redux. For smaller projects, personally i don't think it is worth it but for bigger projects with more developers, using the same set of API to create new state in reduce is quite a good idea
It was mentioned many times before that Immutable.js has some internal optimizations, such as storing lists as more complex tree structures which give better performance when searching for elements. It's also often pointed out that using Immutable.js enforces immutability, whereas using Object.assign or object spread and destructuring assignments relies to developers to take care of immutability. EDIT: I haven't yet seen a good benchmark of Immutable.js vs no-library immutability. If someone knows of one please share. Sharing is caring
React渲染问题研究以及Immutable的应用的更多相关文章
- react 渲染
目录 React渲染 createElement的三个参数 element如何生成真实节点 ReactDOMComponent 作用 ReactCompositeComponentWrapper 作用 ...
- react中为什么要使用immutable
因为在react中,react的生命周期中的setState()之后的shouldComponentUpdate()阶段默认返回true,所以会造成本组件和子组件的多余的render,重新生成virt ...
- 在 react 项目里如何配合immutable在redux中使用
一.reducer文件的处理 先安装 immutable 与 redux-immutable yarn add immutable redux-immutable 我们可能会在很多地方定义子树,这就需 ...
- 【React】393 深入了解React 渲染原理及性能优化
如今的前端,框架横行,出去面试问到框架是常有的事. 我比较常用React, 这里就写了一篇 React 基础原理的内容, 面试基本上也就问这些, 分享给大家. React 是什么 React是一个专注 ...
- 你所要掌握的最简单基础的React渲染优化
一.React的渲染机制 要掌握一两项React-render优化的方法不难,但是非常重要.无论是在实际项目中的一个小细节,还是迎合'面试官'的口味 1.1 触发Render 我们知道React要更新 ...
- react脚手架改造(react/react-router/redux/eslint/karam/immutable/es6/webpack/Redux DevTools)
公司突然组织需要重新搭建一个基于node的论坛系统,前端采用react,上网找了一些脚手架,或多或少不能满足自己的需求,最终在基于YeoMan的react脚手架generator-react-webp ...
- react渲染原理深度解析
https://mp.weixin.qq.com/s/aM-SkTsQrgruuf5wy3xVmQ 原文件地址 [第1392期]React从渲染原理到性能优化(二)-- 更新渲染 黄琼 前端早读课 ...
- 从 React 的组件更新谈 Immutable 的应用
在介绍 Immutable 如何在 React 中应用之前,先来谈谈 React 组件是如何更新的. React 是基于状态驱动的开发,可以将一个组件看成是一个有限状态机,组件要更新,必须更新状态. ...
- react渲染和diff算法
1.生成虚拟dom createElement的作用就是生成虚拟dom.虚拟dom到底是个啥,其实它就是个javascript对象~,这个对象的属性有props,vType,type, 而props也 ...
随机推荐
- The first day,I get a blogs!!
我拥有了自己的博客,很happy! 今天学习了kvm,虽然命令行界面比较枯燥,还好不算太难,在大家的热心帮助下我创建了一个虚拟机!!
- ubuntu14.04_caffe2安装
今天F8开发者大会上,Facebook正式发布Caffe2.经过一天的折腾,终于在ubuntu14.04上成功配置caffe2,现将经验分享如下: 1.ubuntu14.04系统下caffe2所需依赖 ...
- 将java对象转成json字符串
如果要将数组.对象.Map.List转换成JSON数据,那我们需要一些jar包: json-lib-2.4-jdk15.jar ezmorph-1.0.6.jar commons-logging.ja ...
- 如何把我的Java程序变成exe文件?
JAVA是一种“跨平台”的语言,拥有“一次编写,处处运行”的特点,让它成为当今IT行业,必不可少的一门编程语言.每一个软件开发完成之后,应该大家都需要打包程序并发送给客户,常见的方式:java程序打成 ...
- 接口工具-POSTMAN
前端的一项总要工作就是测试接口,当然这也可能是你们后台人员做的.不管怎样,都需要测试接口,那么就来介绍一款谷歌浏览器接口测试插件,postman.首先你要去谷歌的应用商店,搜索这个插件,(需要FQ), ...
- iOS源码博文集锦1
iOS精选源码 iOS一种弹出视图效果带动画 导航栏显示渐变色,类似qq一样 一分钟找到重力方向 简单高度自定义的日历.可根据项目的需求灵活修改布局 类似于UITableView且极简的图片浏览器 小 ...
- error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
一.情形描述 下载了一个VS的源码,不知道此源码的版本.使用VS2010编译时出现如下报错: error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏 二.解决方法 在VS2010界 ...
- (转载)ubuntu创建新用户并增加管…
问题导读: 1.adduser与useradd有什么区别? 2.那种方式会自动创建组.用户组等信息? 3.如何新建用户具有管理员权限? $是普通管员,#是系统管理员,在Ubuntu下,root用户默认 ...
- 26. leetcode 350. Intersection of Two Arrays II
350. Intersection of Two Arrays II Given two arrays, write a function to compute their intersection. ...
- 转载的log4cplus使用指南
以下转载的log4cplus使用指南的版本可能不是最新,仅作参考了解.应以最新安装包中的示例代码为准. 目 录1 Log4cplus简介 52 安装方法 53 主要类说明 64 ...