[译]Reactjs性能篇

news2024/12/26 8:02:19

英文有限,技术一般,海涵海涵,由于不是翻译出身,所以存在大量的瞎胡乱翻译的情况,信不过我的,请看原文~~

原文地址:https://facebook.github.io/react/docs/advanced-performance.html


###性能优化

每当开发者选择将react用在真实项目中时都会先问一个问题:使用react是否会让项目速度更快,更灵活,更容易维护。此外每次状态数据发生改变时都会进行重新渲染界面的处理做法会不会造成性能瓶颈?而在react内部则是通过使用一些精妙的技巧来最小化每次造成ui更新的昂贵的dom操作从而保证性能的。

####避免直接作用于DOM
react实现了一层虚拟dom,它用来映射浏览器的原生dom树。通过这一层虚拟的dom,可以让react避免直接操作dom,因为直接操作浏览器dom的速度要远低于操作javascript对象。每当组件的属性或者状态发生改变时,react会在内存中构造一个新的虚拟dom与原先老的进行对比,用来判断是否需要更新浏览器的dom树,这样就尽可能的优化了渲染dom的性能损耗。

在此之上,react提供了组件生命周期函数,shouldComponentUpdate,组件在决定重新渲染(虚拟dom比对完毕生成最终的dom后)之前会调用该函数,该函数将是否重新渲染的权限交给了开发者,该函数默认直接返回true,表示默认直接出发dom更新:

shouldComponentUpdate: function(nextProps, nextState) {
	return true;
}

值得注意的是,react会非常频繁的调用该函数,所以如果你打算自己实现该函数的逻辑,请尽可能保证性能。

比方说,你有一个拥有多个帖子的聊天应用,如果此时只有一个发生了变化,如果你如下实现了shouldComponentUpdate,react会根据情况避免重新渲染那些没有发生变化的帖子:

shouldComponentUpdate: function(nextProps, nextState) {
	// TODO: return whether or not current chat thread is different to former one.
	// 根据实际情况判断当前帖子的状态是否和之前不同
}

总之,react尽可能的避免了昂贵的dom操作,并且允许开发者干涉该行为。

####shouldComponentUpdate实战
这里举个包含子元素的组件例子,如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图中每个圆点表示一个dom节点,当某个dom节点的shouldComponentUpdate返回false时(例如c2),react就无需为其更新dom,注意,react甚至根本不会去调用c4和c5节点的shouldComponentUpdate函数哦~

图中c1和c3的shouldComponentUpdate返回了true,因此react会检查检查其它们包含的直接子节点。最有趣的是c8节点,虽然调用它的shouldComponentUpdate方法返回的是true,但react检查后发现其dom结构并未发生改变,所以react最终是不会重新渲染其浏览器dom的。

上图的情况下,react最终只会重新渲染c6,原因你应该懂的。

那么我们应该如何实现shouldComponentUpdate函数呢?假设你有一个只包含字符串的组件,如下:

React.createClass({
	propTypes: {
		value: React.PropTypes.string.isRequired
	},

	render: function() {
		return <div>{this.props.value}</div>;
	}
});

我们可以简单的直接实现shouldComponentUpdate如下:

shouldComponentUpdate: function(nextProps, nextState) {
	return this.props.value !== nextProps.value;
}

目前为止一切都很顺利,处理基础类型的属性和状态是很简单的,我们可以直接使用js语言提供的===比对来实现一个mix并注入到所有组件中,事实上,react自身已经提供了一个类似的:PureRenderMixin。

但是如果你的组件所拥有的属性或状态不是基础类型呢,而是复合类型呢?比方说是一个js对象,{foo: 'bar'}

React.createClass({
	propTypes: {
		value: React.PropTypes.object.isRequired
	},

	render: function() {
		return <div>{this.props.value.foo}</div>;
	}
});

这种情况下我们刚才实现的那种shouldComponentUpdate就歇菜了:

// 假设 this.props.value 是 { foo: 'bar' }
// 假设 nextProps.value 是 { foo: 'bar' },
// 但是nextProps和this.props对应的引用不相同
this.props.value !== nextProps.value; // true

要想修复这个问题,简单粗暴的方法是我们直接比对foo的值,如下:

shouldComponentUpdate: function(nextProps, nextState) {
	return this.props.value.foo !== nextProps.value.foo;
}

