我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述。jQueryclosest()用于查看单击事件的目标是否将dom元素作为其父元素之一。如果存在匹配项,则单击事件属于其中一个子项,因此不被视为在组件之外。
因此,在我的组件中,我想将一个单击处理程序附加到窗口。当处理程序启动时,我需要将目标与组件的dom子级进行比较。
click事件包含类似“path”的财产,它似乎保存了事件经过的dom路径。我不知道该比较什么,或者如何最好地遍历它,我想肯定有人已经把它放在了一个聪明的效用函数中。。。不
在这里尝试了许多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它非常完整。
我通过npm安装了模块并将其导入到组件中:
import onClickOutside from 'react-onclickoutside'
然后,在组件类中,我定义了handleClickOutside方法:
handleClickOutside = () => {
console.log('onClickOutside() method called')
}
导出组件时,我将其包装在onClickOutside()中:
export default onClickOutside(NameOfComponent)
就是这样。
基于Tanner Linsley在2020年夏威夷联合会议上的精彩演讲:
使用OuterClick API
const Client = () => {
const innerRef = useOuterClick(ev => {/*event handler code on outer click*/});
return <div ref={innerRef}> Inside </div>
};
实施
function useOuterClick(callback) {
const callbackRef = useRef(); // initialize mutable ref, which stores callback
const innerRef = useRef(); // 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", handleClick);
return () => document.removeEventListener("click", handleClick);
function handleClick(e) {
if (innerRef.current && callbackRef.current &&
!innerRef.current.contains(e.target)
) callbackRef.current(e);
}
}, []); // no dependencies -> stable click listener
return innerRef; // convenience for client (doesn't need to init ref himself)
}
下面是一个工作示例:
/*自定义挂钩*/函数useOuterClick(回调){const innerRef=useRef();const callbackRef=useRef();//在ref中设置当前回调,然后第二个useEffect使用它useEffect(()=>{//useEffect包装器对于并发模式是安全的callbackRef.current=回调;});使用效果(()=>{document.addEventListener(“单击”,handleClick);return()=>document.removeEventListener(“单击”,handleClick);//从ref中读取最近的回调和innerRefdom节点函数句柄Click(e){如果(内部参考当前&&回调参考当前&&!innerRef.current.contains(e.target)) {callbackRef.current(e);}}}, []); // 无需回调+innerRef-depreturn innerRef;//返回参考;客户端可以省略`useRef`}/*用法*/常量客户端=()=>{const[counter,setCounter]=useState(0);const innerRef=useOuterClick(e=>{//调用处理程序时,计数器状态是最新的alert(`Clicked outside!Increment counter to${counter+1}`);设置计数器(c=>c+1);});返回(<div><p>点击外面</p><div id=“container”ref={innerRef}>内部,计数器:{counter}</div></div>);};ReactDOM.render(<Client/>,document.getElementById(“root”));#容器{边框:1px纯红色;填充:20px;}<script src=“https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js“integrity=”sha256-Ef0vObdWpkMAnxp39TYSLVS/vVUokDE8CDFnx7tjY6U=“crossrorigin=”匿名“></script><script src=“https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js“integrity=”sha256-p2yuFdE8hNZsQ31Qk+s8N+Me2fL5cc6NKXOC0U9uGw=“crossrorigin=”匿名“></script><script>var{useRef,useEffect,useCallback,useState}=反应</script><div id=“root”></div>
要点
useOuterClick利用可变引用提供瘦客户端API包含组件([]deps)的生命周期的稳定单击侦听器客户端可以设置回调,而无需使用callback将其记忆回调主体可以访问最新的属性和状态-没有过时的闭包值
(iOS的侧注)
iOS通常只将某些元素视为可点击的。要使外部单击有效,请选择一个不同于文档的单击侦听器-不向上包括正文。例如,在React根div上添加一个监听器,并扩展其高度,如height:100vh,以捕捉所有外部点击。来源:quicksmod.org
以下解决方案使用ES6并遵循绑定以及通过方法设置ref的最佳实践。
要将其付诸行动:
挂钩实施反应16.3后的类实现反应16.3之前的类实现
挂钩实施:
import React, { useRef, useEffect } from "react";
/**
* Hook that alerts clicks outside of the passed ref
*/
function useOutsideAlerter(ref) {
useEffect(() => {
/**
* Alert if clicked on outside of element
*/
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
alert("You clicked outside of me!");
}
}
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
/**
* Component that alerts if you click outside of it
*/
export default function OutsideAlerter(props) {
const wrapperRef = useRef(null);
useOutsideAlerter(wrapperRef);
return <div ref={wrapperRef}>{props.children}</div>;
}
类实现:
16.3之后
import React, { Component } from "react";
/**
* Component that alerts if you click outside of it
*/
export default class OutsideAlerter extends Component {
constructor(props) {
super(props);
this.wrapperRef = React.createRef();
this.handleClickOutside = this.handleClickOutside.bind(this);
}
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
/**
* Alert if clicked on outside of element
*/
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.current.contains(event.target)) {
alert("You clicked outside of me!");
}
}
render() {
return <div ref={this.wrapperRef}>{this.props.children}</div>;
}
}
16.3之前
import React, { Component } from "react";
/**
* Component that alerts if you click outside of it
*/
export default class OutsideAlerter extends Component {
constructor(props) {
super(props);
this.setWrapperRef = this.setWrapperRef.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
}
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
/**
* Set the wrapper ref
*/
setWrapperRef(node) {
this.wrapperRef = node;
}
/**
* Alert if clicked on outside of element
*/
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
alert("You clicked outside of me!");
}
}
render() {
return <div ref={this.setWrapperRef}>{this.props.children}</div>;
}
}
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赋给特定元素。