Подтвердить что ты не робот

Обработчик событий React и jQuery запущен в неправильном порядке

Я создаю меню, которое появляется в верхней части экрана. Когда пользователь нажимает на один из пунктов меню div, появляется много ссылок. Я не смогу скрыть этот div, нажав другой пункт меню (без проблем, уже реализованный), но также щелкнув в другом месте, затем этот элемент div и menuitems.

Я слышал о двух решениях:

  • Показать невидимый div, который находится за меню, и охватывает весь экран и добавьте к нему обработчик кликов.
  • Прикрепить обработчик события document.body.

Первый из них не подходит для меня, потому что этот div будет охватывать ссылки, которые находятся на странице, и я хочу, чтобы их можно было щелкнуть, даже после нажатия кнопки меню, и появился соответствующий div. Поэтому я попробовал вторую душу. Но проблема в том, что обработчик jquery click на теле запускается перед обработчиком компонента. Я понятия не имею, как заставить его сначала вызвать обработчик моих компонентов, а затем предотвратить распространение событий.

Вот код и скрипт js:

/** @jsx React.DOM */
var Menu = React.createClass({
    click: function(e) {
        console.log('component handled click - should be called before jquery one and prevent jquery handler from running at all');
        e.stopPropagation();
    },
    render: function(){
    console.log("component renders");
        return (
            <div>
                <div>| MenuItem1 | MenuItem2 | MenuItem 3 |</div>
                <br />
                <div className="drop">
                    MenuItem 1 (This div appears when you click menu item)
                    <ul>
                        <li><a href="#" onClick={this.click}>Item 1 - when I click this I don't want document.body.click to fire</a></li>
                        <li><a href="#" onClick={this.click}>Item 1 - when I click this I don't want document.body.click to fire</a></li>
                    </ul>
                </div>
            </div>
        );
    }
});

React.renderComponent(<Menu />, document.getElementById("menu"));

$(document).ready(function() {
    console.log('document is ready');
    $("body").click(function() {
        console.log('jquery handled click - should not happen before component handled click');
    });
});

http://jsfiddle.net/psonsx6j/

пусковой консоли, прежде чем запускать ее для объяснения проблемы

4b9b3361

Ответ 1

Поэтому я решил это. Когда вы добавляете обработчик событий в документ с помощью jQuery, этот обработчик событий запускается первым. Таким образом, вы не можете поймать событие и прекратить его распространение. Но когда вы прикрепляете обработчик с document.addEventlistener, этот обработчик вызывается последним, и у вас есть возможность реагировать:), чтобы щелкнуть мышью по компонентам React.

Я изменил код Майка, чтобы он работал (http://jsfiddle.net/s8hmstzz/):

/** @jsx React.DOM */
var Menu = React.createClass({
    getInitialState: function() {
        return {
            show: true
        }
    },
    componentDidMount: function() {
        // when attaching with jquery this event handler is run before other handlers
        //$('body').bind('click', this.bodyClickHandler);
        document.addEventListener('click', this.bodyClickHandler);
    },
    componentWillUnmount: function() {
        //$('body').unbind('click', this.bodyClickHandler);
        document.removeEventListener('click', this.bodyClickHandler);
    },
    anchorClickHandler: function(e) {
        console.log('click on anchor - doing some stuff and then stopping propation');
        e.stopPropagation();
        e.nativeEvent.stopImmediatePropagation();
    },
    bodyClickHandler: function(e) {
        console.log('bodyclickhandler');
        this.setState({show: false})
    },
    clickHandler: function(e) {
        // react to click on menu item
    },
    preventEventBubbling: function(e) {
        console.log('click on div - stopping propagation');
        e.stopPropagation();
        e.nativeEvent.stopImmediatePropagation();
    },
    render: function() {
        return (
            <div>
                <a href="#" onClick={this.anchorClickHandler}>Link - will stop propagation</a>
                <div id="menudrop" onClick={this.preventEventBubbling} style={{display: this.state.show ? 'block' : 'none'}}>
                    <ul>
                        <li onClick={this.clickHandler}>Menu Item</li>
                    </ul>
                </div>
            </div>
        )
    }
});

React.renderComponent(<Menu />, document.getElementById('menu'));

Запустите консоль, а затем нажмите div, anchor и body, чтобы увидеть, как она работает. Я не знаю, почему использование addEventListener вместо jQuery меняет порядок обработчиков событий.

Ответ 2

Смотрите мой комментарий выше.

В основном вы не можете полагаться на возможность прекратить распространение события типа onClick в React при использовании jquery (или обычных событий JS) в сочетании.

Что вы можете сделать, это проверить свою функцию $('body').click(), которую вы не нажимаете на меню (поэтому она не закрывает ваше меню после нажатия на элемент). Также стоит отметить, что вы должны привязать это событие тела INSIDE к компоненту реакции, чтобы вы могли его легко добавлять/удалять только путем установки компонента и не изменять функциональные возможности компонента тем, что сам компонент ничего не знает о.

Итак, что вы делаете, это что-то вроде этого (nb: непроверенный код, но на основе компонента, который у меня есть с этой же проблемой)

var Menu = React.createClass({
    getInitialState: function() {
        return {
            show: true
        }
    },
    componentDidMount: function() {
        $('body').bind('click', this.bodyClickHandler);
    },
    componentWillUnmount: function() {
        $('body').unbind('click', this.bodyClickHandler);
    },
    bodyClickHandler: function(e) {
        if ($(e.target).parents('div').get(0) !== this.getDOMNode()) {
            this.setState({
                show: false
            })
        }
    },
    clickHandler: function(e) {
        // react to click on menu item
    },
    render: function() {
        return (
            <div style={{display: this.state.show ? 'block' : 'none'}}>
                <ul>
                    <li onClick={this.clickHandler}>Menu Item</li>
                </ul>
            </div>
        )
    }
});

Итак, в приведенном выше коде мы предполагаем, что вы хотите отобразить раскрывающееся меню, как только компонент будет установлен в dom. Это может быть не так, поскольку вы, вероятно, захотите смонтировать компонент, а затем показать/скрыть его на основе зависания. В этом случае вам просто нужно переместить привязку события $('body').bind() от componentDidMount к обратному вызову везде, где вы показываете меню. $('body').unbind(), вероятно, следует перемещать в том случае, если компонент скрыт. Затем вы получаете идеальную ситуацию, когда при отображении меню мы привязываем событие тела, ожидая щелчка, и отключаем его всякий раз, когда он скрыт и/или отключен.

Что происходит, когда bodyClickHandler вызывается до clickHandler при нажатии на меню, однако bodyClickHandler скрывает только меню, если вы щелкнули родительское дерево, не содержали корневого div нашего компонента (this.getDOMNode() возвращает элемент html корня node внутри render() return).

В результате щелчок за пределами компонента закроет его, щелчок внутри компонента ничего не сделает, щелчок по <li> будет срабатывать clickHandler. Все работает как ожидалось.

Надеюсь, что это поможет.