提问人:JalajYadav_Avesta 提问时间:10/17/2023 最后编辑:JalajYadav_Avesta 更新时间:10/17/2023 访问量:83
无法在 React useEffect 中访问 DOM 进行清理 Component Unmount 上的 return 函数
Cannot access DOM for cleanup in the react useEffect return function on Component Unmount
问:
我有以下组件,并通过 DOM 操作将事件列表器附加到其中的按钮。我可以确认 eventListener 已附加,因为单击按钮时,我可以在 devtools 中看到控制台日志。这里的问题是我无法像下面那样在返回功能中删除 eventListener。大多数博客和指南都建议使用 useRef 钩子。我熟悉 useRef 钩子,是的,它确实有效。
import React, { useEffect } from 'react';
function MyCustomComponent() {
useEffect(() => {
// Define the event handler
function handleClick() {
console.log('Button was clicked!');
}
document.getElementById("uniqueId").addEventListener('click', handleClick);
// Cleanup: remove the event listener from the button
return () => {
document.getElementById("uniqueId").removeEventListener('click', handleClick);
};
}, []); // The empty dependency array means this useEffect will run once after the component is mounted
return (
<div>
<button id="uniqueId">Click Me!</button>
</div>
);
}
export default MyCustomComponent;
以上是我面临的问题的缩小版,而不是实际的确切情况。 由于某些特定要求,我在我的项目中提供了一个纯 JS 文件,并尝试使用 dom 操作使用我的 Javscript 代码将 HTML 节点附加到组件中(在这种情况下,假设我使用 dom 操作附加 button#uniqueId),这对我来说工作正常。
但是,当我继续提供清理功能并尝试在清理函数中删除 EventListener 时,我无法访问 DOM 元素,并且它正在添加到 DevTools 中网页的分离节点计数。此问题将增加页面上的负载和网页的潜在内存泄漏。为什么 react 文档建议当我们甚至无法访问其中的 dom 时,可以在返回函数中执行任何清理。
有关该问题的任何进一步澄清,请发表评论。
答:
我有以下组件,并通过 DOM 操作将事件列表器附加到其中的按钮。
通常这不是你在 React 中处理事件的方式,请参阅文档。这不仅不必要地复杂,而且如果您有多个组件,则唯一 ID 可能不再是唯一的。
相反,使用 React 来挂接点击处理程序:
function MyCustomComponent() {
// Define the event handler
function handleClick() {
console.log("Button was clicked!");
}
// Hook it up with React
return (
<div>
<button onClick={handleClick}>Click Me!</button>
</div>
);
}
对于一个简单的元素来说,这可能不是必需的,但是如果你将回调传递给一个子组件,如果其属性没有改变,它可能会避免重新渲染,你可以添加它以使点击处理程序函数稳定。但对于一个简单的人来说,这可能是矫枉过正。button
useCallback
button
在你的问题中,你似乎在说你的代码可能直接在DOM级别工作。如果是这样,则不清楚代码中显示的组件与您实际执行的操作有何关系。
但是,如果你对 React 应用范围之外的元素执行此操作,以至于你无法处理上述事件,那么你处理这个问题的通常方法是保留对该元素的引用,以便你可以从你附加到的同一个处理程序中删除处理程序:
useEffect(() => {
// Define the event handler
function handleClick() {
console.log("Button was clicked!");
}
const element = document.getElementById("uniqueId"); // <−−−−−−−−−−−
element.addEventListener("click", handleClick);
// Cleanup: remove the event listener from the button
return () => {
element.removeEventListener("click", handleClick);
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
};
}, []);
这不仅可以防止在返回时出现问题(因为元素已被删除),还可以防止在元素发生更改时将事件处理程序留在元素上,或者如果事件处理程序暂时与文档分离但又放回原处,等等。您可以保证始终将其从添加它的元素中删除。document.getElementById
null
id
如果您确定不会更改(通常是这种情况)并且元素不会被分离和重新连接,则可以通过可选链接使用防护装置:id
null
useEffect(() => {
// Define the event handler
function handleClick() {
console.log("Button was clicked!");
}
document.getElementById("uniqueId").addEventListener("click", handleClick);
// Cleanup: remove the event listener from the button
return () => {
document.getElementById("uniqueId")?.removeEventListener("click", handleClick);
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−
};
}, []);
不过,我通常会选择第一种方法。
但同样,在答案中显示的代码中,您根本不会使用 、 或 。id
getElementById
addEventListener
另一种选择是添加和删除侦听器,而不是特定元素,使用事件委派来确定是否处理点击:body
useEffect(() => {
// Define the event handler
function handleClick(event) {
// Did the element pass through an element with our desired ID?
const element = event.target.closest("#uniqueId"); // Note the #, that's a CSS selector
if (element /* If we were doing this on anything other than `body`, we'd want: && event.currentTarget.contains(element)*/) {
// Yes, handle it
console.log("Button was clicked!");
}
}
document.body.addEventListener("click", handleClick);
// Cleanup: remove the event listener from the body
return () => {
document.body.removeEventListener("click", handleClick);
};
}, []);
请注意,这将使用具有给定 的当前元素,而不是一开始就具有该元素的元素。(同样,通常不是问题。id
id
评论
button
removeEventListener
评论
div
useRef
divRef.current
unmount
div
[<>]