我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述。jQueryclosest()用于查看单击事件的目标是否将dom元素作为其父元素之一。如果存在匹配项,则单击事件属于其中一个子项,因此不被视为在组件之外。
因此,在我的组件中,我想将一个单击处理程序附加到窗口。当处理程序启动时,我需要将目标与组件的dom子级进行比较。
click事件包含类似“path”的财产,它似乎保存了事件经过的dom路径。我不知道该比较什么,或者如何最好地遍历它,我想肯定有人已经把它放在了一个聪明的效用函数中。。。不
这已经有很多答案了,但它们没有解决e.stopPropagation()和阻止单击要关闭的元素之外的react链接的问题。
由于React有自己的人工事件处理程序,您无法将文档用作事件侦听器的基础。在这之前,您需要e.stopPropagation(),因为React使用文档本身。如果改用document.querySelector('body')。您可以防止点击React链接。下面是我如何实现单击外部并关闭的示例。这使用ES6和React 16.3。
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
};
this.insideContainer = React.createRef();
}
componentWillMount() {
document.querySelector('body').addEventListener("click", this.handleClick, false);
}
componentWillUnmount() {
document.querySelector('body').removeEventListener("click", this.handleClick, false);
}
handleClick(e) {
/* Check that we've clicked outside of the container and that it is open */
if (!this.insideContainer.current.contains(e.target) && this.state.isOpen === true) {
e.preventDefault();
e.stopPropagation();
this.setState({
isOpen: false,
})
}
};
togggleOpenHandler(e) {
e.preventDefault();
this.setState({
isOpen: !this.state.isOpen,
})
}
render(){
return(
<div>
<span ref={this.insideContainer}>
<a href="#open-container" onClick={(e) => this.togggleOpenHandler(e)}>Open me</a>
</span>
<a href="/" onClick({/* clickHandler */})>
Will not trigger a click when inside is open.
</a>
</div>
);
}
}
export default App;
在这里尝试了许多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它非常完整。
我通过npm安装了模块并将其导入到组件中:
import onClickOutside from 'react-onclickoutside'
然后,在组件类中,我定义了handleClickOutside方法:
handleClickOutside = () => {
console.log('onClickOutside() method called')
}
导出组件时,我将其包装在onClickOutside()中:
export default onClickOutside(NameOfComponent)
就是这样。
https://stackoverflow.com/a/42234988/9536897解决方案在手机上不起作用。
您可以尝试:
// returns true if the element or one of its parents has the class classname
hasSomeParentTheClass(element, classname) {
if(element.target)
element=element.target;
if (element.className&& element.className.split(" ").indexOf(classname) >= 0) return true;
return (
element.parentNode &&
this.hasSomeParentTheClass(element.parentNode, classname)
);
}
componentDidMount() {
const fthis = this;
$(window).click(function (element) {
if (!fthis.hasSomeParentTheClass(element, "myClass"))
fthis.setState({ pharmacyFocus: null });
});
}
在视图中,将className赋给特定元素。
我在discuss.reactjs.org上找到了一个解决方案,这要感谢Ben Alpert。单击我的树中的一个组件会导致一个重新阅读程序,在更新时删除了单击的元素。由于React的重读发生在调用文档正文处理程序之前,因此元素未被检测为“在”树中。
解决方案是在应用程序根元素上添加处理程序。
主要:
window.__myapp_container = document.getElementById('app')
React.render(<App/>, window.__myapp_container)
组件:
import { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
export default class ClickListener extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
onClickOutside: PropTypes.func.isRequired
}
componentDidMount () {
window.__myapp_container.addEventListener('click', this.handleDocumentClick)
}
componentWillUnmount () {
window.__myapp_container.removeEventListener('click', this.handleDocumentClick)
}
/* using fat arrow to bind to instance */
handleDocumentClick = (evt) => {
const area = ReactDOM.findDOMNode(this.refs.area);
if (!area.contains(evt.target)) {
this.props.onClickOutside(evt)
}
}
render () {
return (
<div ref='area'>
{this.props.children}
</div>
)
}
}
非侵入性方式无需添加另一个DIV EL。
注意:React可能会说findDomNode已弃用,但到目前为止,我还没有遇到任何问题
@异常:单击要忽略的类
@idException:单击时忽略的id
import React from "react"
import ReactDOM from "react-dom"
type Func1<T1, R> = (a1: T1) => R
export function closest(
el: Element,
fn: (el: Element) => boolean
): Element | undefined {
let el_: Element | null = el;
while (el_) {
if (fn(el_)) {
return el_;
}
el_ = el_.parentElement;
}
}
let instances: ClickOutside[] = []
type Props = {
idException?: string,
exceptions?: (string | Func1<MouseEvent, boolean>)[]
handleClickOutside: Func1<MouseEvent, void>
}
export default class ClickOutside extends React.Component<Props> {
static defaultProps = {
exceptions: []
};
componentDidMount() {
if (instances.length === 0) {
document.addEventListener("mousedown", this.handleAll, true)
window.parent.document.addEventListener(
"mousedown",
this.handleAll,
true
)
}
instances.push(this)
}
componentWillUnmount() {
instances.splice(instances.indexOf(this), 1)
if (instances.length === 0) {
document.removeEventListener("mousedown", this.handleAll, true)
window.parent.document.removeEventListener(
"mousedown",
this.handleAll,
true
)
}
}
handleAll = (e: MouseEvent) => {
const target: HTMLElement = e.target as HTMLElement
if (!target) return
instances.forEach(instance => {
const { exceptions, handleClickOutside: onClickOutside, idException } = instance.props as Required<Props>
let exceptionsCount = 0
if (exceptions.length > 0) {
const { functionExceptions, stringExceptions } = exceptions.reduce(
(acc, exception) => {
switch (typeof exception) {
case "function":
acc.functionExceptions.push(exception)
break
case "string":
acc.stringExceptions.push(exception)
break
}
return acc
},
{ functionExceptions: [] as Func1<MouseEvent, boolean>[], stringExceptions: [] as string[] }
)
if (functionExceptions.length > 0) {
exceptionsCount += functionExceptions.filter(
exception => exception(e) === true
).length
}
if (exceptionsCount === 0 && stringExceptions.length > 0) {
const el = closest(target, (e) => stringExceptions.some(ex => e.classList.contains(ex)))
if (el) {
exceptionsCount++
}
}
}
if (idException) {
const target = e.target as HTMLDivElement
if (document.getElementById(idException)!.contains(target)) {
exceptionsCount++
}
}
if (exceptionsCount === 0) {
// eslint-disable-next-line react/no-find-dom-node
const node = ReactDOM.findDOMNode(instance)
if (node && !node.contains(target)) {
onClickOutside(e)
}
}
})
};
render() {
return React.Children.only(this.props.children)
}
}
用法
<ClickOutside {...{ handleClickOutside: () => { alert('Clicked Outside') } }}>
<div >
<div>Breathe</div>
</div>
</ClickOutside>