【发布时间】:2014-07-12 08:57:05
【问题描述】:
我想在下拉组件外部发生点击时关闭下拉菜单。
我该怎么做?
【问题讨论】:
标签: reactjs
我想在下拉组件外部发生点击时关闭下拉菜单。
我该怎么做?
【问题讨论】:
标签: reactjs
使用生命周期方法向文档添加和删除事件监听器。
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/
【讨论】:
this.refs.component 指的是从 0.14 开始的 DOM 元素 facebook.github.io/react/blog/2015/07/03/…
在元素中我添加了mousedown 和mouseup,如下所示:
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 隐藏它时。
【讨论】:
preventDefault() 或原生 Android 行为启动,React 的预处理会干扰.从那以后我写了npmjs.com/package/react-onclickoutside。
.backdrop)。.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;
}
【讨论】:
对于您的特定用例,当前接受的答案有点过度设计。如果您想在用户点击下拉列表时进行监听,只需使用 <select> 组件作为父元素并为其附加 onBlur 处理程序即可。
这种方法的唯一缺点是它假定用户已经保持对元素的关注,并且它依赖于表单控件(如果您考虑到 tab key 还聚焦和模糊元素) - 但这些缺点只是对更复杂的用例的真正限制,在这种情况下可能需要更复杂的解决方案。
var Dropdown = React.createClass({
handleBlur: function(e) {
// do something when user clicks outside of this element
},
render: function() {
return (
<select onBlur={this.handleBlur}>
...
</select>
);
}
});
【讨论】:
onBlur div 也适用于具有tabIndex 属性的 div
我为源自组件外部的事件编写了一个通用事件处理程序,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']);
【讨论】:
我对其中一个答案投了赞成票,尽管它对我不起作用。它最终导致我找到了这个解决方案。我稍微改变了操作顺序。我监听目标上的 mouseDown 和目标上的 mouseUp。如果其中任何一个返回 TRUE,我们就不会关闭模式。一旦注册了点击,在任何地方,这两个布尔值 { 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>
);
}
这样做的好处是所有代码都由子组件而不是父组件。这意味着在重用该组件时无需复制样板代码。
【讨论】:
使用优秀的react-onclickoutside mixin:
npm install --save react-onclickoutside
然后
var Component = React.createClass({
mixins: [
require('react-onclickoutside')
],
handleClickOutside: function(evt) {
// ...handling code goes here...
}
});
【讨论】:
派对迟到了,但我已经成功地在下拉列表的父元素上设置了一个模糊事件,并使用相关代码关闭下拉列表,并将 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。
【讨论】:
您可以使用refs 来实现这一点,如下所示应该可以。
将ref 添加到您的元素中:
<div ref={(element) => { this.myElement = element; }}></div>
然后您可以添加一个函数来处理元素外部的点击,如下所示:
handleClickOutside(e) {
if (!this.myElement.contains(e)) {
this.setState({ myElementVisibility: false });
}
}
最后,在 will mount 和 will unmount 上添加和删除事件侦听器。
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
}
【讨论】:
this 引用在document.addEventListener() 中调用handleClickOutside 可能存在错误。否则,它会给出 Uncaught ReferenceError: handleClickOutside is not defined in componentWillMount()
我找到了一个更简单的方法。
您只需要在模态框上添加onHide(this.closeFunction)
<Modal onHide={this.closeFunction}>
...
</Modal>
假设你有一个关闭模式的功能。
【讨论】: