2014-09-08 3 views
1

Я работаю над модулем контекстного меню в ReactJS, и это заставило меня задуматься о том, как иметь дело с неиерархическими компонентами.ReactJS и неиерархические компоненты

Проблема, с которой я столкнулся, заключается в том, что многие различные элементы приложения могут использовать контекстное меню. Обычно в React вы передаете обратный вызов от родительского объекта к дочерним элементам, которые должны взаимодействовать с родителем. Например, моя первая мысль заключалась в том, что функция openContextMenu(mousePosition, optionsObject) передана из моего класса ContextMenu ко всем элементам, которые хотят отобразить контекстное меню, щелкнув правой кнопкой мыши.

Но для всех таких элементов (или, может быть, даже любых) это не имеет смысла быть детьми контекстного меню! Контекстное меню не является иерархическим по отношению к другим компонентам приложения. В Angular я, вероятно, написал бы службу ContextMenu, которая потребовала бы компонентов, если бы они хотели получить доступ к такому меню.

Это ситуация, в которой должен использоваться глобальный обработчик событий? Я думаю об этом все неправильно? Каков способ реагирования на такое горизонтальное взаимодействие между компонентами?

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

Очень круто! Вот версия демонстрации, в которой есть рабочая библиотека EventEmitter и подчеркивание, чтобы она работала в области предварительного просмотра. http://jsbin.com/gamamidaloru/7/edit –

+0

Хм. Похоже, этот мусор не спасся. Я вижу, что «EventEmitter2 не определен» в консоли. – bennlich

+0

Я не знаком с Реактивом, но разве эти слушатели событий утечки никогда не расходятся? – Bergi

Смежные вопросы