我们当然可以通过深比对来确定属性或状态是否确实发生了改变,但是这种深比对是非常昂贵的,还记得我们刚出说过shouldComponentUpdate函数的调用非常频繁么?更何况我们为每个model去单独实现一个匹配的深比对逻辑,对于开发人员来说也是非常痛苦的。最重要的是,如果我们不是很小心的处理对象引用关系的话,还会带来灾难。例如下面这个组件:

React.createClass({
	getInitialState: function() {
		return { value: { foo: 'bar' } };
	},

	onClick: function() {
		var value = this.state.value;
		value.foo += 'bar'; // ANTI-PATTERN!
		this.setState({ value: value });
	},

	render: function() {
		return (
  			<div>
    			<InnerComponent value={this.state.value} />
    			<a onClick={this.onClick}>Click me</a>
  			</div>
		);
	}
});

起初,InnerComponent组件进行渲染,它得到的value属性为{foo: 'bar'}。当用户点击链接后,父组件的状态将会更新为{ value: { foo: 'barbar' } },触发了InnerComponent组件的重新渲染,因为它得到了一个新的属性:{ foo: 'barbar' }

看上去一切都挺好的,其实问题在于,父组件和子组件供用了同一个对象的引用,当用户触发click事件时,InnerComponent的prop将会发生改变,因此它的shouldComponentUpdate函数将会被调用,而此时如果按照我们目前的shouldComponentUpdate比对逻辑的话,this.props.value.foonextProps.value.foo是相等的,因为事实上,它们同时引用同一个对象哦~所以,我们将会看到,InnerComponent的ui并没有更新。哎~,不信的话,我贴出完整代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>demo</title>
		<!--引入React库-->
		<script src="lib/react.min.js"></script>
		<!--引入JSX转换库-->
		<script src="lib/JSXTransformer.js"></script>
		<!--组件样式-->
	</head>
	<body>
		<!--定义容器-->
		<div id="content"></div>

		<!--声明脚本类型为JSX-->
		<script type="text/jsx">

			var InnerComponent = React.createClass({
    			shouldComponentUpdate: function(nextProps, nextState) {
					return this.props.value.foo !== nextProps.value.foo;
				},
				render: function() {
					return (
  						<div>
    						{this.props.value.foo}
  						</div>
					);
				}
			});

			var OutComponent = React.createClass({
				getInitialState: function() {
					return { value: { foo: 'bar' } };
				},

				onClick: function() {
					var value = this.state.value;
					value.foo += 'bar'; // ANTI-PATTERN!
					this.setState({ value: value });
				},

				render: function() {
					return (
  						<div>
    						<InnerComponent value={this.state.value} />
    						<a onClick={this.onClick}>Click me</a>
  						</div>
					);
				}
			});

			React.render(<OutComponent />, document.querySelector("#content"));
	
		</script>
	</body>
</html>

####Immutable-js的救赎
Immutable-js是一个javascript集合库,作者是Lee Byron,该项目最近从fb开源。它提供了不可变集合类型:

  • 不可变性:一旦创建,这个集合不允许再更改。
  • 延续性:新集合可以衍生自一个已经创建过的集合,并作一些改动,此时源集合不会受到任何影响。
  • 结构共享:如果新集合衍生自一个老集合,那么新集合中与老集合相同的部份将会共享同一块内存,这样做的好处是节省内存开销,并能在创建新集合对象时减少内存拷贝的性能损耗。

不可变性使得监控状态变化变得可行,每次状态发生变化,将总是返回一个新的对象,我们只需要检查改变前后的对象引用是否相同即可。举个例子:

var x = { foo: "bar" };
var y = x;
y.foo = "baz";
x === y; // true

尽管变量y被修改,但由于y和x的引用相同,最后的比对仍然返回true。如果上面的代码用immutable-js来实现:

var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar'  });
var y = x.set('foo', 'baz');
x === y; // false

看到了么,是不是很爽?

另外一种监控变量的方法是设置一个标识位,但这需要开发者编写额外的代码,哎,反正活着就是麻烦。

总之,不可变集合结构提供了你一个廉价且简单的监控对象改变的方法,你可以放在shouldComponentUpdate中。因此,如果你的model属性和状态是基于immutable-js来实现的,那么你就可以直接使用官方提供的PureRenderMixin哟~

####Immutable-js和Flux
如果你恰巧使用Flux,并且你又基于immutable-js来实现你的store,那你先看一下相关的api吧。

