Thi*_*man 315 javascript dom reactjs
我正在寻找一种方法来检测是否在组件外发生了单击事件,如本文所述.jQuery nearest()用于查看click事件中的目标是否具有dom元素作为其父元素之一.如果匹配,则click事件属于其中一个子项,因此不被视为在组件之外.
所以在我的组件中,我想将一个单击处理程序附加到窗口.当处理程序触发时,我需要将目标与我的组件的dom子项进行比较.
click事件包含"path"之类的属性,它似乎保存了事件所经过的dom路径.我不确定要比较什么或如何最好地遍历它,我认为有人必须已经把它放在一个聪明的实用功能中......不是吗?
Ben*_*Bud 511
以下解决方案使用ES6并遵循绑定的最佳实践以及通过方法设置ref.
要看它的实际效果:Code Sandbox demo
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>;
}
}
OutsideAlerter.propTypes = {
children: PropTypes.element.isRequired,
};
Run Code Online (Sandbox Code Playgroud)
Pab*_*nda 128
以下是最适合我的解决方案,无需将事件附加到容器:
某些HTML元素可以具有所谓的" 焦点 ",例如输入元素.当这些元素失去焦点时,它们也会响应模糊事件.
要赋予任何元素具有焦点的能力,只需确保其tabindex属性设置为-1以外的任何值.在常规HTML中,可以通过设置tabindex属性,但在React中你必须使用tabIndex(注意大写I).
你也可以通过JavaScript来实现 element.setAttribute('tabindex',0)
这就是我用它来制作自定义的DropDown菜单.
var DropDownMenu = React.createClass({
getInitialState: function(){
return {
expanded: false
}
},
expand: function(){
this.setState({expanded: true});
},
collapse: function(){
this.setState({expanded: false});
},
render: function(){
if(this.state.expanded){
var dropdown = ...; //the dropdown content
} else {
var dropdown = undefined;
}
return (
<div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } >
<div className="currentValue" onClick={this.expand}>
{this.props.displayValue}
</div>
{dropdown}
</div>
);
}
});
Run Code Online (Sandbox Code Playgroud)
Pau*_*ald 87
我被困在同一个问题上.我在这里参加派对有点晚了,但对我来说这是一个非常好的解决方案.希望它对别人有帮助.你需要导入findDOMNode从react-dom
import ReactDOM from 'react-dom';
// ... ?
componentDidMount() {
document.addEventListener('click', this.handleClickOutside, true);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside, true);
}
handleClickOutside = event => {
const domNode = ReactDOM.findDOMNode(this);
if (!domNode || !domNode.contains(event.target)) {
this.setState({
visible: false
});
}
}
Run Code Online (Sandbox Code Playgroud)
您可以创建一个可重用的钩子useComponentVisible.
import { useState, useEffect, useRef } from 'react';
export default function useComponentVisible(initialIsVisible) {
const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
const ref = useRef(null);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsComponentVisible(false);
}
};
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
});
return { ref, isComponentVisible, setIsComponentVisible };
}
Run Code Online (Sandbox Code Playgroud)
然后在组件中添加要执行以下操作的功能:
const DropDown = () => {
const { ref, isComponentVisible } = useComponentVisible(true);
return (
<div ref={ref}>
{isComponentVisible && (<p>Dropdown Component</p>)}
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
在此处查找codesandbox示例.
Bea*_*ith 84
在尝试了很多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它有多完整.
我通过npm安装了模块并将其导入到我的组件中:
import onClickOutside from 'react-onclickoutside'
Run Code Online (Sandbox Code Playgroud)
然后,在我的组件类中,我定义了handleClickOutside方法:
handleClickOutside = () => {
console.log('onClickOutside() method called')
}
Run Code Online (Sandbox Code Playgroud)
在导出我的组件时,我将其包装在onClickOutside():
export default onClickOutside(NameOfComponent)
Run Code Online (Sandbox Code Playgroud)
而已.
Thi*_*man 38
感谢Ben Alpert在discuss.reactjs.org上找到了解决方案.建议的方法将处理程序附加到文档,但结果证明是有问题的.单击树中的某个组件会导致重新渲染,删除更新时单击的元素.因为React的rerender 在调用文档正文处理程序之前发生,所以该元素未被检测为树的"内部".
解决方案是在应用程序根元素上添加处理程序.
主要:
window.__myapp_container = document.getElementById('app')
React.render(<App/>, window.__myapp_container)
Run Code Online (Sandbox Code Playgroud)
零件:
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>
)
}
}
Run Code Online (Sandbox Code Playgroud)
Niy*_*yaz 28
这里没有其他答案对我有用.我试图隐藏模糊的弹出窗口,但由于内容是绝对定位的,onBlur甚至在点击内部内容时也开始了.
这是一种对我有用的方法:
// Inside the component:
onBlur(event) {
// currentTarget refers to this component.
// relatedTarget refers to the element where the user clicked (or focused) which
// triggered this event.
// So in effect, this condition checks if the user clicked outside the component.
if (!event.currentTarget.contains(event.relatedTarget)) {
// do your thing.
}
},
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助.
for*_*d04 28
创建外部单击通知挂钩:
function useOuterClickNotifier(onOuterClick, innerRef) {
useEffect(
() => {
// only add listener, if the element exists
if (innerRef.current) {
document.addEventListener("click", handleClick);
}
// unmount previous first in case inputs have changed
return () => document.removeEventListener("click", handleClick);
function handleClick(e) {
innerRef.current && !innerRef.current.contains(e.target) && onOuterClick(e);
}
},
[onOuterClick, innerRef] // invoke again, if inputs have changed
);
}
Run Code Online (Sandbox Code Playgroud)
在以下任何组件中使用挂钩:
const InnerComp = () => {
const innerRef = useRef(null);
useOuterClickNotifier(
// if you want to optimize performance a bit,
// don't provide an anonymous function here
// See link down under (*1)
e => alert("clicked outside of this component!"),
innerRef
);
return (
<div ref={innerRef}>
inside component
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
* 1 通过跳过效果来优化性能
然后,根据您的用例,您可以在外部click回调中执行某些操作,为简化起见,此处将其存入存根alert("clicked outside of this component!")。例如,使用useStateHook 设置某些状态或调用给定的回调。
useOuterClickNotifier 钩子可以在所有组件中重复使用,而无需像在类解决方案中那样需要包装器组件/渲染道具( Link)中。mousedown,click)IOS仅将某些元素视为可点击的-有关quirksmode,SO答案以及此处的更多信息。为避免这种情况,请选择其他外部点击侦听器元素(不包括在内body)。例如,您可以<div id="root"></div>改为注册反应根的点击,并将其高度扩展到整个视口(查看IOS Codesandbox)。或什至更好的做法是:完全避免使用全局变量,并将一个显式元素传递给useOuterClickNotifierHook,该元素可用于注册外部点击。
感谢@Omar的提示,感谢@Kostas Sarantopoulos提供CSS媒体查询的其他替代方法(请参阅评论)。
希望有帮助。
ono*_*oya 18
[更新]使用Hooks使用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;
Run Code Online (Sandbox Code Playgroud)
使用React解决方案^ 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;
Run Code Online (Sandbox Code Playgroud)
小智 15
带打字稿
function Tooltip(): ReactElement {
const [show, setShow] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent): void {
if (ref.current && !ref.current.contains(event.target as Node)) {
setShow(false);
}
}
// Bind the event listener
document.addEventListener('mousedown', handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener('mousedown', handleClickOutside);
};
});
return (
<div ref={ref}></div>
)
}Run Code Online (Sandbox Code Playgroud)
小智 13
或者:
const onClickOutsideListener = () => {
alert("click outside")
document.removeEventListener("click", onClickOutsideListener)
}
...
return (
<div
onMouseLeave={() => {
document.addEventListener("click", onClickOutsideListener)
}}
>
...
</div>
Run Code Online (Sandbox Code Playgroud)
Oli*_*ari 11
Material-UI 有一个小组件来解决这个问题:https : //material-ui.com/components/click-away-listener/你可以随意挑选它。压缩后重量为 1.5 kB,支持移动、IE 11 和门户。
Kuz*_*ave 11
Ez方式...
const componentRef = useRef();
useEffect(() => {
document.addEventListener("click", handleClick);
return () => document.removeEventListener("click", handleClick);
function handleClick(e: any) {
if(componentRef && componentRef.current){
const ref: any = componentRef.current
if(!ref.contains(e.target)){
// put your action here
}
}
}
}, []);
Run Code Online (Sandbox Code Playgroud)
然后将 ref 放在您的组件上
<div ref={componentRef as any}> My Component </div>
Run Code Online (Sandbox Code Playgroud)
小智 7
只需使用 mui (material-ui) 中的 ClickAwayListener:
<ClickAwayListener onClickAway={handleClickAway}>
{children}
<ClickAwayListener >
Run Code Online (Sandbox Code Playgroud)
有关更多信息,您可以检查:https ://mui.com/base/react-click-away-listener/
Typescript + @ford04 提案的简化版本:
useOuterClick应用程序编程接口const Client = () => {
const ref = useOuterClick<HTMLDivElement>(e => { /* Custom-event-handler */ });
return <div ref={ref}> Inside </div>
};
Run Code Online (Sandbox Code Playgroud)
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)
}
Run Code Online (Sandbox Code Playgroud)
这是我的方法(演示-https: //jsfiddle.net/agymay93/4/):
我创建了一个称为的特殊组件WatchClickOutside,它可以像(我假设JSX语法)那样使用:
<WatchClickOutside onClickOutside={this.handleClose}>
<SomeDropdownEtc>
</WatchClickOutside>
Run Code Online (Sandbox Code Playgroud)
这是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>
);
}
}
Run Code Online (Sandbox Code Playgroud)
对于那些需要绝对定位的人,我选择的一个简单选项是添加一个包装组件,该组件的样式设置为以透明背景覆盖整个页面。然后你可以在这个元素上添加一个 onClick 来关闭你的内部组件。
<div style={{
position: 'fixed',
top: '0', right: '0', bottom: '0', left: '0',
zIndex: '1000',
}} onClick={() => handleOutsideClick()} >
<Content style={{position: 'absolute'}}/>
</div>
Run Code Online (Sandbox Code Playgroud)
就像现在,如果您在内容上添加点击处理程序,该事件也将传播到上方的 div 并因此触发 handlerOutsideClick。如果这不是您想要的行为,只需停止处理程序上的事件传播即可。
<Content style={{position: 'absolute'}} onClick={e => {
e.stopPropagation();
desiredFunctionCall();
}}/>
Run Code Online (Sandbox Code Playgroud)
`
我部分是通过遵循this并遵循React官方文档处理需要react ^ 16.3的refs来做到这一点的。在尝试了这里的其他一些建议之后,这是唯一对我有用的东西......
class App extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentWillMount() {
document.addEventListener("mousedown", this.handleClick, false);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClick, false);
}
handleClick = e => {
/*Validating click is made inside a component*/
if ( this.inputRef.current === e.target ) {
return;
}
this.handleclickOutside();
};
handleClickOutside(){
/*code to handle what to do when clicked outside*/
}
render(){
return(
<div>
<span ref={this.inputRef} />
</div>
)
}
}
Run Code Online (Sandbox Code Playgroud)
这已经有很多答案,但是它们没有解决,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;
Run Code Online (Sandbox Code Playgroud)
import { useClickAway } from "react-use";
useClickAway(ref, () => console.log('OUTSIDE CLICKED'));
Run Code Online (Sandbox Code Playgroud)
小智 5
带 Hook 的打字稿
注意:我使用的是 React 版本 16.3,带有 React.createRef。对于其他版本,请使用 ref 回调。
下拉组件:
interface DropdownProps {
...
};
export const Dropdown: React.FC<DropdownProps> () {
const ref: React.RefObject<HTMLDivElement> = React.createRef();
const handleClickOutside = (event: MouseEvent) => {
if (ref && ref !== null) {
const cur = ref.current;
if (cur && !cur.contains(event.target as Node)) {
// close all dropdowns
}
}
}
useEffect(() => {
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
});
return (
<div ref={ref}>
...
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
207017 次 |
| 最近记录: |