我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述。jQueryclosest()用于查看单击事件的目标是否将dom元素作为其父元素之一。如果存在匹配项,则单击事件属于其中一个子项,因此不被视为在组件之外。
因此,在我的组件中,我想将一个单击处理程序附加到窗口。当处理程序启动时,我需要将目标与组件的dom子级进行比较。
click事件包含类似“path”的财产,它似乎保存了事件经过的dom路径。我不知道该比较什么,或者如何最好地遍历它,我想肯定有人已经把它放在了一个聪明的效用函数中。。。不
@ford04提案的字体+简化版:
使用OuterClick API
const Client = () => {
const ref = useOuterClick<HTMLDivElement>(e => { /* Custom-event-handler */ });
return <div ref={ref}> Inside </div>
};
实施
export default function useOuterClick<T extends HTMLElement>(callback: Function) {
const callbackRef = useRef<Function>(); // initialize mutable ref, which stores callback
const innerRef = useRef<T>(null); // returned to client, who marks "border" element
// update cb on each render, so second useEffect has access to current value
useEffect(() => { callbackRef.current = callback; });
useEffect(() => {
document.addEventListener("click", _onClick);
return () => document.removeEventListener("click", _onClick);
function _onClick(e: any): void {
const clickedOutside = !(innerRef.current?.contains(e.target));
if (clickedOutside)
callbackRef.current?.(e);
}
}, []); // no dependencies -> stable click listener
return innerRef; // convenience for client (doesn't need to init ref himself)
}
对于那些需要绝对定位的人,我选择的一个简单选项是添加一个包装器组件,该组件的样式是以透明背景覆盖整个页面。然后可以在这个元素上添加一个onClick来关闭内部组件。
<div style={{
position: 'fixed',
top: '0', right: '0', bottom: '0', left: '0',
zIndex: '1000',
}} onClick={() => handleOutsideClick()} >
<Content style={{position: 'absolute'}}/>
</div>
现在,如果您在内容上添加一个单击处理程序,那么事件也将传播到上面的div,从而触发handlerOutsideClick。如果这不是您想要的行为,只需停止处理程序上的事件进程。
<Content style={{position: 'absolute'}} onClick={e => {
e.stopPropagation();
desiredFunctionCall();
}}/>
`
这是我解决问题的方法
我从我的自定义钩子返回一个布尔值,当这个值发生变化时(如果单击在我作为参数传递的ref之外,则为true),这样我就可以用useEffect钩子捕捉到这个变化,我希望你能清楚。
下面是一个活生生的例子:codesandbox上的实时示例
import { useEffect, useRef, useState } from "react";
const useOutsideClick = (ref) => {
const [outsieClick, setOutsideClick] = useState(null);
useEffect(() => {
const handleClickOutside = (e) => {
if (!ref.current.contains(e.target)) {
setOutsideClick(true);
} else {
setOutsideClick(false);
}
setOutsideClick(null);
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
return outsieClick;
};
export const App = () => {
const buttonRef = useRef(null);
const buttonClickedOutside = useOutsideClick(buttonRef);
useEffect(() => {
// if the the click was outside of the button
// do whatever you want
if (buttonClickedOutside) {
alert("hey you clicked outside of the button");
}
}, [buttonClickedOutside]);
return (
<div className="App">
<button ref={buttonRef}>click outside me</button>
</div>
);
}