如何监听组件外的单击事件

当点击发生在下拉组件之外时,我想要closures下拉菜单。

我怎么做?

在元素中,我添加了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隐藏它。

使用生命周期方法将事件侦听器添加到文档并将其删除。

 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

查看事件的目标,如果事件直接在组件上,或者组件的子组件上,那么点击就在里面。 否则它在外面。

 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/

对于您的具体使用情况,目前接受的答案是过度工程devise。 如果要监听用户单击下拉列表时只需使用<select>组件作为父元素,并将onBlur处理程序附加到它。

这种方法的唯一缺点是它假定用户已经把注意力集中在元素上了,它依赖于一个表单控件(如果考虑到tab键也是焦点和模糊的话,这可能是也可能不是你想要的元素) – 但是这些缺点仅仅是更复杂用例的限制,在这种情况下,可能需要更复杂的解决scheme。

  var Dropdown = React.createClass({ handleBlur: function(e) { // do something when user clicks outside of this element }, render: function() { return ( <select onBlur={this.handleBlur}> ... </select> ); } }); 

我写了一个通用的事件处理程序,用于事件发生在组件之外 , 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']); 

我投了一个答案,即使它不适合我。 它最终导致我到这个解决scheme。 我稍微改变了操作顺序。 我侦听目标上的mouseDown和目标上的mouseUp。 如果其中任何一个返回TRUE,我们不会closures模式。 一旦点击被注册,任何地方,这两个布尔值{mouseDownOnModal,mouseUpOnModal}被设置为false。

 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> ); } 

这样做的好处是所有的代码都是由子组件而不是父代来完成的。 这意味着在重新使用该组件时,没有可以复制的样板代码。

  1. 创build跨越整个屏幕( .backdrop )的固定图层。
  2. 使目标元素( .target )位于.backdrop元素之外,并具有更大的堆栈索引( z-index )。

然后任何点击.backdrop元素将被视为“ .target元素之外”。

 .click-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 1; } .target { position: relative; z-index: 2; } 
  • 这里是一个例子: http : //jsfiddle.net/LHmwd/
  • 更多的讨论: https : //github.com/facebook/react/issues/579

你可以使用ref来实现这个,像下面的东西应该可以工作。

添加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 } 

超晚了派对,但我已经成功的设置模糊事件的下拉母关联元素与关联的代码来closures下拉,并附加一个mousedown监听器的父元素,检查下拉是否打开或者不是,如果它打开,将停止事件传播,以便模糊事件不会被触发。

由于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。

我发现了一个更简单的方法。

你只需要在模式上添加onHide(this.closeFunction)

 <Modal onHide="this.closeFunction"> ... </Modal> 

假设你有一个closures模式的function。

使用优秀的react-onclickoutside mixin:

 npm install --save react-onclickoutside 

接着

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