让我们来用个模拟应用演示一下使用一种可行的方案。首先,我们需要定义一下用到的model:

var User = Immutable.Record({
	id: undefined,
	name: undefined,
	email: undefined
});

var Message = Immutable.Record({
	timestamp: new Date(),
	sender: undefined,
	text: ''
});

每个Record接受一个对象,分别定义了字段和默认值。我们的messages store需要使用的是list结构:

this.users = Immutable.List();
this.messages = Immutable.List();

很简单吧,接着,每当store接受到一个新的message时,我们只需要创建一个新的record并把它加入到列表中即可:

this.messages = this.messages.push(new Message({
	timestamp: payload.timestamp,
	sender: payload.sender,
	text: payload.text
});

对比我们刚刚讲过的immutable-js特性,在react的组件中我们只需要直接注入PureRenderMixin即可高枕无忧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1891069.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

不同操作系统下的换行符

1. 关键字2. 换行符的比较3. ASCII码4. 修改换行符 4.1. VSCode 5. 参考文档 1. 关键字 CR LF CRLF 换行符 2. 换行符的比较 英文全称英文缩写中文含义转义字符ASCII码值操作系统Carriage ReturnCR回车\r13MacIntosh&#xff08;早期的Mac&#xff09;LinefeedLF换行/新行\…

Qt Q_ASSERT详解

Q_ASSERT详解 引言一、基本用法二、深入了解三、参考链接 引言 Q_ASSERT是 Qt 框架中的一个宏&#xff0c;用于在调试时检查某个条件是否为真。它是程序调试中的一个重要工具&#xff0c;有助于开发者在开发过程中及时发现并修复潜在的错误。 一、基本用法 只在使用 Qt 的 D…

暑期大数据人工智能学习-企业项目试岗实训开营

暑期企业项目-试岗实训活动全面开启啦 跟张良均老师学大数据人工智能 不仅可以提供实习证明&#xff0c;有需要话也可以提供实习鉴定报告 √54个热门案例拆解 √40项目实战课程 √27个项目可选 √4个项目方向

数据提取的奥秘

在数字化时代&#xff0c;数据提取作为连接原始数据与知识发现的桥梁&#xff0c;其重要性不言而喻。它不仅是数据分析和数据治理的基石&#xff0c;更是企业决策和业务优化的关键。以下是数据提取奥秘的详细解析&#xff1a; 一、数据提取的定义与意义 定义&#xff1a;数据…

怎样保存python文件

按下“CtrlS”键即可快速保存Python文件。 或者点击“File”&#xff0c;在下拉菜单中选择“Save”。 打开后我们就会看到这样的一个页面窗口了。 我们还能在这里进行路径的保存位置的查找。 然后在这里选择文件类型&#xff0c;并输入文件名。 接下来我们点击保存就可以完成操…

PyCharm远程开发配置(2024以下版本)

目录 PyCharm远程开发配置 1、清理远程环境 1.1 点击Setting 1.2 进入Interpreter 1.3 删除远程环境 1.4 删除SSH 2、连接远程环境 2.1 点击Close Project 2.2 点击New Project 2.3 项目路径设置 2.4 SSH配置 2.5 选择python3解释器在远程环境的位置 2.6 配置远程…

AI Agent当牛做马,办公自动化带来超级生产力|对话Laplace

成立仅9个月的AI初创公司拉普拉斯智能&#xff08;Laplace AI&#xff09;&#xff0c;已经用原生智能生产力操作平台帮助企业用户实现智能体落地了&#xff01; 平台名为拉普拉斯智能实验室&#xff08;Laplace AI Lab&#xff09;&#xff0c;入口统一&#xff0c;用自然语言…

2024-2025年本田维修电路图线路图接线图资料更新

此次更新了2024-2025年本田车系电路图资料&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表位置等等&#xff01; 汽修帮手汽…

小迪安全v2023 javaWeb项目

小迪安全v2023 javaWeb项目 大体上跟随小迪安全的课程&#xff0c;本意是记录自己的学习历程&#xff0c;不能说是完全原创吧&#xff0c;大家可以关注一下小迪安全&#xff0c;他讲的挺好的。 若有冒犯&#xff0c;麻烦私信移除。 已获得迪の认可&#xff0c;哈哈 文章目录…

【Spring Boot】关系映射开发(一):一对一映射

关系映射开发&#xff08;一&#xff09;&#xff1a;一对一映射 1.认识实体间关系映射1.1 映射方向1.2 ORM 映射类型 2.实现 “一对一” 映射2.1 编写实体2.1.1 新建 Student 实体2.1.2 新建 Card 实体 2.2 编写 Repository 层2.2.1 编写 Student 实体的 Repository2.2.2 编写…

轮换IP是什么?——深入了解轮换IP的特点

大家在日常上网时&#xff0c;可能听说过“轮换IP”这个词。那么&#xff0c;轮换IP到底是什么&#xff1f;它有哪些特点&#xff1f;今天&#xff0c;我们就来揭开轮换IP的神秘面纱。 什么是轮换IP&#xff1f; 简单来说&#xff0c;轮换IP是指定期更换上网时使用的IP地址。…

C++字体库开发二

Font: 字体大小&#xff0c;方向&#xff0c;变换 FontContext: 多语言&#xff0c;更新&#xff0c;基础字体&#xff0c;表情 FontDescription: 字重&#xff0c;子样式&#xff0c;名称&#xff0c;底色 FontDir: 字体目标 FontFace: Regular,Bold特殊字重 FontFamily: 字体…

嵌入式c语言3——自定义数据类型

结构体struct&#xff0c;共用体union 结构体中定义变量&#xff0c;首尾地址相连 对于union&#xff0c;其包含变量对起始地址相同 由于其起始地址相同&#xff0c;则改变其中某一变量值时有可能使得另一个变量值发生改变 enum 枚举&#xff0c;可以用来定义一堆整形常量构成…

itk::ShapedNeighborhoodIterator类C2516问题

错误问题&#xff1a; 1>C:\itk\src-5.3.0\Modules\Core\Common\include\itkShapedNeighborhoodIterator.h(183,1): error C2516: itk::ShapedNeighborhoodIterator<TImage,TBoundaryCondition>::ConstIterator: is not a legal base class 1>C:\itk\src-5.3.0\Mo…

解决Visual Studio 一直弹出管理员身份运行问题(win10/11解决办法)

不知道大家是否有遇到这个问题 解决办法也很简单 找到启动文件 如果是快捷方式就继续打开文件位置 找到这个程序启动项 右键 选择 兼容性疑难解答&#xff08;win11 则需要 按住 shift 右键&#xff09; win10 解决办法 这样操作完后就可以了 win11解决办法按以下选择就行

Android触摸事件分发关键点【笔记摘要】

触摸事件分发&#xff1a;就是一个为了解决触摸事件冲突而设置的机制 1.事件类型 ACTION_DOWN -> ACTION_UP / ACTION_CANCEL ACTION_DOWN -> ACTION_MOVE -> ACTION_MOVE -> ACTION_MOVE -> ACTION_UP / ACTION_CANCEL 这个取消事件ACTION_CANCEL它是一种特殊…

Go语言工程管理

本文内容为Go工程创建和配置开发及简单程序示例。 目录 工程管理 GOPATH 配置GOPATH GOROOT 新建系统变量 配置go工程 添加go path 简单的程序实现 程序代码 开始运行 运行结果 内容解析 总结 工程管理 GOPATH go语言的项目&#xff0c;需要有特定的目录结构进行…

Osg中的智能指针和观察指针

目录 1 设计 内容 1 设计 osg中能够使用智能指针的对象都继承自引用计数类Referenced&#xff0c;观察指针(observer_ptr)与智能指针之间通过ObserverSet相互关联&#xff0c;其中obserserver_ptr直接依赖ObeserverSet。 Referenced不直接依赖ObserverSet类&#xff0c;但可…

springboot java.lang.ClassNotFoundException: dm.jdbc.driver.DmDriver 应该如何解决

遇到的问题&#xff1a;项目中引用了外部的达梦jar包 在idea中正常使用 也能找到dm.jdbc.driver.DmDriver 驱动 但是当通过jenkins 构建部署到服务器上 总是报 ClassNotFoundException: dm.jdbc.driver.DmDriver 找不到驱动 应用到的驱动代码如下格式 排查步骤 1.首先看你的项…

hibernate与jpa学习

jpa是一个规范&#xff0c;hibernate是jpa的实现&#xff0c;是一种框架。 hibernate&#xff1a; ORM框架/持久层框架&#xff08;Object Relational Mapping&#xff09; 它是是一个轻量级开放源代码的对象关系映射框架&#xff0c;hibernate可以自动生成SQL语句&#xff0…