推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > 21CTO > 缓存 React 事件监听器来提高性能

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

21CTO 2018-10-28

在 js 里面有个不被重视的概念:对象和函数的引用,而这个却直接地影响了 React 的性能。如果你打算创建两个相同的函数,但是却又不相等。你可以试着:

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
const object1 = { x: true }; 
const object3 = object1;
object3.x = false;
object1.x; // false

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

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

如若将组件的 prop 从 { x: 1 } 改为另外一个 { x: 1 },React 将会重新渲染,因为这两个对象在内存上有不用的引用。如果只是将组件的 prop 从上文中的 object1 改为 object3 ,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={() => alert('!')} />
     </div>
   );
 }
}

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

修改

如果函数不依赖于组件(不是 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>
   );
 }
}

但是如果函数是动态的怎么办呢?

修改(高级)

这里有个非常常见的使用情况,在简单的组件里面,有很多独立的动态事件监听器,例如在遍历数组的时候:

class SomeComponent extends React.PureComponent {
 render() {
   return (
     <ul>
       {this.props.list.map(listItem =>
         <li key={listItem.text}>
           <Button onClick={() => alert(listItem.text)} />
         </li>
       )}
     </ul>
   );
 }
}

在这个例子里面,有不确定数量的按钮和监听器,每个按钮都有独立的函数,并且无法在组件SomComponent创建之前知道。要如何解决这个难题呢?

输入记忆,或者更简单的称之为缓存。对于每个唯一的值,创建和缓存对应的函数。对以后这个唯一值的所有引用,都返回之前的缓存函数。

这就是我如何实现上面的例子:

class SomeComponent extends React.PureComponent {
 // Each instance of SomeComponent has a cache of click handlers
 // that are unique to it.
 clickHandlers = {};
 // Generate and/or return a click handler,
 // given a unique identifier.
 getClickHandler(key) {
   // If no click handler exists for this unique identifier, create one.
   if (!Object.prototype.hasOwnProperty.call(this.clickHandlers, key)) {
     this.clickHandlers[key] = () => alert(key);
   }
   return this.clickHandlers[key];
 }
 render() {
   return (
     <ul>
       {this.props.list.map(listItem =>
         <li key={listItem.text}>
           <Button onClick={this.getClickHandler(listItem.text)} />
         </li>
       )}
     </ul>
   );
 }
}

数组中的每一项都会被传入 getClickHandler 方法中。这个方法里面,第一次传值调用的时候,会对应这个唯一的值创建函数,并返回。以后通过这个值调用这个方法的时候,将会不会返回新的函数,相反会返回之前在内存里创建的函数的引用。

最终,重新渲染SomeComponent组件时,不会引起Button组件的重新渲染。相似的,在list里面添加项也会为按钮动态地创建事件监听器。

可能需要费点脑子为事件处理函数创建唯一的标识,来区分不同的函数,但是在遍历里面,没有比每个 JSX 对象生成的 key 更简单得了。

这里对使用 index 当作唯一标识有个提醒:如果数组顺序改了或者有删除项,可能会获得错误的返回。当将数组从 [ ‘soda’, ‘pizza’ ] 改为 [ ‘pizza’ ],同时已经缓存了事件监听器为listeners[0] = () => alert(‘soda’),但点击 index 为 0 的按钮 pizza 的时候,它将会弹出 soda。这也是React建议不要使用数组的索引作为 key 的原因。


英文:Charles Stover  
译文:众成翻译/飞鱼
来源:http://www.zcfy.cc/article/cache-your-react-event-listeners-to-improve-performance


版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《缓存 React 事件监听器来提高性能》的版权归原作者「21CTO」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注21CTO微信公众号

21CTO微信公众号:www_21cto_com

21CTO

手机扫描上方二维码即可关注21CTO微信公众号

21CTO最新文章

精品公众号随机推荐