【问题标题】:How can one prevent excess JS event handlers in React?如何在 React 中防止过多的 JS 事件处理程序?
【发布时间】:2021-02-12 13:06:14
【问题描述】:

问题

应用程序需要窗口的内部大小。 React 模式建议在一次性效果挂钩中注册一个事件监听器。对window.addEventListener 的调用似乎只发生了一次,但事件侦听器会堆积起来并对性能产生负面影响。

代码

这是重现此问题的精简源代码

import React, {useState, useEffect} from 'react';

const getWindowRect = () => {
  return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
}

// custom hook to track the window dimensions
const useWindowRect = () => {
  // use state for the aspect ratio
  let [rect, setRect] = useState(getWindowRect);

  // useEffect w/o deps should only be called once
  useEffect(() => {
    const resizeHandler = () => { setRect(getWindowRect()); }; 
    
    window.addEventListener('resize', resizeHandler);
    console.log('added resize listener');

    // return the cleanup function
    return () => {
      window.removeEventListener('resize', resizeHandler);
      console.log('removed resize listener');
    }
  }, []);

  // return the up-to-date window rect
  return rect;
}

const App = () => {
  const window_rect = useWindowRect();
  return <div>
    {window_rect.width/window_rect.height}
  </div>
};

export default App;

测试

相关控制台输出显示:

added resize listener

这是监听器只添加一次的预期结果,无论应用重新渲染多少次

参考,窗口未调整大小 最大侦听器:56

调整性能,数百个听众积累最大听众:900+

在 window.addEventListener 注释掉的情况下调整性能最大听众数:49

环境

  • 反应 16.13.1
  • TypeScript 4.0.3
  • WebPack 4.44.2
  • Babel 加载器 8.1.0
  • Chrome 86.0.4240.111(官方版本)(x86_64)

演示

假设很难在 JSFiddle 或 CodePen 上运行性能指标我在这个 repo 中提供了一个完整的演示:oclyke-exploration/resize-handler-performance 只要你安装了 node 和 yarn,你就可以轻松运行演示。

一般性讨论

  • 这种方法在没有这些症状之前有效,但是环境略有不同并且不包括 TypeScript(这可能是由交叉编译引起的吗?)
  • 我已经简要调查了提供给 window.removeEventListener 的函数引用是否与提供给 window.addEventListener 的函数引用相同 - 尽管当效果只出现一次时,这甚至不应该发挥作用
  • 有许多可能的方法可以解决此问题 - 此问题旨在询问为什么这种预期有效的方法不起作用
  • 使用 react-scripts 4.0.0 在新的 create-react-app 项目中重现了此问题

问

有人对此问题有解释吗?我难住了! (相关:其他人可以重现这个问题吗?)

【问题讨论】:

  • 不会解决问题,但是提示:将useState&lt;DOMRect&gt;(getWindowRect()); 更改为useState(getWindowRect);,以免每次渲染都调用DOMRect。也可以在组件外部声明该函数以避免每次渲染都重新创建它。
  • 我不确定这是否会解决,但您可以尝试将resizeHandler 定义为主要的钩子主体并使用useCallback 对其进行记忆。这样,应该只有一个事件侦听器,其引用被保存。编辑:我假设您已经验证效果只运行一次
  • @CertainPerformance 感谢rect 提示的延迟初始化!对于其他人,这里是lazy initialization in useState 的简洁解释
  • 我认为问题与您的代码无关,看起来事件处理程序是由invokeGuardedCallbackDev 在react-dom.development.js 中注册的。它也看起来像they're cleaned up after long enough
  • 好吧好吧好吧...我知道会有一些聪明人可以让我直截了当。刚刚在生产模式下进行了测试,实际上这消除了这些症状。非常感谢 PatrickRoberts 和 Aleksey L.

标签: javascript reactjs typescript performance events


【解决方案1】:

正如 Patrick Roberts 和 Aleksey L. 在 cmets 中关于这个问题所指出的,这个问题实际上并不是一个问题,因为:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-15
    • 1970-01-01
    • 2021-06-27
    • 2020-04-02
    • 2010-09-28
    • 2014-07-09
    • 2017-01-18
    相关资源
    最近更新 更多