专栏名称: 众成翻译
翻译,求知的另一种表达
目录
相关文章推荐
奔流新闻  ·  中国女篮领队王芳卸任 ·  8 小时前  
奔流新闻  ·  中国女篮领队王芳卸任 ·  8 小时前  
新浪体育  ·  中国巨人遭 KO 倒下 他其实早就该输了 ·  昨天  
现代快报  ·  吴艳妮等,南京夺冠! ·  2 天前  
上饶新闻  ·  收藏!上饶最新油菜花期赏花攻略出炉~ ·  2 天前  
上饶新闻  ·  收藏!上饶最新油菜花期赏花攻略出炉~ ·  2 天前  
51好读  ›  专栏  ›  众成翻译

缓存 React 事件监听器来提高性能

众成翻译  · 掘金  ·  · 2021-02-04 19:48

正文

阅读 62

缓存 React 事件监听器来提高性能

译者:飞鱼

原文链接

在 js 里面有个不被重视的概念:对象和函数的引用,而这个却直接地影响了 React 的性能。如果你打算创建两个相同的函数,但是却又不相等。你可以试着:(译者注:无法在markdown中插入代码,请查看原文代码!)

const functionOne = function() { alert('Hello world!'); }; 
const functionTwo = function() { alert('Hello world!'); }; 
functionOne === functionTwo; // false
复制代码

如果将一个变量指向一个已经存在的函数,看看它们的不同:

const functionThree = function() { 
	alert('Hello world!');
 }; 
const functionFour = functionThree; 
functionThree === functionFour; // true
复制代码

对象也是这样的。

const object1 = {}; 
const object2 = {}; 
const object3 = object1; 
object1 === object2; // false 
object1 === object3; // true
复制代码

如果你学过其他语言,可能会熟悉指针。每次你创建对象的时候,你都会为其分配设备内存。当声明 oject1 = {} 的时候,将会在用户的 RAM 中创建一串字节给到 object1 。可想而知, object1 就是一个保存了键值对存放在 RAM 的地址。而声明 object2 = {} ,将会在 RAM 中创建另外一串不同的字节给到 object2 object 上地址和 object2 的一样吗?不是的。这也为什么这两个变量的是不相等。他们的键值对可能会完全相同,但是他们在内存中的地址是不一样的,这才是会被比较的地方。

若使得 object3 = object1 ,会让 object3 的值为 object1 的地址。这不是一个新的对象。内存中的位置是一样的。可以如下验证:

const object1 = { x: true }; 
const object3 = object1;
object3.x = false; object1.x; // false
复制代码

这个例子里,在内存中创建对象并指向 object1 。让后让 object3 等于同样的内存地址。通过修改 object3 ,可以改变对应内存中的值,这也意味着所有指向该内存的变量都会被修改。 obect1 ,仍指向该内存,所以值也被改变了。

初级工程师会犯这种非常常见的错误,并且需要深入学习相关教程;只是本文是讨论 React 性能的,甚至是对变量引用有较深资历的开发者也可能需要学习。

这个和 React 有什么关系呢?React 有个节省执行时间的聪明方式,可以优化性能:如果组件的 props 和 state 都没有变化,render 的输出必然也是没有变化的。很清晰的,如果所有的都一样,那就意味着没有变化。如果没有变化, render 必须返回相同的输出,就不用执行了。这使得 React 更加快速,按需渲染。

React 采用和 JavaScript 一样的方式,通过简单的 == 操作符来判断 props 和 state 是否有变化。React 不会深入比较对象是否相等。深对比是对比对象的每一个键值对,而不是对比内存地址。React 处理方式就是浅对比,仅仅是对比一下引用是否相同而已。

如若将组件的 prop 从 { x: 1 } 改为另外一个 { x: 1 } ,React 将会重新渲染,因为这两个对象在内存上有不用的引用。如果只是将组件的 prop 从上文中的 object1 改为 object3 ,React 是不会重新渲染的,应为这两个对象是同一个引用。

在 Javascript,函数也是同样的处理方式。如果 React 接收到不同内存地址而功能相同的函数,React 也会重新渲染。如果 React 接收到相同函数的引用,就会不重新渲染。

在代码审核的时候,我就遇到下面这种常见误用的场景

class SomeComponent extends React.PureComponent {
	get instructions() { 
		if (this.props.do) { return 'Click the button: '; } 
		return 'Do NOT click the button: '; 
	} 
	render() { 
		return ( <div> {this.instructions} <Button onClick={() =&gt; alert('!')} /></div> ); 
	} 
}
复制代码

这是非常直接的一个组件。当按钮被点击的时候,就 alert。instructions 用来表示是否点击了按钮。而 SomeComponent 的 prop 的 do={true} do={false} 决定了 instructions。

这里有问题的是,当 SomeComponent 重新渲染的时候(例如 do 属性从 true 切换到 false), Button 也会重新渲染!尽管每次这个 onClick 方法都是相同的,但是每次渲染都会被重新创建。每次渲染都会在内存中创建新的函数(因为会在 render 函数里重新创建),一个指向新内存地址的引用被传递到 &lt;Button /&gt; ,虽然输入完全没有变化,该 Button 组件还是会重新渲染。

修改

如果函数不依赖于组件(不是 this 上下文),你可以在组件的外部定义它。所有的组件实例都会用到相同的引用,因为都是同一个函数。

const createAlertBox = () => alert('!'); 
class SomeComponent extends React.PureComponent { 
	get instructions() { 
		if (this.props.do) { return 'Click the button: '; } 
		return 'Do NOT click the button: '; 
	} 
	render() { 
		return ( <div> {this.instructions} <Button onClick={createAlertBox} /> </div> ); 
	} 
}
复制代码

和前面的例子相反, createAlertBox 在每次渲染中仍然有着有相同的引用。因此按钮就不会重新渲染了。

Button 就像一个又小又快速渲染的组件,你可能在大型、复杂、渲染速度慢的组件里面看到这些行内的定义,在 React 应用里面真的会有很多很多。最好不要在渲染方法里面定义这些函数。

如果函数确实依赖于组件,使得你不能在组件外部定义,你可以将组件的方法作为事件处理传递过去。

class SomeComponent extends React.PureComponent { 
	createAlertBox = () => { 
		alert(this.props.message); }; 
		get instructions() { 
			if (this.props.do) { 
				return 'Click the button: '; 
			} 
			return 'Do NOT click the button: '; 
		} 
		render() { 
			return ( <div> {this.instructions} <Button onClick={this.createAlertBox} />; </div> ); 
		}
}
复制代码

在本例中,每个 SomeComponent 的实例有不同的告警方式。按钮的点击事件处理需要独立于 SomeComponent 。通过传递 createAlertBox 方法,他就和 SomeComponent 是否渲染无关了。甚至和 message 这个属性是否修改也没有关系。 createAlertBox 的内存地址没有改变,意味着 Button 没有重新渲染。这可以节省运行时间并提升应用的渲染速度。







请到「今天看啥」查看全文