我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述。jQueryclosest()用于查看单击事件的目标是否将dom元素作为其父元素之一。如果存在匹配项,则单击事件属于其中一个子项,因此不被视为在组件之外。
因此,在我的组件中,我想将一个单击处理程序附加到窗口。当处理程序启动时,我需要将目标与组件的dom子级进行比较。
click事件包含类似“path”的财产,它似乎保存了事件经过的dom路径。我不知道该比较什么,或者如何最好地遍历它,我想肯定有人已经把它放在了一个聪明的效用函数中。。。不
[更新]使用挂钩的React ^16.8解决方案
代码沙盒
import React, { useEffect, useRef, useState } from 'react';
const SampleComponent = () => {
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = e => {
if (!myRef.current.contains(e.target)) {
setClickedOutside(true);
}
};
const handleClickInside = () => setClickedOutside(false);
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
});
return (
<button ref={myRef} onClick={handleClickInside}>
{clickedOutside ? 'Bye!' : 'Hello!'}
</button>
);
};
export default SampleComponent;
反应溶液^16.3:
代码沙盒
import React, { Component } from "react";
class SampleComponent extends Component {
state = {
clickedOutside: false
};
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
myRef = React.createRef();
handleClickOutside = e => {
if (!this.myRef.current.contains(e.target)) {
this.setState({ clickedOutside: true });
}
};
handleClickInside = () => this.setState({ clickedOutside: false });
render() {
return (
<button ref={this.myRef} onClick={this.handleClickInside}>
{this.state.clickedOutside ? "Bye!" : "Hello!"}
</button>
);
}
}
export default SampleComponent;
所有提出的解决方案都假设可以将事件添加到文档中,并依赖于本机方法.contains()来区分事件是在当前组件内部还是外部触发的
ref.current.contains(event.target)
但这在React中并不总是有效的。事实上,在React中,React.createPortal API允许从一个组件中指定一个新的真实父组件,JSX在该组件中被呈现,但同时,事件冒泡被模拟为组件在声明的位置呈现(即React.cCreatePortal被调用的位置)。
这是通过将所有事件附加到应用程序根元素并在Javascript中模拟事件来实现的。
因此,在这种情况下,所提出的解决方案被打破了,因为在门户元素内部的单击,对于标准HTML来说,在当前元素之外,实际上应该像在内部一样进行处理。
所以我重写了这个问题的一个注释中提出的解决方案,并对其进行了重构以使用功能组件。这也适用于一个或多个嵌套门户。
export default function OutsideClickDetector({onOutsideClick, Component ='div', ...props} : OutsideClickDetectorProps) {
const isClickInside = useRef<boolean>(false);
const onMouseDown = () => {
isClickInside.current = true;
};
const handleBodyClick = useCallback((e) => {
if(!isClickInside.current) {
onOutsideClick?.(e);
}
isClickInside.current = false;
}, [isClickInside, onOutsideClick]);
useEffect(() => {
document.addEventListener('mousedown', handleBodyClick);
return () => document.removeEventListener('mousedown', handleBodyClick);
});
return <Component onMouseDown={onMouseDown} onMouseUp={() => isClickInside.current = false}{...props} />;
}
这是我的方法(演示-https://jsfiddle.net/agymay93/4/):
我创建了一个名为WatchClickOutside的特殊组件,它可以像这样使用(我假设JSX语法):
<WatchClickOutside onClickOutside={this.handleClose}>
<SomeDropdownEtc>
</WatchClickOutside>
以下是WatchClickOutside组件的代码:
import React, { Component } from 'react';
export default class WatchClickOutside extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentWillMount() {
document.body.addEventListener('click', this.handleClick);
}
componentWillUnmount() {
// remember to remove all events to avoid memory leaks
document.body.removeEventListener('click', this.handleClick);
}
handleClick(event) {
const {container} = this.refs; // get container that we'll wait to be clicked outside
const {onClickOutside} = this.props; // get click outside callback
const {target} = event; // get direct click event target
// if there is no proper callback - no point of checking
if (typeof onClickOutside !== 'function') {
return;
}
// if target is container - container was not clicked outside
// if container contains clicked target - click was not outside of it
if (target !== container && !container.contains(target)) {
onClickOutside(event); // clicked outside - fire callback
}
}
render() {
return (
<div ref="container">
{this.props.children}
</div>
);
}
}
在这里尝试了许多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它非常完整。
我通过npm安装了模块并将其导入到组件中:
import onClickOutside from 'react-onclickoutside'
然后,在组件类中,我定义了handleClickOutside方法:
handleClickOutside = () => {
console.log('onClickOutside() method called')
}
导出组件时,我将其包装在onClickOutside()中:
export default onClickOutside(NameOfComponent)
就是这样。