虽然我知道,这个问题已被多次询问,但通常的答案是"你传递的是副本,而不是原来的#34; 。除非我理解错误,否则这不是我的问题。
我使用以下代码在React中创建了一个模态:
const Modal: React.StatelessComponent<Props> =
({ isOpen, closeModal, closeOnClick = true, style, children }: Props) => {
const closeOnEsc: (e: KeyboardEvent) => void = (e: KeyboardEvent) => {
e.stopImmediatePropagation();
if (e.keyCode === 27) {
closeModal();
}
};
if (isOpen) {
document.body.classList.add('freezeScroll');
window.addEventListener('keyup', closeOnEsc, { once: true });
return (
<div className={styles.modal} onClick={closeOnClick ? closeModal : undefined} >
<div className={`${styles.modalContent} ${closeOnClick ? styles.clickable : undefined} ${style}`} >
{children}
</div>
</div>
);
}
document.body.classList.remove('freezeScroll');
window.removeEventListener('keyup', closeOnEsc, { once: true });
return null;
};
我只添加了{once:true}选项,因为我无法将其删除。
关闭模态后,会调用window.removeEventListener,但它不会删除事件监听器。
任何人都可以帮我找出原因吗?
答案 0 :(得分:0)
我不会亲自手动绑定SFC中的侦听器。如果你真的想要,我只是让回调删除听众。
在closeOnEsc
回调中,添加window.removeEventListener('keyup', closeOnEsc);
这个问题,如果在没有调用回调的情况下卸载组件,那么你的内存泄漏很少。
我会使用类组件,并确保在componentWillUnmount
生命周期钩子中也删除了侦听器。
答案 1 :(得分:0)
您不应直接在渲染中附加事件侦听器。这些不是幂等的副作用,它会导致内存泄漏。在每次渲染时(即使它没有提交给 DOM),您都在分配事件侦听器在您的模态关闭后,您只删除最后一个。正确的做法是使用 useEffect
(或 useLayoutEffect
钩子),或者将您的组件转换为类并使用生命周期方法。
这是带有钩子的带注释的示例:
const Modal: React.StatelessComponent<Props> = ({
isOpen,
closeModal,
closeOnClick = true,
style,
children,
}: Props) => {
useEffect(
function () {
// we don't need to add listener when modal is closed
if (!isOpen) return;
// we are closuring function to properly remove event listener later
const closeOnEsc: (e: KeyboardEvent) => void = (e: KeyboardEvent) => {
e.stopImmediatePropagation();
if (e.keyCode === 27) {
closeModal();
}
};
window.addEventListener("keyup", closeOnEsc);
// We return cleanup function that will be executed either before effect will be fired again,
// or when componen will be unmounted
return () => {
// since we closured function, we will definitely remove listener
window.removeEventListener("keyup", closeOnEsc);
};
},
[isOpen]
);
useEffect(
function () {
// it is very tricky with setting class on body, since we theoretically can have multiple open modals simultaneously
// if one of this model will be closed, it will remove class from body.
// This is not particularly good solution - it is better to have some centralized place to set this class.
// This is why I moved it into separate effect
if (isOpen) {
document.body.classList.add("freezeScroll");
} else {
document.body.classList.remove("freezeScroll");
}
// just in case if modal would be unmounted, we reset body
return () => {
document.body.classList.remove("freezeScroll");
};
},
[isOpen]
);
if (isOpen) {
return (
<div
className={styles.modal}
onClick={closeOnClick ? closeModal : undefined}
>
<div
className={`${styles.modalContent} ${
closeOnClick ? styles.clickable : undefined
} ${style}`}
>
{children}
</div>
</div>
);
}
return null;
};
再说一遍。非常重要的是,类和函数组件的渲染方法没有任何非幂等的副作用。这种情况下的幂等性意味着如果我们多次调用某个副作用,它不会改变结果。这就是您可以从渲染中调用 console.log
的原因 - 它是幂等的,至少从我们的角度来看是这样。与反应钩子相同。您可以(并且应该!)在每次渲染时调用它们,而不会弄乱 React 内部状态。将新的事件侦听器附加到 DOM 或窗口不是幂等的,因为在每次渲染时您都附加了新的事件侦听器而无需清除前一个。
从技术上讲,您可以先删除事件侦听器,然后在没有 useEffect
的情况下附加新的,但在这种情况下,您必须以某种方式保留渲染之间的侦听器身份(引用),或者立即从窗口中删除所有 keyup 事件.前者很难做到(您在每次渲染时都创建新函数),后者会干扰程序的其他部分。