我一直在研究ReactJS中的上下文菜单模块,它让我思考如何处理非分层组件。
我遇到的问题是应用程序中的许多不同项目可能想要使用上下文菜单。通常在React中,您将从父对象的回调传递给需要与父进程通信的子进程。例如,我的第一个想法是将openContextMenu(mousePosition, optionsObject)
函数从我的ContextMenu
类传递到所有想要在右键单击上显示上下文菜单的元素。
但是对于所有这些元素(或者甚至可能是任何元素)来说,上下文菜单的孩子都没有意义!相对于应用程序的其他组件,上下文菜单不是分层的。在Angular中,如果他们想要访问这样的菜单,我可能会编写一个ContextMenu
服务组件。
这是应该使用全局事件处理程序的情况吗?我觉得这一切都错了吗?什么是React方法来处理组件之间的这种水平交互?
答案 0 :(得分:6)
上下文菜单很特别。任何时候都不应该打开多个上下文菜单。它们也很特别,因为它可以从任何地方打开。尝试使用demo来了解放在一起时的外观。
为了解决我们的全局问题,我们将创建一个包装私有事件发射器的mixin。
var menuEvents = new events.EventEmitter();
var ContextMenuMixin = {
// this.openContextMenu(['foo', 'bar'], (err, choice) => void)
openContextMenu: function(options, callback){
menuEvents.emit('open', {
options: options,
callback: callback
});
},
closeContextMenu: function(){
menuEvents.emit('close');
}
};
现在对于组件,我们需要做一些事情。这是初始化部分。只是绑定到一些事件,以及轻量级鼠标跟踪。
var mouse = {x: 0, y: 0};
var updateMouse = function(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
};
var ContextMenu = React.createClass({
getInitialState: function(){
return {options: null, callback: null};
},
componentDidMount: function(){
menuEvents.addListener('open', this.handleOpenEvent);
menuEvents.addListener('close', this.closeMenu);
addEventListener('mousemove', updateMouse);
},
这些事件处理程序非常简单。 handleOpenEvent只是将事件有效负载和鼠标位置存储在状态中,这有效地锁定了鼠标位置,直到它下一次打开。而对应的只是重置状态,并调用回调错误。
handleOpenEvent: function(payload){
this.setState(_.merge({}, payload, mouse));
},
closeMenu: function(){
if (this.state.callback) {
this.replaceState(this.getInitialState());
this.state.callback(new Error('no selection made'));
}
},
最后,我们渲染传递给事件的选项列表,并为每个选项创建点击处理程序。
render: function(){
if (!this.state.options) {
return <div />
}
var style = {
left: this.state.x,
top: this.state.y,
position: 'fixed'
};
return (
<div className="react-contextmenu" style={style}>
<ul className="react-contextmenu-options">
{this.state.options.map(function(x, i){
return <li key={i}
onClick={this.makeClickHandler(x)}>
{x}
</li>
}, this)}
</ul>
</div>
);
},
makeClickHandler: function(option){
return function(){
if (this.state.callback) {
this.state.callback(null, option);
this.replaceState(this.getInitialState());
}
}.bind(this);
}