【发布时间】: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
在 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<DOMRect>(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