如何防止 React 中过多的 JS 事件处理程序?

ocl*_*yke 7 javascript performance events typescript reactjs

问题

应用程序需要窗口的内部大小。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;
Run Code Online (Sandbox Code Playgroud)

测试

相关的控制台输出显示:

added resize listener
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

环境

  • 反应 16.13.1
  • 打字稿 4.0.3
  • 网络包 4.44.2
  • 通天塔加载器 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- 尽管当效果只发生一次时,这甚至不应该发挥作用
  • 有许多可能的方法可以解决此问题 - 这个问题旨在询问为什么这种预期有效的方法不起作用
  • create-react-app使用 react-scripts 4.0.0在新项目上重现了这个问题

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

ocl*_*yke 0

正如 Patrick Roberts 和 Aleksey L. 在对该问题的评论中指出的那样,这个问题实际上并不是一个问题,因为: