如何侦听组件外部的单击事件

时间:2014-05-23 05:44:58

标签: reactjs

我希望在下拉组件外部发生点击时关闭下拉菜单。

我该怎么做?

11 个答案:

答案 0 :(得分:69)

使用生命周期方法向文档中添加和删除事件侦听器。

React.createClass({
    handleClick: function (e) {
        if (this.getDOMNode().contains(e.target)) {
            return;
        }
    },

    componentWillMount: function () {
        document.addEventListener('click', this.handleClick, false);
    },

    componentWillUnmount: function () {
        document.removeEventListener('click', this.handleClick, false);
    }
});

查看此组件的第48-54行:https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54

答案 1 :(得分:53)

在元素中,我添加了mousedownmouseup,如下所示:

onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}

然后在父母那里我这样做:

componentDidMount: function () {
    window.addEventListener('mousedown', this.pageClick, false);
},

pageClick: function (e) {
  if (this.mouseIsDownOnCalendar) {
      return;
  }

  this.setState({
      showCal: false
  });
},

mouseDownHandler: function () {
    this.mouseIsDownOnCalendar = true;
},

mouseUpHandler: function () {
    this.mouseIsDownOnCalendar = false;
}

showCal是一个布尔值,当true在我的情况下显示日历并且false隐藏它时。

答案 2 :(得分:17)

查看事件的目标,如果事件直接在组件上,或者该组件的子项,则单击是在内部。否则它在外面。

React.createClass({
    clickDocument: function(e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            // Inside of the component.
        } else {
            // Outside of the component.
        }

    },
    componentDidMount: function() {
        $(document).bind('click', this.clickDocument);
    },
    componentWillUnmount: function() {
        $(document).unbind('click', this.clickDocument);
    },
    render: function() {
        return (
            <div ref='component'>
                ...
            </div> 
        )
    }
});

如果要在许多组件中使用它,那么使用mixin会更好:

var ClickMixin = {
    _clickDocument: function (e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            this.clickInside(e);
        } else {
            this.clickOutside(e);
        }
    },
    componentDidMount: function () {
        $(document).bind('click', this._clickDocument);
    },
    componentWillUnmount: function () {
        $(document).unbind('click', this._clickDocument);
    },
}

请参阅此处的示例:https://jsfiddle.net/0Lshs7mg/1/

答案 3 :(得分:11)

对于您的具体用例,目前接受的答案有点过度设计。如果您想要在用户点击下拉列表时收听,只需使用<select>组件作为父元素,并为其附加onBlur处理程序。

这种方法的唯一缺点是它假设用户已经保持对元素的关注,并且它依赖于表单控件(如果你考虑到{{1,它可能是也可能不是你想要的键也可以集中和模糊元素) - 但这些缺点只是对更复杂的用例的限制,在这种情况下可能需要更复杂的解决方案。

tab

答案 4 :(得分:5)

我为源自组件react-outside-event之外的事件编写了一个通用事件处理程序。

实施本身很简单:

  • 安装组件时,会将事件处理程序附加到window对象。
  • 当事件发生时,组件会检查事件是否来自组件内部。如果没有,则会在目标组件上触发onOutsideEvent
  • 卸载组件时,将删除事件处理程序。
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * @param {ReactClass} Target The component that defines `onOutsideEvent` handler.
 * @param {String[]} supportedEvents A list of valid DOM event names. Default: ['mousedown'].
 * @return {ReactClass}
 */
export default (Target, supportedEvents = ['mousedown']) => {
    return class ReactOutsideEvent extends React.Component {
        componentDidMount = () => {
            if (!this.refs.target.onOutsideEvent) {
                throw new Error('Component does not defined "onOutsideEvent" method.');
            }

            supportedEvents.forEach((eventName) => {
                window.addEventListener(eventName, this.handleEvent, false);
            });
        };

        componentWillUnmount = () => {
            supportedEvents.forEach((eventName) => {
                window.removeEventListener(eventName, this.handleEvent, false);
            });
        };

        handleEvent = (event) => {
            let target,
                targetElement,
                isInside,
                isOutside;

            target = this.refs.target;
            targetElement = ReactDOM.findDOMNode(target);
            isInside = targetElement.contains(event.target) || targetElement === event.target;
            isOutside = !isInside;



            if (isOutside) {
                target.onOutsideEvent(event);
            }
        };

        render() {
            return <Target ref='target' {... this.props} />;
        }
    }
};

要使用该组件,您需要使用更高阶的组件包装目标组件类声明,并定义您要处理的事件:

import React from 'react';
import ReactDOM from 'react-dom';
import ReactOutsideEvent from 'react-outside-event';

class Player extends React.Component {
    onOutsideEvent = (event) => {
        if (event.type === 'mousedown') {

        } else if (event.type === 'mouseup') {

        }
    }

    render () {
        return <div>Hello, World!</div>;
    }
}

export default ReactOutsideEvent(Player, ['mousedown', 'mouseup']);

答案 5 :(得分:4)

我投了其中一个答案,即使它对我不起作用。它最终引导我到这个解决方案。我稍微改变了操作顺序。我在目标上监听mouseDown,在目标上监听mouseUp。如果其中任何一个返回TRUE,我们不会关闭模态。只要在任何地方注册了点击,这两个布尔值{mouseDownOnModal,mouseUpOnModal}就会被设置回假。

componentDidMount() {
    document.addEventListener('click', this._handlePageClick);
},

componentWillUnmount() {
    document.removeEventListener('click', this._handlePageClick);
},

_handlePageClick(e) {
    var wasDown = this.mouseDownOnModal;
    var wasUp = this.mouseUpOnModal;
    this.mouseDownOnModal = false;
    this.mouseUpOnModal = false;
    if (!wasDown && !wasUp)
        this.close();
},

_handleMouseDown() {
    this.mouseDownOnModal = true;
},

_handleMouseUp() {
    this.mouseUpOnModal = true;
},

render() {
    return (
        <Modal onMouseDown={this._handleMouseDown} >
               onMouseUp={this._handleMouseUp}
            {/* other_content_here */}
        </Modal>
    );
}

这样做的好处是所有代码都依赖于子组件,而不是父组件。这意味着重用此组件时没有要复制的样板代码。

答案 6 :(得分:3)

  1. 创建一个跨越整个屏幕的固定图层(.backdrop)。
  2. 将目标元素(.target)置于.backdrop元素之外且具有更高的堆叠索引(z-index)。
  3. 然后,对.backdrop元素的任何点击都将被视为“.target元素之外”。

    .click-overlay {
        position: fixed;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        z-index: 1;
    }
    
    .target {
        position: relative;
        z-index: 2;
    }
    

答案 7 :(得分:1)

参加派对的时间已经很晚了,但是我已经成功地在下拉列表的父元素上设置了一个模糊事件,并使用关联的代码来关闭下拉列表,并且还将mousedown侦听器附加到检查的父元素如果下拉列表是否打开,并且如果事件传播打开则会停止事件传播,以便不会触发模糊事件。

由于mousedown事件会冒泡,因此可以防止儿童在镜头上造成任何模糊现象。

/* Some react component */
...

showFoo = () => this.setState({ showFoo: true });

hideFoo = () => this.setState({ showFoo: false });

clicked = e => {
    if (!this.state.showFoo) {
        this.showFoo();
        return;
    }
    e.preventDefault()
    e.stopPropagation()
}

render() {
    return (
        <div 
            onFocus={this.showFoo}
            onBlur={this.hideFoo}
            onMouseDown={this.clicked}
        >
            {this.state.showFoo ? <FooComponent /> : null}
        </div>
    )
}

...

e.preventDefault()不应该被我调用,但是如果没有它,firefox也不会因为某种原因而玩得很好。适用于Chrome,Firefox和Safari。

答案 8 :(得分:1)

您可以使用ref来实现此目的,类似以下内容应该有效。

<div ref={(element) => { this.myElement = element; }}></div> 添加到您的元素:

handleClickOutside(e) {
  if (!this.myElement.contains(e)) {
    this.setState({ myElementVisibility: false });
  }
}

然后,您可以添加一个函数来处理元素外部的单击,如下所示:

componentWillMount() {
  document.addEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

componentWillUnmount() {
  document.removeEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

最后,添加和删除将挂载的事件侦听器并将卸载。

{{1}}

答案 9 :(得分:0)

我找到了一个更简单的方法。

您只需在模态

上添加onHide(this.closeFunction)即可
<Modal onHide={this.closeFunction}>
...
</Modal>

假设您有关闭模态的功能。

答案 10 :(得分:-1)

使用优秀的react-onclickoutside mixin:

npm install --save react-onclickoutside

然后

var Component = React.createClass({
  mixins: [
    require('react-onclickoutside')
  ],
  handleClickOutside: function(evt) {
    // ...handling code goes here... 
  }
});